Merge "Fix: wrong size checking in SparseTable."
diff --git a/CleanSpec.mk b/CleanSpec.mk
index c274a6c..be13c30 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -51,6 +51,7 @@
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libjni_latinime_intermediates)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libjni_latinime_intermediates)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libjni_latinime_intermediates)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libjni_latinime_intermediates)
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
 # ************************************************
diff --git a/java/src/com/android/inputmethod/event/Combiner.java b/java/src/com/android/inputmethod/event/Combiner.java
index c3869a2..bdc7612 100644
--- a/java/src/com/android/inputmethod/event/Combiner.java
+++ b/java/src/com/android/inputmethod/event/Combiner.java
@@ -34,4 +34,10 @@
      * @return the resulting event.
      */
     Event processEvent(ArrayList<Event> previousEvents, Event event);
+
+    /**
+     * Get the feedback that should be shown to the user for the current state of this combiner.
+     * @return A CharSequence representing the feedback to show users. It may include styles.
+     */
+    CharSequence getCombiningStateFeedback();
 }
diff --git a/java/src/com/android/inputmethod/event/CombinerChain.java b/java/src/com/android/inputmethod/event/CombinerChain.java
index 0e01c81..cf2a4d1 100644
--- a/java/src/com/android/inputmethod/event/CombinerChain.java
+++ b/java/src/com/android/inputmethod/event/CombinerChain.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.event;
 
+import android.text.SpannableStringBuilder;
+
 import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.util.ArrayList;
@@ -33,8 +35,10 @@
  * a colored background.
  */
 public class CombinerChain {
-    // TODO: Create an object type to represent input material + visual feedback + decoding state
-
+    // The already combined text, as described above
+    private StringBuilder mCombinedText;
+    // The feedback on the composing state, as described above
+    private SpannableStringBuilder mStateFeedback;
     private final ArrayList<Combiner> mCombiners;
 
     /**
@@ -50,9 +54,15 @@
         mCombiners = CollectionUtils.newArrayList();
         // The dead key combiner is always active, and always first
         mCombiners.add(new DeadKeyCombiner());
+        mCombinedText = new StringBuilder();
+        mStateFeedback = new SpannableStringBuilder();
     }
 
-    // Pass a new event through the whole chain.
+    /**
+     * Pass a new event through the whole chain.
+     * @param previousEvents the list of previous events in this composition
+     * @param newEvent the new event to process
+     */
     public void processEvent(final ArrayList<Event> previousEvents, final Event newEvent) {
         final ArrayList<Event> modifiablePreviousEvents = new ArrayList<Event>(previousEvents);
         Event event = newEvent;
@@ -62,8 +72,24 @@
             event = combiner.processEvent(modifiablePreviousEvents, event);
             if (null == event) {
                 // Combiners return null if they eat the event.
-                return;
+                break;
             }
         }
+        if (null != event) {
+            mCombinedText.append(event.getTextToCommit());
+        }
+        mStateFeedback.clear();
+        for (int i = mCombiners.size() - 1; i >= 0; --i) {
+            mStateFeedback.append(mCombiners.get(i).getCombiningStateFeedback());
+        }
+    }
+
+    /**
+     * Get the char sequence that should be displayed as the composing word. It may include
+     * styling spans.
+     */
+    public CharSequence getComposingWordWithCombiningFeedback() {
+        final SpannableStringBuilder s = new SpannableStringBuilder(mCombinedText);
+        return s.append(mStateFeedback);
     }
 }
diff --git a/java/src/com/android/inputmethod/event/DeadKeyCombiner.java b/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
index f77ce63..f891017 100644
--- a/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
+++ b/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
@@ -61,4 +61,9 @@
             }
         }
     }
+
+    @Override
+    public CharSequence getCombiningStateFeedback() {
+        return mDeadSequence;
+    }
 }
