Merge "[IL71] Add indices to toCodePointArray."
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 77e99bf..0477133 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -216,8 +216,9 @@
     public static final int CODE_LANGUAGE_SWITCH = -10;
     public static final int CODE_EMOJI = -11;
     public static final int CODE_SHIFT_ENTER = -12;
+    public static final int CODE_SYMBOL_SHIFT = -13;
     // Code value representing the code is not specified.
-    public static final int CODE_UNSPECIFIED = -13;
+    public static final int CODE_UNSPECIFIED = -14;
 
     public static boolean isLetterCode(final int code) {
         return code >= CODE_SPACE;
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index aca3619..a4253bb 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -59,6 +59,7 @@
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
 import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
+import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
 import com.android.inputmethod.keyboard.KeyboardId;
@@ -79,6 +80,7 @@
 import com.android.inputmethod.latin.utils.ApplicationUtils;
 import com.android.inputmethod.latin.utils.CapsModeUtils;
 import com.android.inputmethod.latin.utils.CompletionInfoUtils;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
 import com.android.inputmethod.latin.utils.IntentUtils;
 import com.android.inputmethod.latin.utils.JniUtils;
 import com.android.inputmethod.latin.utils.LatinImeLoggerUtils;
@@ -203,7 +205,7 @@
             case MSG_RESUME_SUGGESTIONS:
                 latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor(
                         latinIme.mSettings.getCurrent(),
-                        false /* includeResumedWordInSuggestions */, latinIme.mKeyboardSwitcher);
+                        false /* includeResumedWordInSuggestions */);
                 break;
             case MSG_REOPEN_DICTIONARIES:
                 latinIme.initSuggest();
@@ -217,10 +219,15 @@
                         (SuggestedWords) msg.obj, latinIme.mKeyboardSwitcher);
                 break;
             case MSG_RESET_CACHES:
-                latinIme.mInputLogic.retryResetCaches(latinIme.mSettings.getCurrent(),
+                final SettingsValues settingsValues = latinIme.mSettings.getCurrent();
+                if (latinIme.mInputLogic.retryResetCachesAndReturnSuccess(settingsValues,
                         msg.arg1 == 1 /* tryResumeSuggestions */,
-                        msg.arg2 /* remainingTries */,
-                        latinIme.mKeyboardSwitcher, this);
+                        msg.arg2 /* remainingTries */, this /* handler */)) {
+                    // If we were able to reset the caches, then we can reload the keyboard.
+                    // Otherwise, we'll do it when we can.
+                    latinIme.mKeyboardSwitcher.loadKeyboard(latinIme.getCurrentInputEditorInfo(),
+                            settingsValues);
+                }
                 break;
             }
         }
@@ -1194,6 +1201,30 @@
         return mSubtypeSwitcher.getCurrentSubtypeLocale();
     }
 
+    /**
+     * @param codePoints code points to get coordinates for.
+     * @return x,y coordinates for this keyboard, as a flattened array.
+     */
+    public int[] getCoordinatesForCurrentKeyboard(final int[] codePoints) {
+        return getCoordinatesForKeyboard(codePoints, mKeyboardSwitcher.getKeyboard());
+    }
+
+    public static int[] getCoordinatesForKeyboard(final int[] codePoints, final Keyboard keyboard) {
+        final int length = codePoints.length;
+        final int[] coordinates = CoordinateUtils.newCoordinateArray(length);
+        Key key;
+        for (int i = 0; i < length; ++i) {
+            if (keyboard != null && (key = keyboard.getKey(codePoints[i])) != null) {
+                CoordinateUtils.setXYInArray(coordinates, i,
+                        key.getX() + key.getWidth() / 2, key.getY() + key.getHeight() / 2);
+            } else {
+                CoordinateUtils.setXYInArray(coordinates, i,
+                        Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+            }
+        }
+        return coordinates;
+    }
+
     // Callback for the {@link SuggestionStripView}, to call when the "add to dictionary" hint is
     // pressed.
     @Override
@@ -1255,8 +1286,22 @@
         // this transformation, it should be done already before calling onCodeInput.
         final int keyX = mainKeyboardView.getKeyX(x);
         final int keyY = mainKeyboardView.getKeyY(y);
-        mInputLogic.onCodeInput(codePoint, keyX, keyY, mHandler, mKeyboardSwitcher,
+        final int codeToSend;
+        if (Constants.CODE_SHIFT == codePoint) {
+            // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
+            // alphabetic shift and shift while in symbol layout.
+            final Keyboard currentKeyboard = mKeyboardSwitcher.getKeyboard();
+            if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {
+                codeToSend = codePoint;
+            } else {
+                codeToSend = Constants.CODE_SYMBOL_SHIFT;
+            }
+        } else {
+            codeToSend = codePoint;
+        }
+        mInputLogic.onCodeInput(codeToSend, keyX, keyY, mHandler, mKeyboardSwitcher,
                 mSubtypeSwitcher);
+        mKeyboardSwitcher.onCodeInput(codePoint);
     }
 
     // Called from PointerTracker through the KeyboardActionListener interface
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index ebf6563..6cd8401 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -16,8 +16,10 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
 import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.Arrays;
@@ -276,6 +278,8 @@
     /**
      * Add a dummy key by retrieving reasonable coordinates
      */
+    // TODO: make this private or remove it entirely. Right now it's used in the tests
+    @UsedForTesting
     public void addKeyInfo(final int codePoint, final Keyboard keyboard) {
         final int x, y;
         final Key key;
@@ -292,18 +296,18 @@
     /**
      * Set the currently composing word to the one passed as an argument.
      * This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity.
-     * @param word the char sequence to set as the composing word.
+     * @param codePoints the code points to set as the composing word.
+     * @param coordinates the x, y coordinates of the key in the CoordinateUtils format
      * @param previousWord the previous word, to use as context for suggestions. Can be null if
      *   the context is nil (typically, at start of text).
-     * @param keyboard the keyboard this is typed on, for coordinate info/proximity.
      */
-    public void setComposingWord(final CharSequence word, final CharSequence previousWord,
-            final Keyboard keyboard) {
+    public void setComposingWord(final int[] codePoints, final int[] coordinates,
+            final CharSequence previousWord) {
         reset();
-        final int length = word.length();
-        for (int i = 0; i < length; i = Character.offsetByCodePoints(word, i, 1)) {
-            final int codePoint = Character.codePointAt(word, i);
-            addKeyInfo(codePoint, keyboard);
+        final int length = codePoints.length;
+        for (int i = 0; i < length; ++i) {
+            add(codePoints[i], CoordinateUtils.xFromArray(coordinates, i),
+                    CoordinateUtils.yFromArray(coordinates, i));
         }
         mIsResumed = true;
         mPreviousWordForSuggestion = null == previousWord ? null : previousWord.toString();
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 20be814..d0d3592 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -28,7 +28,6 @@
 
 import com.android.inputmethod.compat.SuggestionSpanUtils;
 import com.android.inputmethod.event.EventInterpreter;
-import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.Dictionary;
@@ -230,20 +229,17 @@
             LatinImeLogger.logOnDelete(x, y);
             break;
         case Constants.CODE_SHIFT:
-            // Note: Calling back to the keyboard on Shift key is handled in
-            // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
-            final Keyboard currentKeyboard = keyboardSwitcher.getKeyboard();
-            if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {
-                // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
-                // alphabetic shift and shift while in symbol layout.
-                performRecapitalization(settingsValues);
-                keyboardSwitcher.updateShiftState();
-            }
+            performRecapitalization(settingsValues);
+            keyboardSwitcher.updateShiftState();
             break;
         case Constants.CODE_CAPSLOCK:
             // Note: Changing keyboard to shift lock state is handled in
             // {@link KeyboardSwitcher#onCodeInput(int)}.
             break;
+        case Constants.CODE_SYMBOL_SHIFT:
+            // Note: Calling back to the keyboard on the symbol Shift key is handled in
+            // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
+            break;
         case Constants.CODE_SWITCH_ALPHA_SYMBOL:
             // Note: Calling back to the keyboard on symbol key is handled in
             // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}.
@@ -300,7 +296,6 @@
                     code, x, y, spaceState, keyboardSwitcher, handler);
             break;
         }
-        keyboardSwitcher.onCodeInput(code);
         // Reset after any single keystroke, except shift, capslock, and symbol-shift
         if (!didAutoCorrect && code != Constants.CODE_SHIFT
                 && code != Constants.CODE_CAPSLOCK
@@ -708,7 +703,7 @@
                 if (settingsValues.mIsInternal) {
                     LatinImeLoggerUtils.onAutoCorrectionCancellation();
                 }
-                revertCommit(settingsValues, keyboardSwitcher, handler);
+                revertCommit(settingsValues, handler);
                 return;
             }
             if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {
@@ -804,7 +799,7 @@
                     && !mConnection.isCursorFollowedByWordCharacter(
                             settingsValues.mSpacingAndPunctuations)) {
                 restartSuggestionsOnWordTouchedByCursor(settingsValues,
-                        true /* includeResumedWordInSuggestions */, keyboardSwitcher);
+                        true /* includeResumedWordInSuggestions */);
             }
             // We just removed at least one character. We need to update the auto-caps state.
             keyboardSwitcher.updateShiftState();
@@ -1042,9 +1037,7 @@
      */
     // TODO: make this private.
     public void restartSuggestionsOnWordTouchedByCursor(final SettingsValues settingsValues,
-            final boolean includeResumedWordInSuggestions,
-            // TODO: Remove this argument.
-            final KeyboardSwitcher keyboardSwitcher) {
+            final boolean includeResumedWordInSuggestions) {
         // HACK: We may want to special-case some apps that exhibit bad behavior in case of
         // recorrection. This is a temporary, stopgap measure that will be removed later.
         // TODO: remove this.
@@ -1091,13 +1084,14 @@
                 }
             }
         }