diff --git a/java/src/com/android/inputmethod/event/Event.java b/java/src/com/android/inputmethod/event/Event.java
index bd4143d..2bfe073 100644
--- a/java/src/com/android/inputmethod/event/Event.java
+++ b/java/src/com/android/inputmethod/event/Event.java
@@ -18,6 +18,7 @@
 
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.utils.StringUtils;
 
 /**
  * Class representing a generic input event as handled by Latin IME.
@@ -52,6 +53,8 @@
     final public static int EVENT_GESTURE = 4;
     // An event corresponding to the manual pick of a suggestion.
     final public static int EVENT_SUGGESTION_PICKED = 5;
+    // An event corresponding to a string generated by some software process.
+    final public static int EVENT_SOFTWARE_GENERATED_STRING = 6;
 
     // 0 is a valid code point, so we use -1 here.
     final public static int NOT_A_CODE_POINT = -1;
@@ -71,6 +74,9 @@
     // it's not relevant.
     final public int mCodePoint;
 
+    // If applicable, this contains the string that should be input.
+    final public CharSequence mText;
+
     // The key code associated with the event, if relevant. This is relevant whenever this event
     // has been triggered by a key press, but not for a gesture for example. This has conceptually
     // no link to the code point, although keys that enter a straight code point may often set
@@ -96,9 +102,11 @@
     final public Event mNextEvent;
 
     // This method is private - to create a new event, use one of the create* utility methods.
-    private Event(final int type, final int codePoint, final int keyCode, final int x, final int y,
-            final SuggestedWordInfo suggestedWordInfo, final int flags, final Event next) {
+    private Event(final int type, final CharSequence text, final int codePoint, final int keyCode,
+            final int x, final int y, final SuggestedWordInfo suggestedWordInfo, final int flags,
+            final Event next) {
         mType = type;
+        mText = text;
         mCodePoint = codePoint;
         mKeyCode = keyCode;
         mX = x;
@@ -123,13 +131,13 @@
 
     public static Event createSoftwareKeypressEvent(final int codePoint, final int keyCode,
             final int x, final int y) {
-        return new Event(EVENT_INPUT_KEYPRESS, codePoint, keyCode, x, y,
+        return new Event(EVENT_INPUT_KEYPRESS, null /* text */, codePoint, keyCode, x, y,
                 null /* suggestedWordInfo */, FLAG_NONE, null);
     }
 
     public static Event createHardwareKeypressEvent(final int codePoint, final int keyCode,
             final Event next) {
-        return new Event(EVENT_INPUT_KEYPRESS, codePoint, keyCode,
+        return new Event(EVENT_INPUT_KEYPRESS, null /* text */, codePoint, keyCode,
                 Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE,
                 null /* suggestedWordInfo */, FLAG_NONE, next);
     }
@@ -137,7 +145,7 @@
     // This creates an input event for a dead character. @see {@link #FLAG_DEAD}
     public static Event createDeadEvent(final int codePoint, final int keyCode, final Event next) {
         // TODO: add an argument or something if we ever create a software layout with dead keys.
-        return new Event(EVENT_INPUT_KEYPRESS, codePoint, keyCode,
+        return new Event(EVENT_INPUT_KEYPRESS, null /* text */, codePoint, keyCode,
                 Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE,
                 null /* suggestedWordInfo */, FLAG_DEAD, next);
     }
@@ -151,7 +159,7 @@
      */
     public static Event createEventForCodePointFromUnknownSource(final int codePoint) {
         // TODO: should we have a different type of event for this? After all, it's not a key press.
-        return new Event(EVENT_INPUT_KEYPRESS, codePoint, NOT_A_KEY_CODE,
+        return new Event(EVENT_INPUT_KEYPRESS, null /* text */, codePoint, NOT_A_KEY_CODE,
                 Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
                 null /* suggestedWordInfo */, FLAG_NONE, null /* next */);
     }
@@ -167,7 +175,7 @@
     public static Event createEventForCodePointFromAlreadyTypedText(final int codePoint,
             final int x, final int y) {
         // TODO: should we have a different type of event for this? After all, it's not a key press.
-        return new Event(EVENT_INPUT_KEYPRESS, codePoint, NOT_A_KEY_CODE, x, y,
+        return new Event(EVENT_INPUT_KEYPRESS, null /* text */, codePoint, NOT_A_KEY_CODE, x, y,
                 null /* suggestedWordInfo */, FLAG_NONE, null /* next */);
     }
 
@@ -176,13 +184,28 @@
      * @return an event for this suggestion pick.
      */
     public static Event createSuggestionPickedEvent(final SuggestedWordInfo suggestedWordInfo) {
-        return new Event(EVENT_SUGGESTION_PICKED, NOT_A_CODE_POINT, NOT_A_KEY_CODE,
+        return new Event(EVENT_SUGGESTION_PICKED, suggestedWordInfo.mWord,
+                NOT_A_CODE_POINT, NOT_A_KEY_CODE,
                 Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE,
                 suggestedWordInfo, FLAG_NONE, null);
     }
 
+    /**
+     * Creates an input event with a CharSequence. This is used by some software processes whose
+     * output is a string, possibly with styling. Examples include press on a multi-character key,
+     * or combination that outputs a string.
+     * @param text the CharSequence associated with this event.
+     * @param keyCode the key code, or NOT_A_KEYCODE if not applicable.
+     * @return an event for this text.
+     */
+    public static Event createSoftwareTextEvent(final CharSequence text, final int keyCode) {
+        return new Event(EVENT_SOFTWARE_GENERATED_STRING, text, NOT_A_CODE_POINT, keyCode,
+                Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
+                null /* suggestedWordInfo */, FLAG_NONE, null /* next */);
+    }
+
     public static Event createNotHandledEvent() {
-        return new Event(EVENT_NOT_HANDLED, NOT_A_CODE_POINT, NOT_A_KEY_CODE,
+        return new Event(EVENT_NOT_HANDLED, null /* text */, NOT_A_CODE_POINT, NOT_A_KEY_CODE,
                 Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
                 null /* suggestedWordInfo */, FLAG_NONE, null);
     }
@@ -198,12 +221,22 @@
         return EVENT_INPUT_KEYPRESS == mType && Constants.SUGGESTION_STRIP_COORDINATE == mX;
     }
 
-    // TODO: remove this method - we should not have to test this
-    public boolean isCommittable() {
-        return EVENT_INPUT_KEYPRESS == mType || EVENT_MODE_KEY == mType || EVENT_TOGGLE == mType;
-    }
-
     public boolean isHandled() {
         return EVENT_NOT_HANDLED != mType;
     }
+
+    public CharSequence getTextToCommit() {
+        switch (mType) {
+        case EVENT_MODE_KEY:
+        case EVENT_NOT_HANDLED:
+            return "";
+        case EVENT_INPUT_KEYPRESS:
+        case EVENT_TOGGLE:
+            return StringUtils.newSingleCodePointString(mCodePoint);
+        case EVENT_GESTURE:
+        case EVENT_SOFTWARE_GENERATED_STRING:
+            return mText;
+        }
+        throw new RuntimeException("Unknown event type: " + mType);
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 66cb9e3..2dfde94 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -163,7 +163,7 @@
         mCurrentSettingsValues = settingsValues;
         try {
             mState.onLoadKeyboard();
-            mKeyboardTextsSet.setLocale(mSubtypeSwitcher.getCurrentSubtypeLocale());
+            mKeyboardTextsSet.setLocale(mSubtypeSwitcher.getCurrentSubtypeLocale(), mThemeContext);
         } catch (KeyboardLayoutSetException e) {
             Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
             LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause());
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index 81a8e71..dfe0df0 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -34,7 +34,6 @@
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.utils.ResourceUtils;
-import com.android.inputmethod.latin.utils.RunInLocale;
 import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 import com.android.inputmethod.latin.utils.XmlParseUtils;
@@ -45,7 +44,6 @@
 
 import java.io.IOException;
 import java.util.Arrays;
-import java.util.Locale;
 
 /**
  * Keyboard Building helper.
@@ -278,18 +276,7 @@
 
             params.mThemeId = keyboardAttr.getInt(R.styleable.Keyboard_themeId, 0);
             params.mIconsSet.loadIcons(keyboardAttr);
-            final Locale locale = params.mId.mLocale;
-            params.mTextsSet.setLocale(locale);
-            final RunInLocale<Void> job = new RunInLocale<Void>() {
-                @Override
-                protected Void job(final Resources res) {
-                    params.mTextsSet.loadStringResources(mContext);
-                    return null;
-                }
-            };
-            // Null means the current system locale.
-            job.runInLocale(mResources,
-                    SubtypeLocaleUtils.isNoLanguage(params.mId.mSubtype) ? null : locale);
+            params.mTextsSet.setLocale(params.mId.mLocale, mContext);
 
             final int resourceId = keyboardAttr.getResourceId(
                     R.styleable.Keyboard_touchPositionCorrectionData, 0);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index bdc36ed..044cd11 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -23,6 +23,8 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.RunInLocale;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
 import java.util.HashMap;
 import java.util.Locale;
@@ -38,17 +40,22 @@
     // Resource name to text map.
     private HashMap<String, String> mResourceNameToTextsMap = CollectionUtils.newHashMap();
 
-    public void setLocale(final Locale locale) {
+    public void setLocale(final Locale locale, final Context context) {
         final String language = locale.getLanguage();
         mTextsTable = KeyboardTextsTable.getTextsTable(language);
-    }
-
-    // TODO: Consolidate this method with {@link #setLocale(Locale)}.
-    public void loadStringResources(final Context context) {
         final Resources res = context.getResources();
         final int referenceId = context.getApplicationInfo().labelRes;
         final String resourcePackageName = res.getResourcePackageName(referenceId);
-        loadStringResourcesInternal(res, RESOURCE_NAMES, resourcePackageName);
+        final RunInLocale<Void> job = new RunInLocale<Void>() {
+            @Override
+            protected Void job(final Resources resource) {
+                loadStringResourcesInternal(res, RESOURCE_NAMES, resourcePackageName);
+                return null;
+            }
+        };
+        // Null means the current system locale.
+        job.runInLocale(res,
+                SubtypeLocaleUtils.NO_LANGUAGE.equals(locale.toString()) ? null : locale);
     }
 
     @UsedForTesting
@@ -129,7 +136,7 @@
 
     // These texts' name should be aligned with the @string/<name> in
     // values*/strings-action-keys.xml.
-    private static final String[] RESOURCE_NAMES = {
+    static final String[] RESOURCE_NAMES = {
         // Labels for action.
         "label_go_key",
         "label_send_key",
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index b6d4776..38e3864 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1277,7 +1277,9 @@
     // Called from PointerTracker through the KeyboardActionListener interface
     @Override
     public void onTextInput(final String rawText) {
-        mInputLogic.onTextInput(mSettings.getCurrent(), rawText, mHandler);
+        // TODO: have the keyboard pass the correct key code when we need it.
+        final Event event = Event.createSoftwareTextEvent(rawText, Event.NOT_A_KEY_CODE);
+        mInputLogic.onTextInput(mSettings.getCurrent(), event, mHandler);
         mKeyboardSwitcher.updateShiftState();
         mKeyboardSwitcher.onCodeInput(Constants.CODE_OUTPUT_TEXT);
     }
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 965518e..606bb77 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -687,13 +687,23 @@
     }
 
     public boolean isCursorTouchingWord(final SpacingAndPunctuations spacingAndPunctuations) {
-        final int codePointBeforeCursor = getCodePointBeforeCursor();
-        if (Constants.NOT_A_CODE == codePointBeforeCursor
-                || spacingAndPunctuations.isWordSeparator(codePointBeforeCursor)
-                || spacingAndPunctuations.isWordConnector(codePointBeforeCursor)) {
-            return isCursorFollowedByWordCharacter(spacingAndPunctuations);
+        if (isCursorFollowedByWordCharacter(spacingAndPunctuations)) {
+            // If what's after the cursor is a word character, then we're touching a word.
+            return true;
         }
-        return true;
+        final String textBeforeCursor = mCommittedTextBeforeComposingText.toString();
+        int indexOfCodePointInJavaChars = textBeforeCursor.length();
+        int consideredCodePoint = 0 == indexOfCodePointInJavaChars ? Constants.NOT_A_CODE
+                : textBeforeCursor.codePointBefore(indexOfCodePointInJavaChars);
+        // Search for the first non word-connector char
+        if (spacingAndPunctuations.isWordConnector(consideredCodePoint)) {
+            indexOfCodePointInJavaChars -= Character.charCount(consideredCodePoint);
+            consideredCodePoint = 0 == indexOfCodePointInJavaChars ? Constants.NOT_A_CODE
+                    : textBeforeCursor.codePointBefore(indexOfCodePointInJavaChars);
+        }
+        return !(Constants.NOT_A_CODE == consideredCodePoint
+                || spacingAndPunctuations.isWordSeparator(consideredCodePoint)
+                || spacingAndPunctuations.isWordConnector(consideredCodePoint));
     }
 
     public boolean isCursorFollowedByWordCharacter(
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 29382fe..d55a773 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -193,7 +193,10 @@
         final int keyY = event.mY;
         final int newIndex = size();
         mCombinerChain.processEvent(mEvents, event);
-        mTypedWord.appendCodePoint(primaryCode);
+        // TODO: remove mTypedWord and compute it dynamically when necessary. We also need to
+        // make the views of the composing word a SpannableString.
+        mTypedWord.replace(0, mTypedWord.length(),
+                mCombinerChain.getComposingWordWithCombiningFeedback().toString());
         mEvents.add(event);
         refreshSize();
         mCursorPositionWithinWord = mCodePointSize;
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 8faf175..36b30ea 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -161,11 +161,12 @@
      * some additional keys for example.
      *
      * @param settingsValues the current values of the settings.
-     * @param rawText the text to input.
+     * @param event the input event containing the data.
      */
-    public void onTextInput(final SettingsValues settingsValues, final String rawText,
+    public void onTextInput(final SettingsValues settingsValues, final Event event,
             // TODO: remove this argument
             final LatinIME.UIHandler handler) {
+        final String rawText = event.mText.toString();
         mConnection.beginBatchEdit();
         if (mWordComposer.isComposingWord()) {
             commitCurrentAutoCorrection(settingsValues, rawText, handler);
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTestsBase.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTestsBase.java
index c342533..7923afc 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTestsBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTestsBase.java
@@ -22,12 +22,8 @@
 import static com.android.inputmethod.latin.Constants.CODE_OUTPUT_TEXT;
 import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED;
 
-import android.content.Context;
-import android.content.res.Resources;
 import android.test.AndroidTestCase;
 
-import com.android.inputmethod.latin.utils.RunInLocale;
-
 import java.util.Locale;
 
 abstract class KeySpecParserTestsBase extends AndroidTestCase {
@@ -51,16 +47,7 @@
     protected void setUp() throws Exception {
         super.setUp();
 
-        mTextsSet.setLocale(TEST_LOCALE);
-        final Context context = getContext();
-        new RunInLocale<Void>() {
-            @Override
-            protected Void job(final Resources res) {
-                mTextsSet.loadStringResources(context);
-                return null;
-            }
-        }.runInLocale(context.getResources(), TEST_LOCALE);
-
+        mTextsSet.setLocale(TEST_LOCALE, getContext());
         mCodeSettings = KeyboardCodesSet.getCode(CODE_SETTINGS_NAME);
         mCodeActionNext = KeyboardCodesSet.getCode("key_action_next");
         mSettingsIconId = KeyboardIconsSet.getIconId(ICON_SETTINGS_NAME);
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSetTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSetTests.java
index 17d7687..eb906cd 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSetTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSetTests.java
@@ -17,7 +17,6 @@
 package com.android.inputmethod.keyboard.internal;
 
 import android.content.Context;
-import android.content.res.Resources;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.inputmethod.InputMethodInfo;
@@ -25,7 +24,6 @@
 
 import com.android.inputmethod.latin.RichInputMethodManager;
 import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.RunInLocale;
 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
 import java.util.ArrayList;
@@ -58,10 +56,11 @@
     // subtypes. The text is needed to implement Emoji Keyboard, see
     // {@link KeyboardSwitcher#setEmojiKeyboard()}.
     public void testSwitchToAlphaKeyLabel() {
+        final Context context = getContext();
         final KeyboardTextsSet textsSet = new KeyboardTextsSet();
         for (final InputMethodSubtype subtype : mAllSubtypesList) {
             final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype);
-            textsSet.setLocale(locale);
+            textsSet.setLocale(locale, context);
             final String switchToAlphaKeyLabel = textsSet.getText(
                     KeyboardTextsSet.SWITCH_TO_ALPHA_KEY_LABEL);
             assertNotNull("Switch to alpha key label of " + locale, switchToAlphaKeyLabel);
@@ -87,15 +86,7 @@
         final KeyboardTextsSet textsSet = new KeyboardTextsSet();
         for (final InputMethodSubtype subtype : mAllSubtypesList) {
             final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype);
-            textsSet.setLocale(locale);
-            final RunInLocale<Void> job = new RunInLocale<Void>() {
-                @Override
-                protected Void job(final Resources res) {
-                    textsSet.loadStringResources(context);
-                    return null;
-                }
-            };
-            job.runInLocale(context.getResources(), locale);
+            textsSet.setLocale(locale, context);
             for (final String name : TEXT_NAMES_FROM_RESOURCE) {
                 final String text = textsSet.getText(name);
                 assertNotNull(name + " of " + locale, text);
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecSplitTests.java b/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecSplitTests.java
index d01c2ec..a193307 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecSplitTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/MoreKeySpecSplitTests.java
@@ -23,7 +23,6 @@
 import android.test.suitebuilder.annotation.MediumTest;
 
 import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.RunInLocale;
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
@@ -41,14 +40,7 @@
 
         final Instrumentation instrumentation = getInstrumentation();
         final Context targetContext = instrumentation.getTargetContext();
-        mTextsSet.setLocale(TEST_LOCALE);
-        new RunInLocale<Void>() {
-            @Override
-            protected Void job(final Resources res) {
-                mTextsSet.loadStringResources(targetContext);
-                return null;
-            }
-        }.runInLocale(targetContext.getResources(), TEST_LOCALE);
+        mTextsSet.setLocale(TEST_LOCALE, targetContext);
         final String[] testResourceNames = getAllResourceIdNames(
                 com.android.inputmethod.latin.tests.R.string.class);
         final Context testContext = instrumentation.getContext();
diff --git a/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java b/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
index 7f07435..842f3f3 100644
--- a/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
+++ b/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
@@ -89,6 +89,10 @@
             mExtractedText = extractedText;
         }
 
+        public int cursorPos() {
+            return mTextBefore.length();
+        }
+
         /* (non-Javadoc)
          * @see android.view.inputmethod.InputConnectionWrapper#getTextBeforeCursor(int, int)
          */
@@ -131,13 +135,16 @@
     }
 
     private class MockInputMethodService extends InputMethodService {
-        InputConnection mInputConnection;
-        public void setInputConnection(final InputConnection inputConnection) {
-            mInputConnection = inputConnection;
+        private MockConnection mMockConnection;
+        public void setInputConnection(final MockConnection mockConnection) {
+            mMockConnection = mockConnection;
+        }
+        public int cursorPos() {
+            return mMockConnection.cursorPos();
         }
         @Override
         public InputConnection getCurrentInputConnection() {
-            return mInputConnection;
+            return mMockConnection;
         }
     }
 
@@ -336,4 +343,82 @@
         suggestions = r.getSuggestionSpansAtWord();
         assertEquals(suggestions.length, 0);
     }
+
+    public void testCursorTouchingWord() {
+        final MockInputMethodService ims = new MockInputMethodService();
+        final RichInputConnection ic = new RichInputConnection(ims);
+        final SpacingAndPunctuations sap = mSpacingAndPunctuations;
+
+        ims.setInputConnection(new MockConnection("users", 5));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertTrue(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection("users'", 5));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertTrue(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection("users'", 6));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertTrue(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection("'users'", 6));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertTrue(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection("'users'", 7));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertTrue(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection("users '", 6));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertFalse(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection("users '", 7));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertFalse(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection("re-", 3));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertTrue(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection("re--", 4));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertFalse(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection("-", 1));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertFalse(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection("--", 2));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertFalse(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection(" -", 2));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertFalse(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection(" --", 3));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertFalse(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection(" users '", 1));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertTrue(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection(" users '", 3));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertTrue(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection(" users '", 7));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertFalse(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection(" users are", 7));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertTrue(ic.isCursorTouchingWord(sap));
+
+        ims.setInputConnection(new MockConnection(" users 'are", 7));
+        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
+        assertFalse(ic.isCursorTouchingWord(sap));
+    }
 }
diff --git a/tools/dicttool/compat/com/android/inputmethod/event/CombinerChain.java b/tools/dicttool/compat/com/android/inputmethod/event/CombinerChain.java
index 66ad60c..e70ede4 100644
--- a/tools/dicttool/compat/com/android/inputmethod/event/CombinerChain.java
+++ b/tools/dicttool/compat/com/android/inputmethod/event/CombinerChain.java
@@ -21,6 +21,12 @@
 import java.util.ArrayList;
 
 public class CombinerChain {
+    private StringBuilder mComposingWord = new StringBuilder();
     public CombinerChain(final Combiner... combinerList) {}
-    public void processEvent(final ArrayList<Event> previousEvents, final Event newEvent) {}
+    public void processEvent(final ArrayList<Event> previousEvents, final Event newEvent) {
+        mComposingWord.append(newEvent.getTextToCommit());
+    }
+    public CharSequence getComposingWordWithCombiningFeedback() {
+        return mComposingWord;
+    }
 }