-        mWordComposer.setComposingWord(typedWord,
+        final int[] codePoints = StringUtils.toCodePointArray(typedWord);
+        mWordComposer.setComposingWord(codePoints,
+                mLatinIME.getCoordinatesForCurrentKeyboard(codePoints),
                 getNthPreviousWordForSuggestion(settingsValues.mSpacingAndPunctuations,
                         // We want the previous word for suggestion. If we have chars in the word
                         // before the cursor, then we want the word before that, hence 2; otherwise,
                         // we want the word immediately before the cursor, hence 1.
-                        0 == numberOfCharsInWordBeforeCursor ? 1 : 2),
-                keyboardSwitcher.getKeyboard());
+                        0 == numberOfCharsInWordBeforeCursor ? 1 : 2));
         mWordComposer.setCursorPositionWithinWord(
                 typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor));
         mConnection.setComposingRegion(expectedCursorPosition - numberOfCharsInWordBeforeCursor,
@@ -1152,8 +1146,8 @@
      * @param settingsValues the current settings values.
      */
     private void revertCommit(final SettingsValues settingsValues,
-            // TODO: remove these arguments
-            final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) {
+            // TODO: remove this argument
+            final LatinIME.UIHandler handler) {
         final String previousWord = mLastComposedWord.mPrevWord;
         final CharSequence originallyTypedWord = mLastComposedWord.mTypedWord;
         final CharSequence committedWord = mLastComposedWord.mCommittedWord;
@@ -1181,8 +1175,8 @@
                         previousWord, committedWord.toString());
             }
         }
-        final SpannableString textToCommit =
-                new SpannableString(originallyTypedWord + mLastComposedWord.mSeparatorString);
+        final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString;
+        final SpannableString textToCommit = new SpannableString(stringToCommit);
         if (committedWord instanceof SpannableString) {
             final int lastCharIndex = textToCommit.length() - 1;
             // Add the auto-correction to the list of suggestions.
@@ -1214,8 +1208,9 @@
         } else {
             // For languages without spaces, we revert the typed string but the cursor is flush
             // with the typed word, so we need to resume suggestions right away.
-            mWordComposer.setComposingWord(textToCommit, previousWord,
-                    keyboardSwitcher.getKeyboard());
+            final int[] codePoints = StringUtils.toCodePointArray(stringToCommit);
+            mWordComposer.setComposingWord(codePoints,
+                    mLatinIME.getCoordinatesForCurrentKeyboard(codePoints), previousWord);
             mConnection.setComposingText(textToCommit, 1);
         }
         if (settingsValues.mIsInternal) {
@@ -1699,26 +1694,27 @@
      * @param settingsValues the current values of the settings.
      * @param tryResumeSuggestions Whether we should resume suggestions or not.
      * @param remainingTries How many times we may try again before giving up.
+     * @return whether true if the caches were successfully reset, false otherwise.
      */
     // TODO: make this private
-    public void retryResetCaches(final SettingsValues settingsValues,
+    public boolean retryResetCachesAndReturnSuccess(final SettingsValues settingsValues,
             final boolean tryResumeSuggestions, final int remainingTries,
             // TODO: remove these arguments
-            final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) {
+            final LatinIME.UIHandler handler) {
         if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(
                 mConnection.getExpectedSelectionStart(), mConnection.getExpectedSelectionEnd(),
                 false)) {
             if (0 < remainingTries) {
                 handler.postResetCaches(tryResumeSuggestions, remainingTries - 1);
-                return;
+                return false;
             }
-            // If remainingTries is 0, we should stop waiting for new tries, but it's still
-            // better to load the keyboard (less things will be broken).
+            // If remainingTries is 0, we should stop waiting for new tries, however we'll still
+            // return true as we need to perform other tasks (for example, loading the keyboard).
         }
         mConnection.tryFixLyingCursorPosition();
-        keyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), settingsValues);
         if (tryResumeSuggestions) {
             handler.postResumeSuggestions();
         }
+        return true;
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index d6e5b75..826e36d 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -30,6 +30,7 @@
 import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.WordComposer;
 import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService.SuggestionsGatherer;
@@ -312,11 +313,10 @@
                             false /* reportAsTypo */);
                 }
                 final WordComposer composer = new WordComposer();
-                final int length = text.length();
-                for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) {
-                    final int codePoint = text.codePointAt(i);
-                    composer.addKeyInfo(codePoint, dictInfo.getKeyboard(codePoint));
-                }
+                final int[] codePoints = StringUtils.toCodePointArray(text);
+                composer.setComposingWord(codePoints,
+                        LatinIME.getCoordinatesForKeyboard(codePoints, dictInfo.mKeyboard),
+                        null /* previousWord */);
                 // TODO: make a spell checker option to block offensive words or not
                 final ArrayList<SuggestedWordInfo> suggestions =
                         dictInfo.mDictionary.getSuggestions(composer, prevWord,
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java b/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java
index b77f3e2..1ffe506 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java
@@ -27,7 +27,7 @@
  */
 public final class DictAndKeyboard {
     public final Dictionary mDictionary;
-    private final Keyboard mKeyboard;
+    public final Keyboard mKeyboard;
     private final Keyboard mManualShiftedKeyboard;
 
     public DictAndKeyboard(
@@ -43,13 +43,6 @@
                 keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED);
     }
 
-    public Keyboard getKeyboard(final int codePoint) {
-        if (mKeyboard == null) {
-            return null;
-        }
-        return mKeyboard.getKey(codePoint) != null ? mKeyboard : mManualShiftedKeyboard;
-    }
-
     public ProximityInfo getProximityInfo() {
         return mKeyboard == null ? null : mKeyboard.getProximityInfo();
     }
diff --git a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
index 72f2cd2..91a6350 100644
--- a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
@@ -16,17 +16,19 @@
 
 package com.android.inputmethod.latin.utils;
 
+import java.util.Arrays;
+
 public final class CoordinateUtils {
     private static final int INDEX_X = 0;
     private static final int INDEX_Y = 1;
-    private static final int ARRAY_SIZE = INDEX_Y + 1;
+    private static final int ELEMENT_SIZE = INDEX_Y + 1;
 
     private CoordinateUtils() {
         // This utility class is not publicly instantiable.
     }
 
     public static int[] newInstance() {
-        return new int[ARRAY_SIZE];
+        return new int[ELEMENT_SIZE];
     }
 
     public static int x(final int[] coords) {
@@ -46,4 +48,39 @@
         destination[INDEX_X] = source[INDEX_X];
         destination[INDEX_Y] = source[INDEX_Y];
     }
+
+    public static int[] newCoordinateArray(final int arraySize) {
+        return new int[ELEMENT_SIZE * arraySize];
+    }
+
+    public static int xFromArray(final int[] coordsArray, final int index) {
+        return coordsArray[ELEMENT_SIZE * index + INDEX_X];
+    }
+
+    public static int yFromArray(final int[] coordsArray, final int index) {
+        return coordsArray[ELEMENT_SIZE * index + INDEX_Y];
+    }
+
+    public static int[] coordinateFromArray(final int[] coordsArray, final int index) {
+        final int baseIndex = ELEMENT_SIZE * index;
+        return Arrays.copyOfRange(coordsArray, baseIndex, baseIndex + ELEMENT_SIZE);
+    }
+
+    public static void setXYInArray(final int[] coordsArray, final int index,
+            final int x, final int y) {
+        final int baseIndex = ELEMENT_SIZE * index;
+        coordsArray[baseIndex + INDEX_X] = x;
+        coordsArray[baseIndex + INDEX_Y] = y;
+    }
+
+    public static void setCoordinateInArray(final int[] coordsArray, final int index,
+            final int[] coords) {
+        final int baseIndex = ELEMENT_SIZE * index;
+        coordsArray[baseIndex + INDEX_X] = coords[INDEX_X];
+        coordsArray[baseIndex + INDEX_Y] = coords[INDEX_Y];
+    }
+
+    public static void copyArray(final int[] destination, final int[] source) {
+        System.arraycopy(source, 0, destination, 0, source.length);
+    }
 }
diff --git a/tests/src/com/android/inputmethod/latin/WordComposerTests.java b/tests/src/com/android/inputmethod/latin/WordComposerTests.java
index 1336c6d..2fe331b 100644
--- a/tests/src/com/android/inputmethod/latin/WordComposerTests.java
+++ b/tests/src/com/android/inputmethod/latin/WordComposerTests.java
@@ -19,6 +19,8 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.inputmethod.latin.utils.StringUtils;
+
 /**
  * Unit tests for WordComposer.
  */
@@ -33,8 +35,11 @@
         // in UTF-16, whereas those outside the BMP need 4 bytes.
         // http://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane
         final String STR_WITHIN_BMP = "abcdef";
+        final int[] CODEPOINTS_WITHIN_BMP = StringUtils.toCodePointArray(STR_WITHIN_BMP);
+        final int[] COORDINATES_WITHIN_BMP =
+                LatinIME.getCoordinatesForKeyboard(CODEPOINTS_WITHIN_BMP, null);
         final String PREVWORD = "prevword";
-        wc.setComposingWord(STR_WITHIN_BMP, PREVWORD, null /* keyboard */);
+        wc.setComposingWord(CODEPOINTS_WITHIN_BMP, COORDINATES_WITHIN_BMP, PREVWORD);
         assertEquals(wc.size(),
                 STR_WITHIN_BMP.codePointCount(0, STR_WITHIN_BMP.length()));
         assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
@@ -62,10 +67,13 @@
 
         // \uD861\uDED7 is 𨛗, a character outside the BMP
         final String STR_WITH_SUPPLEMENTARY_CHAR = "abcde\uD861\uDED7fgh";
-        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null /* previousWord */,
-                null /* keyboard */);
-        assertEquals(wc.size(), STR_WITH_SUPPLEMENTARY_CHAR.codePointCount(0,
-                        STR_WITH_SUPPLEMENTARY_CHAR.length()));
+        final int[] CODEPOINTS_WITH_SUPPLEMENTARY_CHAR =
+                StringUtils.toCodePointArray(STR_WITH_SUPPLEMENTARY_CHAR);
+        final int[] COORDINATES_WITH_SUPPLEMENTARY_CHAR = LatinIME.getCoordinatesForKeyboard(
+                CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, null);
+        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
+                null /* previousWord */);
+        assertEquals(wc.size(), CODEPOINTS_WITH_SUPPLEMENTARY_CHAR.length);
         assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
         wc.setCursorPositionWithinWord(3);
         assertTrue(wc.isCursorFrontOrMiddleOfComposingWord());
@@ -75,44 +83,46 @@
         assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
         assertNull(wc.getPreviousWordForSuggestion());
 
-        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, STR_WITHIN_BMP, null /* keyboard */);
+        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
+                STR_WITHIN_BMP);
         wc.setCursorPositionWithinWord(3);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(7));
         assertEquals(STR_WITHIN_BMP, wc.getPreviousWordForSuggestion());
 
-        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, STR_WITH_SUPPLEMENTARY_CHAR,
-                null /* keyboard */);
+        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
+                STR_WITH_SUPPLEMENTARY_CHAR);
         wc.setCursorPositionWithinWord(3);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(7));
         assertEquals(STR_WITH_SUPPLEMENTARY_CHAR, wc.getPreviousWordForSuggestion());
 
-        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, STR_WITHIN_BMP, null /* keyboard */);
+        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
+                STR_WITHIN_BMP);
         wc.setCursorPositionWithinWord(3);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(-3));
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-1));
         assertEquals(STR_WITHIN_BMP, wc.getPreviousWordForSuggestion());
 
-        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null /* previousWord */,
-                null /* keyboard */);
+        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
+                null /* previousWord */);
         wc.setCursorPositionWithinWord(3);
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-9));
         assertNull(wc.getPreviousWordForSuggestion());
 
-        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, STR_WITH_SUPPLEMENTARY_CHAR,
-                null /* keyboard */);
+        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
+                STR_WITH_SUPPLEMENTARY_CHAR);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(-10));
         assertEquals(STR_WITH_SUPPLEMENTARY_CHAR, wc.getPreviousWordForSuggestion());
 
-        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null /* previousWord */,
-                null /* keyboard */);
+        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
+                null /* previousWord */);
         assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-11));
 
-        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null /* previousWord */,
-                null /* keyboard */);
+        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
+                null /* previousWord */);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(0));
 
-        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null /* previousWord */,
-                null /* keyboard */);
+        wc.setComposingWord(CODEPOINTS_WITH_SUPPLEMENTARY_CHAR, COORDINATES_WITH_SUPPLEMENTARY_CHAR,
+                null /* previousWord */);
         wc.setCursorPositionWithinWord(2);
         assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(0));
     }
diff --git a/tools/dicttool/Android.mk b/tools/dicttool/Android.mk
index 95afb4c..b7b1fe6 100644
--- a/tools/dicttool/Android.mk
+++ b/tools/dicttool/Android.mk
@@ -43,6 +43,7 @@
         $(LATINIME_CORE_SOURCE_DIRECTORY)/settings/NativeSuggestOptions.java \
         $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/ByteArrayDictBuffer.java \
         $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/CollectionUtils.java \
+        $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/CoordinateUtils.java \
         $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/FileUtils.java \
         $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/JniUtils.java \
         $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/LocaleUtils.java \