Merge "Import translations. DO NOT MERGE"
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 478a5c0..80bf704 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -161,7 +161,7 @@
 
     <declare-styleable name="SuggestionStripView">
         <attr name="suggestionStripOption" format="integer">
-            <!-- This should be aligned with SuggestionStripViewParams.AUTO_CORRECT_* and etc. -->
+            <!-- This should be aligned with SuggestionStripLayoutHelper.AUTO_CORRECT_* and etc. -->
             <flag name="autoCorrectBold" value="0x01" />
             <flag name="autoCorrectUnderline" value="0x02" />
             <flag name="validTypedWordBold" value="0x04" />
diff --git a/java/res/values/keypress-vibration-durations.xml b/java/res/values/keypress-vibration-durations.xml
index ad6bead..0474b1c 100644
--- a/java/res/values/keypress-vibration-durations.xml
+++ b/java/res/values/keypress-vibration-durations.xml
@@ -39,10 +39,19 @@
         <item>MODEL=(SAMSUNG-)?SCH-(J021|R530|I535|I939):MANUFACTURER=samsung,8</item>
         <item>MODEL=(SAMSUNG-)?(SCL21|SC-06D|SC-03E):MANUFACTURER=samsung,8</item>
         <item>MODEL=(SAMSUNG-)?(SHV-210[KLS]?|SPH-L710):MANUFACTURER=samsung,8</item>
+        <!-- Samsung Galaxy S4 -->
+        <item>MODEL=(SAMSUNG-)?GT-I(950[0258][G]?):MANUFACTURER=samsung,7</item>
+        <item>MODEL=(SAMSUNG-)?SGH-(I337|M919|N045):MANUFACTURER=samsung,7</item>
+        <item>MODEL=(SAMSUNG-)?SCH-(I545|I959|R970):MANUFACTURER=samsung,7</item>
+        <item>MODEL=(SAMSUNG-)?SPH-(L720):MANUFACTURER=samsung,7</item>
+        <item>MODEL=(SAMSUNG-)?(SC-04E):MANUFACTURER=samsung,7</item>
+        <item>MODEL=(SAMSUNG-)?(SHV-E300[KLS]?):MANUFACTURER=samsung,7</item>
         <!-- LG Optimus G -->
         <item>MODEL=LG-E97[013]|LS970|L-01E:MANUFACTURER=LGE,15</item>
         <!-- HTC One X -->
         <item>MODEL=HTC One X:MANUFACTURER=HTC,20</item>
+        <!-- HTC One -->
+        <item>MODEL=HTC One:MANUFACTURER=HTC,15</item>
         <!-- Motorola Razor M -->
         <item>MODEL=XT907:MANUFACTURER=motorola,30</item>
         <!-- Sony Xperia Z -->
diff --git a/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java b/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
index 5ab94a4..c5aca17 100644
--- a/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
+++ b/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
@@ -57,6 +57,11 @@
         super(context, attrs, defStyle);
     }
 
+    public void reset() {
+        mStatus = NOT_INITIALIZED;
+        mAnimateToStatus = NOT_INITIALIZED;
+    }
+
     @Override
     protected void onLayout(final boolean changed, final int left, final int top, final int right,
             final int bottom) {
@@ -64,9 +69,7 @@
         mInstallButton = (Button)findViewById(R.id.dict_install_button);
         mCancelButton = (Button)findViewById(R.id.dict_cancel_button);
         mDeleteButton = (Button)findViewById(R.id.dict_delete_button);
-        mInstallButton.setOnClickListener(mOnClickListener);
-        mCancelButton.setOnClickListener(mOnClickListener);
-        mDeleteButton.setOnClickListener(mOnClickListener);
+        setInternalOnClickListener(mOnClickListener);
         setButtonPositionWithoutAnimation(mStatus);
         if (mAnimateToStatus != NOT_INITIALIZED) {
             // We have been asked to animate before we were ready, so we took a note of it.
@@ -139,6 +142,12 @@
 
     public void setInternalOnClickListener(final OnClickListener listener) {
         mOnClickListener = listener;
+        if (null != mInstallButton) {
+            // Already laid out : do it now
+            mInstallButton.setOnClickListener(mOnClickListener);
+            mCancelButton.setOnClickListener(mOnClickListener);
+            mDeleteButton.setOnClickListener(mOnClickListener);
+        }
     }
 
     private ViewPropertyAnimator animateButton(final View button, final int direction) {
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
index de3711c..5ad5900 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
@@ -16,8 +16,11 @@
 
 package com.android.inputmethod.dictionarypack;
 
+import android.view.View;
+
 import com.android.inputmethod.latin.CollectionUtils;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 
 /**
@@ -37,6 +40,7 @@
     }
 
     private HashMap<String, State> mWordlistToState = CollectionUtils.newHashMap();
+    private ArrayList<View> mViewCache = CollectionUtils.newArrayList();
 
     public boolean isOpen(final String wordlistId) {
         final State state = mWordlistToState.get(wordlistId);
@@ -64,4 +68,16 @@
             state.mOpen = false;
         }
     }
+
+    public View findFirstOrphanedView() {
+        for (final View v : mViewCache) {
+            if (null == v.getParent()) return v;
+        }
+        return null;
+    }
+
+    public View addToCacheAndReturnView(final View view) {
+        mViewCache.add(view);
+        return view;
+    }
 }
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
index 6183223..4b89d20 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
@@ -222,7 +222,9 @@
                     refreshNetworkState();
 
                     removeAnyDictSettings(prefScreen);
+                    int i = 0;
                     for (Preference preference : prefList) {
+                        preference.setOrder(i++);
                         prefScreen.addPreference(preference);
                     }
                 }
@@ -302,7 +304,7 @@
                 // the description.
                 final String key = matchLevelString + "." + description + "." + wordlistId;
                 final WordListPreference existingPref = prefMap.get(key);
-                if (null == existingPref || hasPriority(status, existingPref.mStatus)) {
+                if (null == existingPref || existingPref.hasPriorityOver(status)) {
                     final WordListPreference oldPreference = mCurrentPreferenceMap.get(key);
                     final WordListPreference pref;
                     if (null != oldPreference
@@ -313,7 +315,7 @@
                         // need to be the same, others have been tested through the key of the
                         // map. Also, status may differ so we don't want to use #equals() here.
                         pref = oldPreference;
-                        pref.mStatus = status;
+                        pref.setStatus(status);
                     } else {
                         // Otherwise, discard it and create a new one instead.
                         pref = new WordListPreference(activity, mDictionaryListInterfaceState,
@@ -329,18 +331,6 @@
         }
     }
 
-    /**
-     * Finds out if a given status has priority over another for display order.
-     *
-     * @param newStatus
-     * @param oldStatus
-     * @return whether newStatus has priority over oldStatus.
-     */
-    private static boolean hasPriority(final int newStatus, final int oldStatus) {
-        // Both of these should be one of MetadataDbHelper.STATUS_*
-        return newStatus > oldStatus;
-    }
-
     @Override
     public boolean onOptionsItemSelected(final MenuItem item) {
         switch (item.getItemId()) {
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
index 03ed267..1511dbc 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
@@ -572,7 +572,8 @@
      * If several clients use the same metadata URL, we know to only download it once, and
      * dispatch the update process across all relevant clients when the download ends. This means
      * several clients may share a single download ID if they share a metadata URI.
-     * The dispatching is done in {@link UpdateHandler#downloadFinished(Context, Intent)}, which
+     * The dispatching is done in
+     * {@link UpdateHandler#downloadFinished(Context, android.content.Intent)}, which
      * finds out about the list of relevant clients by calling this method.
      *
      * @param context a context instance to open the databases
@@ -863,17 +864,20 @@
                                 r.getAsString(WORDLISTID_COLUMN),
                                 Integer.toString(STATUS_INSTALLED) },
                         null, null, null);
-                if (c.moveToFirst()) {
-                    // There should never be more than one file, but if there are, it's a bug
-                    // and we should remove them all. I think it might happen if the power of the
-                    // phone is suddenly cut during an update.
-                    final int filenameIndex = c.getColumnIndex(LOCAL_FILENAME_COLUMN);
-                    do {
-                        Utils.l("Setting for removal", c.getString(filenameIndex));
-                        filenames.add(c.getString(filenameIndex));
-                    } while (c.moveToNext());
+                try {
+                    if (c.moveToFirst()) {
+                        // There should never be more than one file, but if there are, it's a bug
+                        // and we should remove them all. I think it might happen if the power of
+                        // the phone is suddenly cut during an update.
+                        final int filenameIndex = c.getColumnIndex(LOCAL_FILENAME_COLUMN);
+                        do {
+                            Utils.l("Setting for removal", c.getString(filenameIndex));
+                            filenames.add(c.getString(filenameIndex));
+                        } while (c.moveToNext());
+                    }
+                } finally {
+                    c.close();
                 }
-
                 r.put(STATUS_COLUMN, STATUS_INSTALLED);
                 db.beginTransactionNonExclusive();
                 // Delete all old entries. There should never be any stalled entries, but if
diff --git a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
index 451a0fb..7ec7e9c 100644
--- a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
+++ b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
@@ -61,7 +61,7 @@
     public final Locale mLocale;
     public final String mDescription;
     // The status
-    public int mStatus;
+    private int mStatus;
     // The size of the dictionary file
     private final int mFilesize;
 
@@ -92,12 +92,25 @@
         setKey(wordlistId);
     }
 
-    private void setStatus(final int status) {
+    public void setStatus(final int status) {
         if (status == mStatus) return;
         mStatus = status;
         setSummary(getSummary(status));
     }
 
+    @Override
+    public View onCreateView(final ViewGroup parent) {
+        final View orphanedView = mInterfaceState.findFirstOrphanedView();
+        if (null != orphanedView) return orphanedView; // Will be sent to onBindView
+        final View newView = super.onCreateView(parent);
+        return mInterfaceState.addToCacheAndReturnView(newView);
+    }
+
+    public boolean hasPriorityOver(final int otherPrefStatus) {
+        // Both of these should be one of MetadataDbHelper.STATUS_*
+        return mStatus > otherPrefStatus;
+    }
+
     private String getSummary(final int status) {
         switch (status) {
             // If we are deleting the word list, for the user it's like it's already deleted.
@@ -209,6 +222,9 @@
 
         final ButtonSwitcher buttonSwitcher =
                 (ButtonSwitcher)view.findViewById(R.id.wordlist_button_switcher);
+        // We need to clear the state of the button switcher, because we reuse views; if we didn't
+        // reset it would animate from whatever its old state was.
+        buttonSwitcher.reset();
         if (mInterfaceState.isOpen(mWordlistId)) {
             // The button is open.
             final int previousStatus = mInterfaceState.getStatus(mWordlistId);
diff --git a/java/src/com/android/inputmethod/latin/AssetFileAddress.java b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
index 47c750f..8751925 100644
--- a/java/src/com/android/inputmethod/latin/AssetFileAddress.java
+++ b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
@@ -24,7 +24,7 @@
  * the package file. Open it correctly thus requires the name of the package it is in, but
  * also the offset in the file and the length of this data. This class encapsulates these three.
  */
-final class AssetFileAddress {
+public final class AssetFileAddress {
     public final String mFilename;
     public final long mOffset;
     public final long mLength;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index a9b58de..c038db8 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -32,6 +32,7 @@
 
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
+import java.io.Closeable;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
@@ -319,20 +320,12 @@
                 // Try the next method.
             } finally {
                 // Ignore exceptions while closing files.
-                try {
-                    if (null != afd) afd.close();
-                    if (null != inputStream) inputStream.close();
-                    if (null != uncompressedStream) uncompressedStream.close();
-                    if (null != decryptedStream) decryptedStream.close();
-                    if (null != bufferedInputStream) bufferedInputStream.close();
-                } catch (Exception e) {
-                    Log.e(TAG, "Exception while closing a file descriptor", e);
-                }
-                try {
-                    if (null != bufferedOutputStream) bufferedOutputStream.close();
-                } catch (Exception e) {
-                    Log.e(TAG, "Exception while closing a file", e);
-                }
+                closeAssetFileDescriptorAndReportAnyException(afd);
+                closeCloseableAndReportAnyException(inputStream);
+                closeCloseableAndReportAnyException(uncompressedStream);
+                closeCloseableAndReportAnyException(decryptedStream);
+                closeCloseableAndReportAnyException(bufferedInputStream);
+                closeCloseableAndReportAnyException(bufferedOutputStream);
             }
         }
 
@@ -352,6 +345,26 @@
         }
     }
 
+    // Ideally the two following methods should be merged, but AssetFileDescriptor does not
+    // implement Closeable although it does implement #close(), and Java does not have
+    // structural typing.
+    private static void closeAssetFileDescriptorAndReportAnyException(
+            final AssetFileDescriptor file) {
+        try {
+            if (null != file) file.close();
+        } catch (Exception e) {
+            Log.e(TAG, "Exception while closing a file", e);
+        }
+    }
+
+    private static void closeCloseableAndReportAnyException(final Closeable file) {
+        try {
+            if (null != file) file.close();
+        } catch (Exception e) {
+            Log.e(TAG, "Exception while closing a file", e);
+        }
+    }
+
     /**
      * Queries a content provider for word list data for some locale and cache the returned files
      *
@@ -363,8 +376,14 @@
      */
     public static void cacheWordListsFromContentProvider(final Locale locale,
             final Context context, final boolean hasDefaultWordList) {
-        final ContentProviderClient providerClient = context.getContentResolver().
+        final ContentProviderClient providerClient;
+        try {
+            providerClient = context.getContentResolver().
                 acquireContentProviderClient(getProviderUriBuilder("").build());
+        } catch (final SecurityException e) {
+            Log.e(TAG, "No permission to communicate with the dictionary provider", e);
+            return;
+        }
         if (null == providerClient) {
             Log.e(TAG, "Can't establish communication with the dictionary provider");
             return;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 40e5167..4514ec2 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -21,6 +21,8 @@
 import android.content.res.Resources;
 import android.util.Log;
 
+import com.android.inputmethod.annotations.UsedForTesting;
+
 import java.io.File;
 import java.util.ArrayList;
 import java.util.LinkedList;
@@ -126,21 +128,22 @@
 
     /**
      * Create a dictionary from passed data. This is intended for unit tests only.
-     * @param dictionary the file to read
-     * @param startOffset the offset in the file where the data starts
-     * @param length the length of the data
+     * @param dictionaryList the list of files to read, with their offsets and lengths
      * @param useFullEditDistance whether to use the full edit distance in suggestions
      * @return the created dictionary, or null.
      */
-    public static Dictionary createDictionaryForTest(File dictionary, long startOffset, long length,
+    @UsedForTesting
+    public static Dictionary createDictionaryForTest(final AssetFileAddress[] dictionaryList,
             final boolean useFullEditDistance, Locale locale) {
-        if (dictionary.isFile()) {
-            return new BinaryDictionary(dictionary.getAbsolutePath(), startOffset, length,
-                    useFullEditDistance, locale, Dictionary.TYPE_MAIN);
-        } else {
-            Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath());
-            return null;
+        final DictionaryCollection dictionaryCollection =
+                new DictionaryCollection(Dictionary.TYPE_MAIN);
+        for (final AssetFileAddress address : dictionaryList) {
+            final BinaryDictionary binaryDictionary = new BinaryDictionary(address.mFilename,
+                    address.mOffset, address.mLength, useFullEditDistance, locale,
+                    Dictionary.TYPE_MAIN);
+            dictionaryCollection.addDictionary(binaryDictionary);
         }
+        return dictionaryCollection;
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index cebc93c..c9a42a3 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -855,6 +855,7 @@
         }
         // Remove pending messages related to update suggestions
         mHandler.cancelUpdateSuggestionStrip();
+        if (mWordComposer.isComposingWord()) mConnection.finishComposingText();
         resetComposingState(true /* alsoResetLastComposedWord */);
         // Notify ResearchLogger
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 5d580f2..e783e6d 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -23,7 +23,6 @@
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
-import java.io.File;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.HashSet;
@@ -77,9 +76,9 @@
     }
 
     @UsedForTesting
-    Suggest(final File dictionary, final long startOffset, final long length, final Locale locale) {
-        final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(dictionary,
-                startOffset, length /* useFullEditDistance */, false, locale);
+    Suggest(final AssetFileAddress[] dictionaryList, final Locale locale) {
+        final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(dictionaryList,
+                false /* useFullEditDistance */, locale);
         mLocale = locale;
         mMainDictionary = mainDict;
         addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, mainDict);
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 1f45327..7a16595 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -24,6 +24,9 @@
 import java.util.HashSet;
 
 public final class SuggestedWords {
+    public static final int INDEX_OF_TYPED_WORD = 0;
+    public static final int INDEX_OF_AUTO_CORRECTION = 1;
+
     private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST =
             CollectionUtils.newArrayList(0);
     public static final SuggestedWords EMPTY = new SuggestedWords(
@@ -61,12 +64,12 @@
         return mSuggestedWordInfoList.size();
     }
 
-    public String getWord(int pos) {
-        return mSuggestedWordInfoList.get(pos).mWord;
+    public String getWord(final int index) {
+        return mSuggestedWordInfoList.get(index).mWord;
     }
 
-    public SuggestedWordInfo getInfo(int pos) {
-        return mSuggestedWordInfoList.get(pos);
+    public SuggestedWordInfo getInfo(final int index) {
+        return mSuggestedWordInfoList.get(index);
     }
 
     public boolean willAutoCorrect() {
@@ -108,8 +111,8 @@
                 SuggestedWordInfo.KIND_TYPED, Dictionary.TYPE_USER_TYPED));
         alreadySeen.add(typedWord.toString());
         final int previousSize = previousSuggestions.size();
-        for (int pos = 1; pos < previousSize; pos++) {
-            final SuggestedWordInfo prevWordInfo = previousSuggestions.getInfo(pos);
+        for (int index = 1; index < previousSize; index++) {
+            final SuggestedWordInfo prevWordInfo = previousSuggestions.getInfo(index);
             final String prevWord = prevWordInfo.mWord;
             // Filter out duplicate suggestion.
             if (!alreadySeen.contains(prevWord)) {
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index 0f96c54..949720f 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -64,7 +64,8 @@
      *        task should be interrupted; otherwise, in-progress tasks are allowed
      *        to complete.
      */
-    public static void cancelTask(AsyncTask<?, ?, ?> task, boolean mayInterruptIfRunning) {
+    public static void cancelTask(final AsyncTask<?, ?, ?> task,
+            final boolean mayInterruptIfRunning) {
         if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) {
             task.cancel(mayInterruptIfRunning);
         }
@@ -86,26 +87,34 @@
         private RingCharBuffer() {
             // Intentional empty constructor for singleton.
         }
+
         @UsedForTesting
         public static RingCharBuffer getInstance() {
             return sRingCharBuffer;
         }
-        public static RingCharBuffer init(InputMethodService context, boolean enabled,
-                boolean usabilityStudy) {
-            if (!(enabled || usabilityStudy)) return null;
+
+        public static RingCharBuffer init(final InputMethodService context, final boolean enabled,
+                final boolean usabilityStudy) {
+            if (!(enabled || usabilityStudy)) {
+                return null;
+            }
             sRingCharBuffer.mContext = context;
             sRingCharBuffer.mEnabled = true;
             UsabilityStudyLogUtils.getInstance().init(context);
             return sRingCharBuffer;
         }
-        private static int normalize(int in) {
+
+        private static int normalize(final int in) {
             int ret = in % BUFSIZE;
             return ret < 0 ? ret + BUFSIZE : ret;
         }
+
         // TODO: accept code points
         @UsedForTesting
-        public void push(char c, int x, int y) {
-            if (!mEnabled) return;
+        public void push(final char c, final int x, final int y) {
+            if (!mEnabled) {
+                return;
+            }
             mCharBuf[mEnd] = c;
             mXBuf[mEnd] = x;
             mYBuf[mEnd] = y;
@@ -114,52 +123,54 @@
                 ++mLength;
             }
         }
+
         public char pop() {
             if (mLength < 1) {
                 return PLACEHOLDER_DELIMITER_CHAR;
-            } else {
-                mEnd = normalize(mEnd - 1);
-                --mLength;
-                return mCharBuf[mEnd];
             }
+            mEnd = normalize(mEnd - 1);
+            --mLength;
+            return mCharBuf[mEnd];
         }
-        public char getBackwardNthChar(int n) {
+
+        public char getBackwardNthChar(final int n) {
             if (mLength <= n || n < 0) {
                 return PLACEHOLDER_DELIMITER_CHAR;
-            } else {
-                return mCharBuf[normalize(mEnd - n - 1)];
             }
+            return mCharBuf[normalize(mEnd - n - 1)];
         }
-        public int getPreviousX(char c, int back) {
+
+        public int getPreviousX(final char c, final int back) {
+            final int index = normalize(mEnd - 2 - back);
+            if (mLength <= back
+                    || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) {
+                return INVALID_COORDINATE;
+            }
+            return mXBuf[index];
+        }
+
+        public int getPreviousY(final char c, final int back) {
             int index = normalize(mEnd - 2 - back);
             if (mLength <= back
                     || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) {
                 return INVALID_COORDINATE;
-            } else {
-                return mXBuf[index];
             }
+            return mYBuf[index];
         }
-        public int getPreviousY(char c, int back) {
-            int index = normalize(mEnd - 2 - back);
-            if (mLength <= back
-                    || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) {
-                return INVALID_COORDINATE;
-            } else {
-                return mYBuf[index];
-            }
-        }
-        public String getLastWord(int ignoreCharCount) {
-            StringBuilder sb = new StringBuilder();
+
+        public String getLastWord(final int ignoreCharCount) {
+            final StringBuilder sb = new StringBuilder();
+            final LatinIME latinIme = (LatinIME)mContext;
             int i = ignoreCharCount;
             for (; i < mLength; ++i) {
-                char c = mCharBuf[normalize(mEnd - 1 - i)];
-                if (!((LatinIME)mContext).isWordSeparator(c)) {
+                final char c = mCharBuf[normalize(mEnd - 1 - i)];
+                if (!latinIme.isWordSeparator(c)) {
                     break;
                 }
             }
             for (; i < mLength; ++i) {
                 char c = mCharBuf[normalize(mEnd - 1 - i)];
-                if (!((LatinIME)mContext).isWordSeparator(c)) {
+                if (!latinIme.isWordSeparator(c)) {
                     sb.append(c);
                 } else {
                     break;
@@ -167,6 +178,7 @@
             }
             return sb.reverse().toString();
         }
+
         public void reset() {
             mLength = 0;
         }
@@ -174,11 +186,11 @@
 
     // Get the current stack trace
     public static String getStackTrace(final int limit) {
-        StringBuilder sb = new StringBuilder();
+        final StringBuilder sb = new StringBuilder();
         try {
             throw new RuntimeException();
-        } catch (RuntimeException e) {
-            StackTraceElement[] frames = e.getStackTrace();
+        } catch (final RuntimeException e) {
+            final StackTraceElement[] frames = e.getStackTrace();
             // Start at 1 because the first frame is here and we don't care about it
             for (int j = 1; j < frames.length && j < limit + 1; ++j) {
                 sb.append(frames[j].toString() + "\n");
@@ -222,7 +234,7 @@
             return OnDemandInitializationHolder.sInstance;
         }
 
-        public void init(InputMethodService ims) {
+        public void init(final InputMethodService ims) {
             mIms = ims;
             mDirectory = ims.getFilesDir();
         }
@@ -232,17 +244,17 @@
                     && (mDirectory != null && mDirectory.exists())) {
                 try {
                     mWriter = getPrintWriter(mDirectory, FILENAME, false);
-                } catch (IOException e) {
+                } catch (final IOException e) {
                     Log.e(USABILITY_TAG, "Can't create log file.");
                 }
             }
         }
 
-        public static void writeBackSpace(int x, int y) {
+        public static void writeBackSpace(final int x, final int y) {
             UsabilityStudyLogUtils.getInstance().write("<backspace>\t" + x + "\t" + y);
         }
 
-        public void writeChar(char c, int x, int y) {
+        public static void writeChar(final char c, final int x, final int y) {
             String inputChar = String.valueOf(c);
             switch (c) {
                 case '\n':
@@ -279,15 +291,15 @@
 
         private synchronized String getBufferedLogs() {
             mWriter.flush();
-            StringBuilder sb = new StringBuilder();
-            BufferedReader br = getBufferedReader();
+            final StringBuilder sb = new StringBuilder();
+            final BufferedReader br = getBufferedReader();
             String line;
             try {
                 while ((line = br.readLine()) != null) {
                     sb.append('\n');
                     sb.append(line);
                 }
-            } catch (IOException e) {
+            } catch (final IOException e) {
                 Log.e(USABILITY_TAG, "Can't read log file.");
             } finally {
                 if (LatinImeLogger.sDBG) {
@@ -295,7 +307,7 @@
                 }
                 try {
                     br.close();
-                } catch (IOException e) {
+                } catch (final IOException e) {
                     // ignore.
                 }
             }
@@ -334,10 +346,10 @@
                         srcStream.close();
                         dest.close();
                         destStream.close();
-                    } catch (FileNotFoundException e1) {
+                    } catch (final FileNotFoundException e1) {
                         Log.w(USABILITY_TAG, e1);
                         return;
-                    } catch (IOException e2) {
+                    } catch (final IOException e2) {
                         Log.w(USABILITY_TAG, e2);
                         return;
                     }
@@ -387,13 +399,13 @@
             createLogFileIfNotExist();
             try {
                 return new BufferedReader(new FileReader(mFile));
-            } catch (FileNotFoundException e) {
+            } catch (final FileNotFoundException e) {
                 return null;
             }
         }
 
-        private PrintWriter getPrintWriter(
-                File dir, String filename, boolean renew) throws IOException {
+        private PrintWriter getPrintWriter(final File dir, final String filename,
+                final boolean renew) throws IOException {
             mFile = new File(dir, filename);
             if (mFile.exists()) {
                 if (renew) {
@@ -405,8 +417,7 @@
     }
 
     public static final class Stats {
-        public static void onNonSeparator(final char code, final int x,
-                final int y) {
+        public static void onNonSeparator(final char code, final int x, final int y) {
             RingCharBuffer.getInstance().push(code, x, y);
             LatinImeLogger.logOnInputChar();
         }
@@ -430,7 +441,9 @@
         public static void onAutoCorrection(final String typedWord, final String correctedWord,
                 final String separatorString, final WordComposer wordComposer) {
             final boolean isBatchMode = wordComposer.isBatchMode();
-            if (!isBatchMode && TextUtils.isEmpty(typedWord)) return;
+            if (!isBatchMode && TextUtils.isEmpty(typedWord)) {
+                return;
+            }
             // TODO: this fails when the separator is more than 1 code point long, but
             // the backend can't handle it yet. The only case when this happens is with
             // smileys and other multi-character keys.
@@ -454,36 +467,43 @@
     }
 
     public static String getDebugInfo(final SuggestedWords suggestions, final int pos) {
-        if (!LatinImeLogger.sDBG) return null;
+        if (!LatinImeLogger.sDBG) {
+            return null;
+        }
         final SuggestedWordInfo wordInfo = suggestions.getInfo(pos);
-        if (wordInfo == null) return null;
+        if (wordInfo == null) {
+            return null;
+        }
         final String info = wordInfo.getDebugString();
-        if (TextUtils.isEmpty(info)) return null;
+        if (TextUtils.isEmpty(info)) {
+            return null;
+        }
         return info;
     }
 
-    public static int getAcitivityTitleResId(Context context, Class<? extends Activity> cls) {
+    public static int getAcitivityTitleResId(final Context context,
+            final Class<? extends Activity> cls) {
         final ComponentName cn = new ComponentName(context, cls);
         try {
             final ActivityInfo ai = context.getPackageManager().getActivityInfo(cn, 0);
             if (ai != null) {
                 return ai.labelRes;
             }
-        } catch (NameNotFoundException e) {
+        } catch (final NameNotFoundException e) {
             Log.e(TAG, "Failed to get settings activity title res id.", e);
         }
         return 0;
     }
 
-    public static String getVersionName(Context context) {
+    public static String getVersionName(final Context context) {
         try {
             if (context == null) {
                 return "";
             }
             final String packageName = context.getPackageName();
-            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
+            final PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
             return info.versionName;
-        } catch (NameNotFoundException e) {
+        } catch (final NameNotFoundException e) {
             Log.e(TAG, "Could not find version info.", e);
         }
         return "";
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 09f81d4..322ae5b 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -61,7 +61,7 @@
             super();
         }
 
-        public int layout(final SuggestedWords suggestedWords, final int fromPos,
+        public int layout(final SuggestedWords suggestedWords, final int fromIndex,
                 final int maxWidth, final int minWidth, final int maxRow, final Paint paint,
                 final Resources res) {
             clearKeys();
@@ -70,53 +70,54 @@
             final float padding = res.getDimension(R.dimen.more_suggestions_key_horizontal_padding);
 
             int row = 0;
-            int pos = fromPos, rowStartPos = fromPos;
+            int index = fromIndex;
+            int rowStartIndex = fromIndex;
             final int size = Math.min(suggestedWords.size(), SuggestionStripView.MAX_SUGGESTIONS);
-            while (pos < size) {
-                final String word = suggestedWords.getWord(pos);
+            while (index < size) {
+                final String word = suggestedWords.getWord(index);
                 // TODO: Should take care of text x-scaling.
-                mWidths[pos] = (int)(TypefaceUtils.getLabelWidth(word, paint) + padding);
-                final int numColumn = pos - rowStartPos + 1;
+                mWidths[index] = (int)(TypefaceUtils.getLabelWidth(word, paint) + padding);
+                final int numColumn = index - rowStartIndex + 1;
                 final int columnWidth =
                         (maxWidth - mDividerWidth * (numColumn - 1)) / numColumn;
                 if (numColumn > MAX_COLUMNS_IN_ROW
-                        || !fitInWidth(rowStartPos, pos + 1, columnWidth)) {
+                        || !fitInWidth(rowStartIndex, index + 1, columnWidth)) {
                     if ((row + 1) >= maxRow) {
                         break;
                     }
-                    mNumColumnsInRow[row] = pos - rowStartPos;
-                    rowStartPos = pos;
+                    mNumColumnsInRow[row] = index - rowStartIndex;
+                    rowStartIndex = index;
                     row++;
                 }
-                mColumnOrders[pos] = pos - rowStartPos;
-                mRowNumbers[pos] = row;
-                pos++;
+                mColumnOrders[index] = index - rowStartIndex;
+                mRowNumbers[index] = row;
+                index++;
             }
-            mNumColumnsInRow[row] = pos - rowStartPos;
+            mNumColumnsInRow[row] = index - rowStartIndex;
             mNumRows = row + 1;
             mBaseWidth = mOccupiedWidth = Math.max(
-                    minWidth, calcurateMaxRowWidth(fromPos, pos));
+                    minWidth, calcurateMaxRowWidth(fromIndex, index));
             mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap;
-            return pos - fromPos;
+            return index - fromIndex;
         }
 
-        private boolean fitInWidth(final int startPos, final int endPos, final int width) {
-            for (int pos = startPos; pos < endPos; pos++) {
-                if (mWidths[pos] > width)
+        private boolean fitInWidth(final int startIndex, final int endIndex, final int width) {
+            for (int index = startIndex; index < endIndex; index++) {
+                if (mWidths[index] > width)
                     return false;
             }
             return true;
         }
 
-        private int calcurateMaxRowWidth(final int startPos, final int endPos) {
+        private int calcurateMaxRowWidth(final int startIndex, final int endIndex) {
             int maxRowWidth = 0;
-            int pos = startPos;
+            int index = startIndex;
             for (int row = 0; row < mNumRows; row++) {
                 final int numColumnInRow = mNumColumnsInRow[row];
                 int maxKeyWidth = 0;
-                while (pos < endPos && mRowNumbers[pos] == row) {
-                    maxKeyWidth = Math.max(maxKeyWidth, mWidths[pos]);
-                    pos++;
+                while (index < endIndex && mRowNumbers[index] == row) {
+                    maxKeyWidth = Math.max(maxKeyWidth, mWidths[index]);
+                    index++;
                 }
                 maxRowWidth = Math.max(maxRowWidth,
                         maxKeyWidth * numColumnInRow + mDividerWidth * (numColumnInRow - 1));
@@ -130,40 +131,40 @@
             { 2, 0, 1},
         };
 
-        public int getNumColumnInRow(final int pos) {
-            return mNumColumnsInRow[mRowNumbers[pos]];
+        public int getNumColumnInRow(final int index) {
+            return mNumColumnsInRow[mRowNumbers[index]];
         }
 
-        public int getColumnNumber(final int pos) {
-            final int columnOrder = mColumnOrders[pos];
-            final int numColumn = getNumColumnInRow(pos);
+        public int getColumnNumber(final int index) {
+            final int columnOrder = mColumnOrders[index];
+            final int numColumn = getNumColumnInRow(index);
             return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder];
         }
 
-        public int getX(final int pos) {
-            final int columnNumber = getColumnNumber(pos);
-            return columnNumber * (getWidth(pos) + mDividerWidth);
+        public int getX(final int index) {
+            final int columnNumber = getColumnNumber(index);
+            return columnNumber * (getWidth(index) + mDividerWidth);
         }
 
-        public int getY(final int pos) {
-            final int row = mRowNumbers[pos];
+        public int getY(final int index) {
+            final int row = mRowNumbers[index];
             return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding;
         }
 
-        public int getWidth(final int pos) {
-            final int numColumnInRow = getNumColumnInRow(pos);
+        public int getWidth(final int index) {
+            final int numColumnInRow = getNumColumnInRow(index);
             return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow;
         }
 
-        public void markAsEdgeKey(final Key key, final int pos) {
-            final int row = mRowNumbers[pos];
+        public void markAsEdgeKey(final Key key, final int index) {
+            final int row = mRowNumbers[index];
             if (row == 0)
                 key.markAsBottomEdge(this);
             if (row == mNumRows - 1)
                 key.markAsTopEdge(this);
 
             final int numColumnInRow = mNumColumnsInRow[row];
-            final int column = getColumnNumber(pos);
+            final int column = getColumnNumber(index);
             if (column == 0)
                 key.markAsLeftEdge(this);
             if (column == numColumnInRow - 1)
@@ -174,15 +175,15 @@
     public static final class Builder extends KeyboardBuilder<MoreSuggestionsParam> {
         private final MoreSuggestionsView mPaneView;
         private SuggestedWords mSuggestedWords;
-        private int mFromPos;
-        private int mToPos;
+        private int mFromIndex;
+        private int mToIndex;
 
         public Builder(final Context context, final MoreSuggestionsView paneView) {
             super(context, new MoreSuggestionsParam());
             mPaneView = paneView;
         }
 
-        public Builder layout(final SuggestedWords suggestedWords, final int fromPos,
+        public Builder layout(final SuggestedWords suggestedWords, final int fromIndex,
                 final int maxWidth, final int minWidth, final int maxRow,
                 final Keyboard parentKeyboard) {
             final int xmlId = R.xml.kbd_suggestions_pane_template;
@@ -190,10 +191,10 @@
             mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2;
 
             mPaneView.updateKeyboardGeometry(mParams.mDefaultRowHeight);
-            final int count = mParams.layout(suggestedWords, fromPos, maxWidth, minWidth, maxRow,
+            final int count = mParams.layout(suggestedWords, fromIndex, maxWidth, minWidth, maxRow,
                     mPaneView.newLabelPaint(null /* key */), mResources);
-            mFromPos = fromPos;
-            mToPos = fromPos + count;
+            mFromIndex = fromIndex;
+            mToIndex = fromIndex + count;
             mSuggestedWords = suggestedWords;
             return this;
         }
@@ -201,20 +202,20 @@
         @Override
         public MoreSuggestions build() {
             final MoreSuggestionsParam params = mParams;
-            for (int pos = mFromPos; pos < mToPos; pos++) {
-                final int x = params.getX(pos);
-                final int y = params.getY(pos);
-                final int width = params.getWidth(pos);
-                final String word = mSuggestedWords.getWord(pos);
-                final String info = Utils.getDebugInfo(mSuggestedWords, pos);
-                final int index = pos + SUGGESTION_CODE_BASE;
+            for (int index = mFromIndex; index < mToIndex; index++) {
+                final int x = params.getX(index);
+                final int y = params.getY(index);
+                final int width = params.getWidth(index);
+                final String word = mSuggestedWords.getWord(index);
+                final String info = Utils.getDebugInfo(mSuggestedWords, index);
+                final int indexInMoreSuggestions = index + SUGGESTION_CODE_BASE;
                 final Key key = new Key(
-                        params, word, info, KeyboardIconsSet.ICON_UNDEFINED, index, null, x, y,
-                        width, params.mDefaultRowHeight, 0);
-                params.markAsEdgeKey(key, pos);
+                        params, word, info, KeyboardIconsSet.ICON_UNDEFINED, indexInMoreSuggestions,
+                        null, x, y, width, params.mDefaultRowHeight, 0);
+                params.markAsEdgeKey(key, index);
                 params.onAddKey(key);
-                final int columnNumber = params.getColumnNumber(pos);
-                final int numColumnInRow = params.getNumColumnInRow(pos);
+                final int columnNumber = params.getColumnNumber(index);
+                final int numColumnInRow = params.getNumColumnInRow(index);
                 if (columnNumber < numColumnInRow - 1) {
                     final Divider divider = new Divider(params, params.mDivider, x + width, y,
                             params.mDividerWidth, params.mDefaultRowHeight);
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
new file mode 100644
index 0000000..2f2ec35
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -0,0 +1,561 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.suggestions;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.text.style.CharacterStyle;
+import android.text.style.StyleSpan;
+import android.text.style.UnderlineSpan;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.inputmethod.keyboard.ViewLayoutUtils;
+import com.android.inputmethod.latin.AutoCorrection;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.ResourceUtils;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.Utils;
+
+import java.util.ArrayList;
+
+final class SuggestionStripLayoutHelper {
+    private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3;
+    private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f;
+    private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2;
+    private static final int PUNCTUATIONS_IN_STRIP = 5;
+    private static final float MIN_TEXT_XSCALE = 0.70f;
+
+    public final int mPadding;
+    public final int mDividerWidth;
+    public final int mSuggestionsStripHeight;
+    public final int mSuggestionsCountInStrip;
+    public final int mMoreSuggestionsRowHeight;
+    private int mMaxMoreSuggestionsRow;
+    public final float mMinMoreSuggestionsWidth;
+    public final int mMoreSuggestionsBottomGap;
+    public boolean mMoreSuggestionsAvailable;
+
+    // The index of these {@link ArrayList} is the position in the suggestion strip. The indices
+    // increase towards the right for LTR scripts and the left for RTL scripts, starting with 0.
+    // The position of the most important suggestion is in {@link #mCenterPositionInStrip}
+    private final ArrayList<TextView> mWordViews;
+    private final ArrayList<View> mDividerViews;
+    private final ArrayList<TextView> mDebugInfoViews;
+
+    private final int mColorValidTypedWord;
+    private final int mColorTypedWord;
+    private final int mColorAutoCorrect;
+    private final int mColorSuggested;
+    private final float mAlphaObsoleted;
+    private final float mCenterSuggestionWeight;
+    private final int mCenterPositionInStrip;
+    private final Drawable mMoreSuggestionsHint;
+    private static final String MORE_SUGGESTIONS_HINT = "\u2026";
+    private static final String LEFTWARDS_ARROW = "\u2190";
+
+    private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD);
+    private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
+
+    private final int mSuggestionStripOption;
+    // These constants are the flag values of
+    // {@link R.styleable#SuggestionStripView_suggestionStripOption} attribute.
+    private static final int AUTO_CORRECT_BOLD = 0x01;
+    private static final int AUTO_CORRECT_UNDERLINE = 0x02;
+    private static final int VALID_TYPED_WORD_BOLD = 0x04;
+
+    private final TextView mWordToSaveView;
+    private final TextView mLeftwardsArrowView;
+    private final TextView mHintToSaveView;
+
+    public SuggestionStripLayoutHelper(final Context context, final AttributeSet attrs,
+            final int defStyle, final ArrayList<TextView> wordViews,
+            final ArrayList<View> dividerViews, final ArrayList<TextView> debugInfoViews) {
+        mWordViews = wordViews;
+        mDividerViews = dividerViews;
+        mDebugInfoViews = debugInfoViews;
+
+        final TextView wordView = wordViews.get(0);
+        final View dividerView = dividerViews.get(0);
+        mPadding = wordView.getCompoundPaddingLeft() + wordView.getCompoundPaddingRight();
+        dividerView.measure(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+        mDividerWidth = dividerView.getMeasuredWidth();
+
+        final Resources res = wordView.getResources();
+        mSuggestionsStripHeight = res.getDimensionPixelSize(R.dimen.suggestions_strip_height);
+
+        final TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle);
+        mSuggestionStripOption = a.getInt(
+                R.styleable.SuggestionStripView_suggestionStripOption, 0);
+        final float alphaValidTypedWord = ResourceUtils.getFraction(a,
+                R.styleable.SuggestionStripView_alphaValidTypedWord, 1.0f);
+        final float alphaTypedWord = ResourceUtils.getFraction(a,
+                R.styleable.SuggestionStripView_alphaTypedWord, 1.0f);
+        final float alphaAutoCorrect = ResourceUtils.getFraction(a,
+                R.styleable.SuggestionStripView_alphaAutoCorrect, 1.0f);
+        final float alphaSuggested = ResourceUtils.getFraction(a,
+                R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
+        mAlphaObsoleted = ResourceUtils.getFraction(a,
+                R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
+        mColorValidTypedWord = applyAlpha(a.getColor(
+                R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord);
+        mColorTypedWord = applyAlpha(a.getColor(
+                R.styleable.SuggestionStripView_colorTypedWord, 0), alphaTypedWord);
+        mColorAutoCorrect = applyAlpha(a.getColor(
+                R.styleable.SuggestionStripView_colorAutoCorrect, 0), alphaAutoCorrect);
+        mColorSuggested = applyAlpha(a.getColor(
+                R.styleable.SuggestionStripView_colorSuggested, 0), alphaSuggested);
+        mSuggestionsCountInStrip = a.getInt(
+                R.styleable.SuggestionStripView_suggestionsCountInStrip,
+                DEFAULT_SUGGESTIONS_COUNT_IN_STRIP);
+        mCenterSuggestionWeight = ResourceUtils.getFraction(a,
+                R.styleable.SuggestionStripView_centerSuggestionPercentile,
+                DEFAULT_CENTER_SUGGESTION_PERCENTILE);
+        mMaxMoreSuggestionsRow = a.getInt(
+                R.styleable.SuggestionStripView_maxMoreSuggestionsRow,
+                DEFAULT_MAX_MORE_SUGGESTIONS_ROW);
+        mMinMoreSuggestionsWidth = ResourceUtils.getFraction(a,
+                R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f);
+        a.recycle();
+
+        mMoreSuggestionsHint = getMoreSuggestionsHint(res,
+                res.getDimension(R.dimen.more_suggestions_hint_text_size), mColorAutoCorrect);
+        mCenterPositionInStrip = mSuggestionsCountInStrip / 2;
+        mMoreSuggestionsBottomGap = res.getDimensionPixelOffset(
+                R.dimen.more_suggestions_bottom_gap);
+        mMoreSuggestionsRowHeight = res.getDimensionPixelSize(R.dimen.more_suggestions_row_height);
+
+        final LayoutInflater inflater = LayoutInflater.from(context);
+        mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null);
+        mLeftwardsArrowView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null);
+        mHintToSaveView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null);
+    }
+
+    public int getMaxMoreSuggestionsRow() {
+        return mMaxMoreSuggestionsRow;
+    }
+
+    private int getMoreSuggestionsHeight() {
+        return mMaxMoreSuggestionsRow * mMoreSuggestionsRowHeight + mMoreSuggestionsBottomGap;
+    }
+
+    public int setMoreSuggestionsHeight(final int remainingHeight) {
+        final int currentHeight = getMoreSuggestionsHeight();
+        if (currentHeight <= remainingHeight) {
+            return currentHeight;
+        }
+
+        mMaxMoreSuggestionsRow = (remainingHeight - mMoreSuggestionsBottomGap)
+                / mMoreSuggestionsRowHeight;
+        final int newHeight = getMoreSuggestionsHeight();
+        return newHeight;
+    }
+
+    private static Drawable getMoreSuggestionsHint(final Resources res, final float textSize,
+            final int color) {
+        final Paint paint = new Paint();
+        paint.setAntiAlias(true);
+        paint.setTextAlign(Align.CENTER);
+        paint.setTextSize(textSize);
+        paint.setColor(color);
+        final Rect bounds = new Rect();
+        paint.getTextBounds(MORE_SUGGESTIONS_HINT, 0, MORE_SUGGESTIONS_HINT.length(), bounds);
+        final int width = Math.round(bounds.width() + 0.5f);
+        final int height = Math.round(bounds.height() + 0.5f);
+        final Bitmap buffer = Bitmap.createBitmap(width, (height * 3 / 2), Bitmap.Config.ARGB_8888);
+        final Canvas canvas = new Canvas(buffer);
+        canvas.drawText(MORE_SUGGESTIONS_HINT, width / 2, height, paint);
+        return new BitmapDrawable(res, buffer);
+    }
+
+    private CharSequence getStyledSuggestedWord(final SuggestedWords suggestedWords,
+            final int indexInSuggestedWords) {
+        final String word = suggestedWords.getWord(indexInSuggestedWords);
+        final boolean isAutoCorrect = indexInSuggestedWords == 1
+                && suggestedWords.willAutoCorrect();
+        final boolean isTypedWordValid = indexInSuggestedWords == 0
+                && suggestedWords.mTypedWordValid;
+        if (!isAutoCorrect && !isTypedWordValid) {
+            return word;
+        }
+
+        final int len = word.length();
+        final Spannable spannedWord = new SpannableString(word);
+        final int option = mSuggestionStripOption;
+        if ((isAutoCorrect && (option & AUTO_CORRECT_BOLD) != 0)
+                || (isTypedWordValid && (option & VALID_TYPED_WORD_BOLD) != 0)) {
+            spannedWord.setSpan(BOLD_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        }
+        if (isAutoCorrect && (option & AUTO_CORRECT_UNDERLINE) != 0) {
+            spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        }
+        return spannedWord;
+    }
+
+    private int getIndexInSuggestedWords(final int positionInStrip,
+            final SuggestedWords suggestedWords) {
+        // TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more
+        // suggestions.
+        final int mostImportantIndexInSuggestedWords = suggestedWords.willAutoCorrect()
+                ? SuggestedWords.INDEX_OF_AUTO_CORRECTION : SuggestedWords.INDEX_OF_TYPED_WORD;
+        if (positionInStrip == mCenterPositionInStrip) {
+            return mostImportantIndexInSuggestedWords;
+        }
+        if (positionInStrip == mostImportantIndexInSuggestedWords) {
+            return mCenterPositionInStrip;
+        }
+        return positionInStrip;
+    }
+
+    private int getSuggestionTextColor(final int positionInStrip,
+            final SuggestedWords suggestedWords) {
+        final int indexInSuggestedWords = getIndexInSuggestedWords(positionInStrip, suggestedWords);
+        // TODO: Need to revisit this logic with bigram suggestions
+        final boolean isSuggested = (indexInSuggestedWords != SuggestedWords.INDEX_OF_TYPED_WORD);
+
+        final int color;
+        if (positionInStrip == mCenterPositionInStrip && suggestedWords.willAutoCorrect()) {
+            color = mColorAutoCorrect;
+        } else if (positionInStrip == mCenterPositionInStrip && suggestedWords.mTypedWordValid) {
+            color = mColorValidTypedWord;
+        } else if (isSuggested) {
+            color = mColorSuggested;
+        } else {
+            color = mColorTypedWord;
+        }
+        if (LatinImeLogger.sDBG && suggestedWords.size() > 1) {
+            // If we auto-correct, then the autocorrection is in slot 0 and the typed word
+            // is in slot 1.
+            if (positionInStrip == mCenterPositionInStrip
+                    && AutoCorrection.shouldBlockAutoCorrectionBySafetyNet(
+                            suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION),
+                            suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD))) {
+                return 0xFFFF0000;
+            }
+        }
+
+        if (suggestedWords.mIsObsoleteSuggestions && isSuggested) {
+            return applyAlpha(color, mAlphaObsoleted);
+        }
+        return color;
+    }
+
+    private static int applyAlpha(final int color, final float alpha) {
+        final int newAlpha = (int)(Color.alpha(color) * alpha);
+        return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color));
+    }
+
+    private static void addDivider(final ViewGroup stripView, final View dividerView) {
+        stripView.addView(dividerView);
+        final LinearLayout.LayoutParams params =
+                (LinearLayout.LayoutParams)dividerView.getLayoutParams();
+        params.gravity = Gravity.CENTER;
+    }
+
+    public void layout(final SuggestedWords suggestedWords, final ViewGroup stripView,
+            final ViewGroup placerView) {
+        if (suggestedWords.mIsPunctuationSuggestions) {
+            layoutPunctuationSuggestions(suggestedWords, stripView);
+            return;
+        }
+
+        final int countInStrip = mSuggestionsCountInStrip;
+        setupWordViewsTextAndColor(suggestedWords, countInStrip);
+        mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
+        int x = 0;
+        for (int positionInStrip = 0; positionInStrip < countInStrip; positionInStrip++) {
+            if (positionInStrip != 0) {
+                final View divider = mDividerViews.get(positionInStrip);
+                // Add divider if this isn't the left most suggestion in suggestions strip.
+                addDivider(stripView, divider);
+                x += divider.getMeasuredWidth();
+            }
+
+            final int width = getSuggestionWidth(positionInStrip, placerView.getWidth());
+            final TextView wordView = layoutWord(positionInStrip, width);
+            stripView.addView(wordView);
+            setLayoutWeight(wordView, getSuggestionWeight(positionInStrip),
+                    ViewGroup.LayoutParams.MATCH_PARENT);
+            x += wordView.getMeasuredWidth();
+
+            if (SuggestionStripView.DBG) {
+                layoutDebugInfo(positionInStrip, placerView, x);
+            }
+        }
+    }
+
+    /**
+     * Format appropriately the suggested word in {@link #mWordViews} specified by
+     * <code>positionInStrip</code>. When the suggested word doesn't exist, the corresponding
+     * {@link TextView} will be disabled and never respond to user interaction. The suggested word
+     * may be shrunk or ellipsized to fit in the specified width.
+     *
+     * The <code>positionInStrip</code> argument is the index in the suggestion strip. The indices
+     * increase towards the right for LTR scripts and the left for RTL scripts, starting with 0.
+     * The position of the most important suggestion is in {@link #mCenterPositionInStrip}. This
+     * usually doesn't match the index in <code>suggedtedWords</code> -- see
+     * {@link #getIndexInSuggestedWords(int,SuggestedWords)}.
+     *
+     * @param positionInStrip the position in the suggestion strip.
+     * @param width the maximum width for layout in pixels.
+     * @return the {@link TextView} containing the suggested word appropriately formatted.
+     */
+    private TextView layoutWord(final int positionInStrip, final int width) {
+        final TextView wordView = mWordViews.get(positionInStrip);
+        final CharSequence word = wordView.getText();
+        if (positionInStrip == mCenterPositionInStrip && mMoreSuggestionsAvailable) {
+            // TODO: This "more suggestions hint" should have a nicely designed icon.
+            wordView.setCompoundDrawablesWithIntrinsicBounds(
+                    null, null, null, mMoreSuggestionsHint);
+            // HACK: Align with other TextViews that have no compound drawables.
+            wordView.setCompoundDrawablePadding(-mMoreSuggestionsHint.getIntrinsicHeight());
+        } else {
+            wordView.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
+        }
+
+        // Disable this suggestion if the suggestion is null or empty.
+        wordView.setEnabled(!TextUtils.isEmpty(word));
+        final CharSequence text = getEllipsizedText(word, width, wordView.getPaint());
+        final float scaleX = wordView.getTextScaleX();
+        wordView.setText(text); // TextView.setText() resets text scale x to 1.0.
+        wordView.setTextScaleX(scaleX);
+        return wordView;
+    }
+
+    private void layoutDebugInfo(final int positionInStrip, final ViewGroup placerView,
+            final int x) {
+        final TextView debugInfoView = mDebugInfoViews.get(positionInStrip);
+        final CharSequence debugInfo = debugInfoView.getText();
+        if (debugInfo == null) {
+            return;
+        }
+        placerView.addView(debugInfoView);
+        debugInfoView.measure(
+                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        final int infoWidth = debugInfoView.getMeasuredWidth();
+        final int y = debugInfoView.getMeasuredHeight();
+        ViewLayoutUtils.placeViewAt(
+                debugInfoView, x - infoWidth, y, infoWidth, debugInfoView.getMeasuredHeight());
+    }
+
+    private int getSuggestionWidth(final int positionInStrip, final int maxWidth) {
+        final int paddings = mPadding * mSuggestionsCountInStrip;
+        final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1);
+        final int availableWidth = maxWidth - paddings - dividers;
+        return (int)(availableWidth * getSuggestionWeight(positionInStrip));
+    }
+
+    private float getSuggestionWeight(final int positionInStrip) {
+        if (positionInStrip == mCenterPositionInStrip) {
+            return mCenterSuggestionWeight;
+        }
+        // TODO: Revisit this for cases of 5 or more suggestions
+        return (1.0f - mCenterSuggestionWeight) / (mSuggestionsCountInStrip - 1);
+    }
+
+    private void setupWordViewsTextAndColor(final SuggestedWords suggestedWords,
+            final int countInStrip) {
+        final int count = Math.min(suggestedWords.size(), countInStrip);
+        for (int positionInStrip = 0; positionInStrip < count; positionInStrip++) {
+            final int indexInSuggestedWords =
+                    getIndexInSuggestedWords(positionInStrip, suggestedWords);
+            final TextView wordView = mWordViews.get(positionInStrip);
+            // {@link TextView#getTag()} is used to get the index in suggestedWords at
+            // {@link SuggestionStripView#onClick(View)}.
+            wordView.setTag(indexInSuggestedWords);
+            wordView.setText(getStyledSuggestedWord(suggestedWords, indexInSuggestedWords));
+            wordView.setTextColor(getSuggestionTextColor(positionInStrip, suggestedWords));
+            if (SuggestionStripView.DBG) {
+                mDebugInfoViews.get(positionInStrip).setText(
+                        Utils.getDebugInfo(suggestedWords, indexInSuggestedWords));
+            }
+        }
+        for (int positionInStrip = count; positionInStrip < countInStrip; positionInStrip++) {
+            mWordViews.get(positionInStrip).setText(null);
+            // Make this inactive for touches in {@link #layoutWord(int,int)}.
+            if (SuggestionStripView.DBG) {
+                mDebugInfoViews.get(positionInStrip).setText(null);
+            }
+        }
+    }
+
+    private void layoutPunctuationSuggestions(final SuggestedWords suggestedWords,
+            final ViewGroup stripView) {
+        final int countInStrip = Math.min(suggestedWords.size(), PUNCTUATIONS_IN_STRIP);
+        for (int positionInStrip = 0; positionInStrip < countInStrip; positionInStrip++) {
+            if (positionInStrip != 0) {
+                // Add divider if this isn't the left most suggestion in suggestions strip.
+                addDivider(stripView, mDividerViews.get(positionInStrip));
+            }
+
+            final TextView wordView = mWordViews.get(positionInStrip);
+            wordView.setEnabled(true);
+            wordView.setTextColor(mColorAutoCorrect);
+            final String punctuation = suggestedWords.getWord(positionInStrip);
+            wordView.setText(punctuation);
+            wordView.setTextScaleX(1.0f);
+            wordView.setCompoundDrawables(null, null, null, null);
+            stripView.addView(wordView);
+            setLayoutWeight(wordView, 1.0f, mSuggestionsStripHeight);
+        }
+        mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
+    }
+
+    public void layoutAddToDictionaryHint(final String word, final ViewGroup stripView,
+            final int stripWidth, final CharSequence hintText, final OnClickListener listener) {
+        final int width = stripWidth - mDividerWidth - mPadding * 2;
+
+        final TextView wordView = mWordToSaveView;
+        wordView.setTextColor(mColorTypedWord);
+        final int wordWidth = (int)(width * mCenterSuggestionWeight);
+        final CharSequence text = getEllipsizedText(word, wordWidth, wordView.getPaint());
+        final float wordScaleX = wordView.getTextScaleX();
+        wordView.setTag(word);
+        wordView.setText(text);
+        wordView.setTextScaleX(wordScaleX);
+        stripView.addView(wordView);
+        setLayoutWeight(wordView, mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
+
+        stripView.addView(mDividerViews.get(0));
+
+        final TextView leftArrowView = mLeftwardsArrowView;
+        leftArrowView.setTextColor(mColorAutoCorrect);
+        leftArrowView.setText(LEFTWARDS_ARROW);
+        stripView.addView(leftArrowView);
+
+        final TextView hintView = mHintToSaveView;
+        hintView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
+        hintView.setTextColor(mColorAutoCorrect);
+        final int hintWidth = width - wordWidth - leftArrowView.getWidth();
+        final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint());
+        hintView.setText(hintText);
+        hintView.setTextScaleX(hintScaleX);
+        stripView.addView(hintView);
+        setLayoutWeight(
+                hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
+
+        wordView.setOnClickListener(listener);
+        leftArrowView.setOnClickListener(listener);
+        hintView.setOnClickListener(listener);
+    }
+
+    public CharSequence getAddToDictionaryWord() {
+        return (CharSequence)mWordToSaveView.getTag();
+    }
+
+    public boolean isAddToDictionaryShowing(final View v) {
+        return v == mWordToSaveView || v == mHintToSaveView || v == mLeftwardsArrowView;
+    }
+
+    private static void setLayoutWeight(final View v, final float weight, final int height) {
+        final ViewGroup.LayoutParams lp = v.getLayoutParams();
+        if (lp instanceof LinearLayout.LayoutParams) {
+            final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp;
+            llp.weight = weight;
+            llp.width = 0;
+            llp.height = height;
+        }
+    }
+
+    private static float getTextScaleX(final CharSequence text, final int maxWidth,
+            final TextPaint paint) {
+        paint.setTextScaleX(1.0f);
+        final int width = getTextWidth(text, paint);
+        if (width <= maxWidth) {
+            return 1.0f;
+        }
+        return maxWidth / (float)width;
+    }
+
+    private static CharSequence getEllipsizedText(final CharSequence text, final int maxWidth,
+            final TextPaint paint) {
+        if (text == null) {
+            return null;
+        }
+        final float scaleX = getTextScaleX(text, maxWidth, paint);
+        if (scaleX >= MIN_TEXT_XSCALE) {
+            paint.setTextScaleX(scaleX);
+            return text;
+        }
+
+        // Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To
+        // get squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE).
+        final CharSequence ellipsized = TextUtils.ellipsize(
+                text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE);
+        paint.setTextScaleX(MIN_TEXT_XSCALE);
+        return ellipsized;
+    }
+
+    private static int getTextWidth(final CharSequence text, final TextPaint paint) {
+        if (TextUtils.isEmpty(text)) {
+            return 0;
+        }
+        final Typeface savedTypeface = paint.getTypeface();
+        paint.setTypeface(getTextTypeface(text));
+        final int len = text.length();
+        final float[] widths = new float[len];
+        final int count = paint.getTextWidths(text, 0, len, widths);
+        int width = 0;
+        for (int i = 0; i < count; i++) {
+            width += Math.round(widths[i] + 0.5f);
+        }
+        paint.setTypeface(savedTypeface);
+        return width;
+    }
+
+    private static Typeface getTextTypeface(final CharSequence text) {
+        if (!(text instanceof SpannableString)) {
+            return Typeface.DEFAULT;
+        }
+
+        final SpannableString ss = (SpannableString)text;
+        final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class);
+        if (styles.length == 0) {
+            return Typeface.DEFAULT;
+        }
+
+        if (styles[0].getStyle() == Typeface.BOLD) {
+            return Typeface.DEFAULT_BOLD;
+        }
+        // TODO: BOLD_ITALIC, ITALIC case?
+        return Typeface.DEFAULT;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index 9764610..b2b9427 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -18,34 +18,14 @@
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.Align;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.Spanned;
-import android.text.TextPaint;
-import android.text.TextUtils;
-import android.text.style.CharacterStyle;
-import android.text.style.StyleSpan;
-import android.text.style.UnderlineSpan;
 import android.util.AttributeSet;
 import android.view.GestureDetector;
-import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
-import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
@@ -53,17 +33,13 @@
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.keyboard.MainKeyboardView;
 import com.android.inputmethod.keyboard.MoreKeysPanel;
-import com.android.inputmethod.keyboard.ViewLayoutUtils;
 import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
-import com.android.inputmethod.latin.AutoCorrection;
 import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResourceUtils;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.Utils;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionsListener;
 import com.android.inputmethod.research.ResearchLogger;
@@ -89,516 +65,14 @@
     private final MoreSuggestionsView mMoreSuggestionsView;
     private final MoreSuggestions.Builder mMoreSuggestionsBuilder;
 
-    private final ArrayList<TextView> mWords = CollectionUtils.newArrayList();
-    private final ArrayList<TextView> mInfos = CollectionUtils.newArrayList();
-    private final ArrayList<View> mDividers = CollectionUtils.newArrayList();
+    private final ArrayList<TextView> mWordViews = CollectionUtils.newArrayList();
+    private final ArrayList<TextView> mDebugInfoViews = CollectionUtils.newArrayList();
+    private final ArrayList<View> mDividerViews = CollectionUtils.newArrayList();
 
     Listener mListener;
     private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
 
-    private final SuggestionStripViewParams mParams;
-    private static final float MIN_TEXT_XSCALE = 0.70f;
-
-    private static final class SuggestionStripViewParams {
-        private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3;
-        private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f;
-        private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2;
-        private static final int PUNCTUATIONS_IN_STRIP = 5;
-
-        public final int mPadding;
-        public final int mDividerWidth;
-        public final int mSuggestionsStripHeight;
-        public final int mSuggestionsCountInStrip;
-        public final int mMoreSuggestionsRowHeight;
-        private int mMaxMoreSuggestionsRow;
-        public final float mMinMoreSuggestionsWidth;
-        public final int mMoreSuggestionsBottomGap;
-
-        private final ArrayList<TextView> mWords;
-        private final ArrayList<View> mDividers;
-        private final ArrayList<TextView> mInfos;
-
-        private final int mColorValidTypedWord;
-        private final int mColorTypedWord;
-        private final int mColorAutoCorrect;
-        private final int mColorSuggested;
-        private final float mAlphaObsoleted;
-        private final float mCenterSuggestionWeight;
-        private final int mCenterSuggestionIndex;
-        private final Drawable mMoreSuggestionsHint;
-        private static final String MORE_SUGGESTIONS_HINT = "\u2026";
-        private static final String LEFTWARDS_ARROW = "\u2190";
-
-        private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD);
-        private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
-        private static final int AUTO_CORRECT_BOLD = 0x01;
-        private static final int AUTO_CORRECT_UNDERLINE = 0x02;
-        private static final int VALID_TYPED_WORD_BOLD = 0x04;
-
-        private final int mSuggestionStripOption;
-
-        private final ArrayList<CharSequence> mTexts = CollectionUtils.newArrayList();
-
-        public boolean mMoreSuggestionsAvailable;
-
-        private final TextView mWordToSaveView;
-        private final TextView mLeftwardsArrowView;
-        private final TextView mHintToSaveView;
-
-        public SuggestionStripViewParams(final Context context, final AttributeSet attrs,
-                final int defStyle, final ArrayList<TextView> words, final ArrayList<View> dividers,
-                final ArrayList<TextView> infos) {
-            mWords = words;
-            mDividers = dividers;
-            mInfos = infos;
-
-            final TextView word = words.get(0);
-            final View divider = dividers.get(0);
-            mPadding = word.getCompoundPaddingLeft() + word.getCompoundPaddingRight();
-            divider.measure(
-                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
-            mDividerWidth = divider.getMeasuredWidth();
-
-            final Resources res = word.getResources();
-            mSuggestionsStripHeight = res.getDimensionPixelSize(R.dimen.suggestions_strip_height);
-
-            final TypedArray a = context.obtainStyledAttributes(attrs,
-                    R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle);
-            mSuggestionStripOption = a.getInt(
-                    R.styleable.SuggestionStripView_suggestionStripOption, 0);
-            final float alphaValidTypedWord = ResourceUtils.getFraction(a,
-                    R.styleable.SuggestionStripView_alphaValidTypedWord, 1.0f);
-            final float alphaTypedWord = ResourceUtils.getFraction(a,
-                    R.styleable.SuggestionStripView_alphaTypedWord, 1.0f);
-            final float alphaAutoCorrect = ResourceUtils.getFraction(a,
-                    R.styleable.SuggestionStripView_alphaAutoCorrect, 1.0f);
-            final float alphaSuggested = ResourceUtils.getFraction(a,
-                    R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
-            mAlphaObsoleted = ResourceUtils.getFraction(a,
-                    R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
-            mColorValidTypedWord = applyAlpha(a.getColor(
-                    R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord);
-            mColorTypedWord = applyAlpha(a.getColor(
-                    R.styleable.SuggestionStripView_colorTypedWord, 0), alphaTypedWord);
-            mColorAutoCorrect = applyAlpha(a.getColor(
-                    R.styleable.SuggestionStripView_colorAutoCorrect, 0), alphaAutoCorrect);
-            mColorSuggested = applyAlpha(a.getColor(
-                    R.styleable.SuggestionStripView_colorSuggested, 0), alphaSuggested);
-            mSuggestionsCountInStrip = a.getInt(
-                    R.styleable.SuggestionStripView_suggestionsCountInStrip,
-                    DEFAULT_SUGGESTIONS_COUNT_IN_STRIP);
-            mCenterSuggestionWeight = ResourceUtils.getFraction(a,
-                    R.styleable.SuggestionStripView_centerSuggestionPercentile,
-                    DEFAULT_CENTER_SUGGESTION_PERCENTILE);
-            mMaxMoreSuggestionsRow = a.getInt(
-                    R.styleable.SuggestionStripView_maxMoreSuggestionsRow,
-                    DEFAULT_MAX_MORE_SUGGESTIONS_ROW);
-            mMinMoreSuggestionsWidth = ResourceUtils.getFraction(a,
-                    R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f);
-            a.recycle();
-
-            mMoreSuggestionsHint = getMoreSuggestionsHint(res,
-                    res.getDimension(R.dimen.more_suggestions_hint_text_size), mColorAutoCorrect);
-            mCenterSuggestionIndex = mSuggestionsCountInStrip / 2;
-            mMoreSuggestionsBottomGap = res.getDimensionPixelOffset(
-                    R.dimen.more_suggestions_bottom_gap);
-            mMoreSuggestionsRowHeight = res.getDimensionPixelSize(
-                    R.dimen.more_suggestions_row_height);
-
-            final LayoutInflater inflater = LayoutInflater.from(context);
-            mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null);
-            mLeftwardsArrowView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null);
-            mHintToSaveView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null);
-        }
-
-        public int getMaxMoreSuggestionsRow() {
-            return mMaxMoreSuggestionsRow;
-        }
-
-        private int getMoreSuggestionsHeight() {
-            return mMaxMoreSuggestionsRow * mMoreSuggestionsRowHeight + mMoreSuggestionsBottomGap;
-        }
-
-        public int setMoreSuggestionsHeight(final int remainingHeight) {
-            final int currentHeight = getMoreSuggestionsHeight();
-            if (currentHeight <= remainingHeight) {
-                return currentHeight;
-            }
-
-            mMaxMoreSuggestionsRow = (remainingHeight - mMoreSuggestionsBottomGap)
-                    / mMoreSuggestionsRowHeight;
-            final int newHeight = getMoreSuggestionsHeight();
-            return newHeight;
-        }
-
-        private static Drawable getMoreSuggestionsHint(final Resources res, final float textSize,
-                final int color) {
-            final Paint paint = new Paint();
-            paint.setAntiAlias(true);
-            paint.setTextAlign(Align.CENTER);
-            paint.setTextSize(textSize);
-            paint.setColor(color);
-            final Rect bounds = new Rect();
-            paint.getTextBounds(MORE_SUGGESTIONS_HINT, 0, MORE_SUGGESTIONS_HINT.length(), bounds);
-            final int width = Math.round(bounds.width() + 0.5f);
-            final int height = Math.round(bounds.height() + 0.5f);
-            final Bitmap buffer = Bitmap.createBitmap(
-                    width, (height * 3 / 2), Bitmap.Config.ARGB_8888);
-            final Canvas canvas = new Canvas(buffer);
-            canvas.drawText(MORE_SUGGESTIONS_HINT, width / 2, height, paint);
-            return new BitmapDrawable(res, buffer);
-        }
-
-        private CharSequence getStyledSuggestionWord(final SuggestedWords suggestedWords,
-                final int indexInSuggestedWords) {
-            final String word = suggestedWords.getWord(indexInSuggestedWords);
-            final boolean isAutoCorrect = indexInSuggestedWords == 1
-                    && suggestedWords.willAutoCorrect();
-            final boolean isTypedWordValid = indexInSuggestedWords == 0
-                    && suggestedWords.mTypedWordValid;
-            if (!isAutoCorrect && !isTypedWordValid)
-                return word;
-
-            final int len = word.length();
-            final Spannable spannedWord = new SpannableString(word);
-            final int option = mSuggestionStripOption;
-            if ((isAutoCorrect && (option & AUTO_CORRECT_BOLD) != 0)
-                    || (isTypedWordValid && (option & VALID_TYPED_WORD_BOLD) != 0)) {
-                spannedWord.setSpan(BOLD_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
-            }
-            if (isAutoCorrect && (option & AUTO_CORRECT_UNDERLINE) != 0) {
-                spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
-            }
-            return spannedWord;
-        }
-
-        private int getIndexInSuggestedWords(final int indexInStrip,
-                final SuggestedWords suggestedWords) {
-            // TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more
-            // suggestions.
-            final int mostImportantIndexInSuggestedWords = suggestedWords.willAutoCorrect() ? 1 : 0;
-            if (indexInStrip == mCenterSuggestionIndex) {
-                return mostImportantIndexInSuggestedWords;
-            } else if (indexInStrip == mostImportantIndexInSuggestedWords) {
-                return mCenterSuggestionIndex;
-            } else {
-                return indexInStrip;
-            }
-        }
-
-        private int getSuggestionTextColor(final int indexInStrip,
-                final SuggestedWords suggestedWords) {
-            final int indexInSuggestedWords = getIndexInSuggestedWords(
-                    indexInStrip, suggestedWords);
-            // TODO: Need to revisit this logic with bigram suggestions
-            final boolean isSuggested = (indexInSuggestedWords != 0);
-
-            final int color;
-            if (indexInStrip == mCenterSuggestionIndex && suggestedWords.willAutoCorrect()) {
-                color = mColorAutoCorrect;
-            } else if (indexInStrip == mCenterSuggestionIndex && suggestedWords.mTypedWordValid) {
-                color = mColorValidTypedWord;
-            } else if (isSuggested) {
-                color = mColorSuggested;
-            } else {
-                color = mColorTypedWord;
-            }
-            if (LatinImeLogger.sDBG && suggestedWords.size() > 1) {
-                // If we auto-correct, then the autocorrection is in slot 0 and the typed word
-                // is in slot 1.
-                if (indexInStrip == mCenterSuggestionIndex
-                        && AutoCorrection.shouldBlockAutoCorrectionBySafetyNet(
-                                suggestedWords.getWord(1), suggestedWords.getWord(0))) {
-                    return 0xFFFF0000;
-                }
-            }
-
-            if (suggestedWords.mIsObsoleteSuggestions && isSuggested) {
-                return applyAlpha(color, mAlphaObsoleted);
-            } else {
-                return color;
-            }
-        }
-
-        private static int applyAlpha(final int color, final float alpha) {
-            final int newAlpha = (int)(Color.alpha(color) * alpha);
-            return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color));
-        }
-
-        private static void addDivider(final ViewGroup stripView, final View divider) {
-            stripView.addView(divider);
-            final LinearLayout.LayoutParams params =
-                    (LinearLayout.LayoutParams)divider.getLayoutParams();
-            params.gravity = Gravity.CENTER;
-        }
-
-        public void layout(final SuggestedWords suggestedWords, final ViewGroup stripView,
-                final ViewGroup placer, final int stripWidth) {
-            if (suggestedWords.mIsPunctuationSuggestions) {
-                layoutPunctuationSuggestions(suggestedWords, stripView);
-                return;
-            }
-
-            final int countInStrip = mSuggestionsCountInStrip;
-            setupTexts(suggestedWords, countInStrip);
-            mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
-            int x = 0;
-            for (int indexInStrip = 0; indexInStrip < countInStrip; indexInStrip++) {
-                if (indexInStrip != 0) {
-                    final View divider = mDividers.get(indexInStrip);
-                    // Add divider if this isn't the left most suggestion in suggestions strip.
-                    addDivider(stripView, divider);
-                    x += divider.getMeasuredWidth();
-                }
-
-                final int width = getSuggestionWidth(indexInStrip, stripWidth);
-                final TextView word = layoutWord(suggestedWords, indexInStrip, width);
-                stripView.addView(word);
-                setLayoutWeight(word, getSuggestionWeight(indexInStrip),
-                        ViewGroup.LayoutParams.MATCH_PARENT);
-                x += word.getMeasuredWidth();
-
-                if (DBG) {
-                    layoutDebugInfo(suggestedWords, indexInStrip, placer, x);
-                }
-            }
-        }
-
-        /**
-         * Format appropriately the suggested word indirectly specified by
-         * <code>indexInStrip</code> as text in a corresponding {@link TextView}. When the
-         * suggested word doesn't exist, the corresponding {@link TextView} will be disabled
-         * and never respond to user interaction. The suggested word may be shrunk or ellipsized to
-         * fit in the specified width.
-         *
-         * The <code>indexInStrip</code> argument is the index in the suggestion strip. The indices
-         * increase towards the right for LTR scripts and the left for RTL scripts, starting with 0.
-         * The index of the most important suggestion is in {@link #mCenterSuggestionIndex}. This
-         * usually doesn't match the index in <code>suggedtedWords</code> -- see
-         * {@link #getIndexInSuggestedWords(int,SuggestedWords)}.
-         *
-         * @param suggestedWords the list of suggestions.
-         * @param indexInStrip the in the suggestion strip.
-         * @param width the maximum width for layout in pixels.
-         * @return the {@link TextView} containing the suggested word appropriately formatted.
-         */
-        private TextView layoutWord(final SuggestedWords suggestedWords, final int indexInStrip,
-                final int width) {
-            final int indexInSuggestedWords = getIndexInSuggestedWords(
-                    indexInStrip, suggestedWords);
-            final CharSequence styled = mTexts.get(indexInSuggestedWords);
-            final TextView word = mWords.get(indexInSuggestedWords);
-            if (indexInStrip == mCenterSuggestionIndex && mMoreSuggestionsAvailable) {
-                // TODO: This "more suggestions hint" should have a nicely designed icon.
-                word.setCompoundDrawablesWithIntrinsicBounds(
-                        null, null, null, mMoreSuggestionsHint);
-                // HACK: Align with other TextViews that have no compound drawables.
-                word.setCompoundDrawablePadding(-mMoreSuggestionsHint.getIntrinsicHeight());
-            } else {
-                word.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
-            }
-
-            // Disable this suggestion if the suggestion is null or empty.
-            word.setEnabled(!TextUtils.isEmpty(styled));
-            word.setTextColor(getSuggestionTextColor(indexInStrip, suggestedWords));
-            final CharSequence text = getEllipsizedText(styled, width, word.getPaint());
-            final float scaleX = word.getTextScaleX();
-            word.setText(text); // TextView.setText() resets text scale x to 1.0.
-            word.setTextScaleX(scaleX);
-            return word;
-        }
-
-        private void layoutDebugInfo(final SuggestedWords suggestedWords, final int indexInStrip,
-                final ViewGroup placer, final int x) {
-            final int indexInSuggestedWords = getIndexInSuggestedWords(
-                    indexInStrip, suggestedWords);
-            if (indexInSuggestedWords >= suggestedWords.size()) {
-                return;
-            }
-            final String debugInfo = Utils.getDebugInfo(suggestedWords, indexInSuggestedWords);
-            if (debugInfo == null) {
-                return;
-            }
-            final TextView info = mInfos.get(indexInSuggestedWords);
-            info.setText(debugInfo);
-            placer.addView(info);
-            info.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-            final int infoWidth = info.getMeasuredWidth();
-            final int y = info.getMeasuredHeight();
-            ViewLayoutUtils.placeViewAt(
-                    info, x - infoWidth, y, infoWidth, info.getMeasuredHeight());
-        }
-
-        private int getSuggestionWidth(final int indexInStrip, final int maxWidth) {
-            final int paddings = mPadding * mSuggestionsCountInStrip;
-            final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1);
-            final int availableWidth = maxWidth - paddings - dividers;
-            return (int)(availableWidth * getSuggestionWeight(indexInStrip));
-        }
-
-        private float getSuggestionWeight(final int indexInStrip) {
-            if (indexInStrip == mCenterSuggestionIndex) {
-                return mCenterSuggestionWeight;
-            } else {
-                // TODO: Revisit this for cases of 5 or more suggestions
-                return (1.0f - mCenterSuggestionWeight) / (mSuggestionsCountInStrip - 1);
-            }
-        }
-
-        private void setupTexts(final SuggestedWords suggestedWords, final int countInStrip) {
-            mTexts.clear();
-            final int count = Math.min(suggestedWords.size(), countInStrip);
-            for (int pos = 0; pos < count; pos++) {
-                final CharSequence styled = getStyledSuggestionWord(suggestedWords, pos);
-                mTexts.add(styled);
-            }
-            for (int pos = count; pos < countInStrip; pos++) {
-                // Make this inactive for touches in layout().
-                mTexts.add(null);
-            }
-        }
-
-        private void layoutPunctuationSuggestions(final SuggestedWords suggestedWords,
-                final ViewGroup stripView) {
-            final int countInStrip = Math.min(suggestedWords.size(), PUNCTUATIONS_IN_STRIP);
-            for (int indexInStrip = 0; indexInStrip < countInStrip; indexInStrip++) {
-                if (indexInStrip != 0) {
-                    // Add divider if this isn't the left most suggestion in suggestions strip.
-                    addDivider(stripView, mDividers.get(indexInStrip));
-                }
-
-                final TextView word = mWords.get(indexInStrip);
-                word.setEnabled(true);
-                word.setTextColor(mColorAutoCorrect);
-                final String text = suggestedWords.getWord(indexInStrip);
-                word.setText(text);
-                word.setTextScaleX(1.0f);
-                word.setCompoundDrawables(null, null, null, null);
-                stripView.addView(word);
-                setLayoutWeight(word, 1.0f, mSuggestionsStripHeight);
-            }
-            mMoreSuggestionsAvailable = false;
-        }
-
-        public void layoutAddToDictionaryHint(final String word, final ViewGroup stripView,
-                final int stripWidth, final CharSequence hintText, final OnClickListener listener) {
-            final int width = stripWidth - mDividerWidth - mPadding * 2;
-
-            final TextView wordView = mWordToSaveView;
-            wordView.setTextColor(mColorTypedWord);
-            final int wordWidth = (int)(width * mCenterSuggestionWeight);
-            final CharSequence text = getEllipsizedText(word, wordWidth, wordView.getPaint());
-            final float wordScaleX = wordView.getTextScaleX();
-            wordView.setTag(word);
-            wordView.setText(text);
-            wordView.setTextScaleX(wordScaleX);
-            stripView.addView(wordView);
-            setLayoutWeight(wordView, mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
-
-            stripView.addView(mDividers.get(0));
-
-            final TextView leftArrowView = mLeftwardsArrowView;
-            leftArrowView.setTextColor(mColorAutoCorrect);
-            leftArrowView.setText(LEFTWARDS_ARROW);
-            stripView.addView(leftArrowView);
-
-            final TextView hintView = mHintToSaveView;
-            hintView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
-            hintView.setTextColor(mColorAutoCorrect);
-            final int hintWidth = width - wordWidth - leftArrowView.getWidth();
-            final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint());
-            hintView.setText(hintText);
-            hintView.setTextScaleX(hintScaleX);
-            stripView.addView(hintView);
-            setLayoutWeight(
-                    hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
-
-            wordView.setOnClickListener(listener);
-            leftArrowView.setOnClickListener(listener);
-            hintView.setOnClickListener(listener);
-        }
-
-        public CharSequence getAddToDictionaryWord() {
-            return (CharSequence)mWordToSaveView.getTag();
-        }
-
-        public boolean isAddToDictionaryShowing(final View v) {
-            return v == mWordToSaveView || v == mHintToSaveView || v == mLeftwardsArrowView;
-        }
-
-        private static void setLayoutWeight(final View v, final float weight, final int height) {
-            final ViewGroup.LayoutParams lp = v.getLayoutParams();
-            if (lp instanceof LinearLayout.LayoutParams) {
-                final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp;
-                llp.weight = weight;
-                llp.width = 0;
-                llp.height = height;
-            }
-        }
-
-        private static float getTextScaleX(final CharSequence text, final int maxWidth,
-                final TextPaint paint) {
-            paint.setTextScaleX(1.0f);
-            final int width = getTextWidth(text, paint);
-            if (width <= maxWidth) {
-                return 1.0f;
-            }
-            return maxWidth / (float)width;
-        }
-
-        private static CharSequence getEllipsizedText(final CharSequence text, final int maxWidth,
-                final TextPaint paint) {
-            if (text == null) return null;
-            paint.setTextScaleX(1.0f);
-            final int width = getTextWidth(text, paint);
-            if (width <= maxWidth) {
-                return text;
-            }
-            final float scaleX = maxWidth / (float)width;
-            if (scaleX >= MIN_TEXT_XSCALE) {
-                paint.setTextScaleX(scaleX);
-                return text;
-            }
-
-            // Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To
-            // get squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE).
-            final CharSequence ellipsized = TextUtils.ellipsize(
-                    text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE);
-            paint.setTextScaleX(MIN_TEXT_XSCALE);
-            return ellipsized;
-        }
-
-        private static int getTextWidth(final CharSequence text, final TextPaint paint) {
-            if (TextUtils.isEmpty(text)) return 0;
-            final Typeface savedTypeface = paint.getTypeface();
-            paint.setTypeface(getTextTypeface(text));
-            final int len = text.length();
-            final float[] widths = new float[len];
-            final int count = paint.getTextWidths(text, 0, len, widths);
-            int width = 0;
-            for (int i = 0; i < count; i++) {
-                width += Math.round(widths[i] + 0.5f);
-            }
-            paint.setTypeface(savedTypeface);
-            return width;
-        }
-
-        private static Typeface getTextTypeface(final CharSequence text) {
-            if (!(text instanceof SpannableString))
-                return Typeface.DEFAULT;
-
-            final SpannableString ss = (SpannableString)text;
-            final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class);
-            if (styles.length == 0)
-                return Typeface.DEFAULT;
-
-            switch (styles[0].getStyle()) {
-            case Typeface.BOLD: return Typeface.DEFAULT_BOLD;
-            // TODO: BOLD_ITALIC, ITALIC case?
-            default: return Typeface.DEFAULT;
-            }
-        }
-    }
+    private final SuggestionStripLayoutHelper mLayoutHelper;
 
     /**
      * Construct a {@link SuggestionStripView} for showing suggestions to be picked by the user.
@@ -619,19 +93,17 @@
         mSuggestionsStrip = (ViewGroup)findViewById(R.id.suggestions_strip);
         for (int pos = 0; pos < MAX_SUGGESTIONS; pos++) {
             final TextView word = (TextView)inflater.inflate(R.layout.suggestion_word, null);
-            word.setTag(pos);
             word.setOnClickListener(this);
             word.setOnLongClickListener(this);
-            mWords.add(word);
+            mWordViews.add(word);
             final View divider = inflater.inflate(R.layout.suggestion_divider, null);
-            divider.setTag(pos);
             divider.setOnClickListener(this);
-            mDividers.add(divider);
-            mInfos.add((TextView)inflater.inflate(R.layout.suggestion_info, null));
+            mDividerViews.add(divider);
+            mDebugInfoViews.add((TextView)inflater.inflate(R.layout.suggestion_info, null));
         }
 
-        mParams = new SuggestionStripViewParams(
-                context, attrs, defStyle, mWords, mDividers, mInfos);
+        mLayoutHelper = new SuggestionStripLayoutHelper(
+                context, attrs, defStyle, mWordViews, mDividerViews, mDebugInfoViews);
 
         mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null);
         mMoreSuggestionsView = (MoreSuggestionsView)mMoreSuggestionsContainer
@@ -657,24 +129,25 @@
     public void setSuggestions(final SuggestedWords suggestedWords) {
         clear();
         mSuggestedWords = suggestedWords;
-        mParams.layout(mSuggestedWords, mSuggestionsStrip, this, getWidth());
+        mLayoutHelper.layout(mSuggestedWords, mSuggestionsStrip, this);
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.suggestionStripView_setSuggestions(mSuggestedWords);
         }
     }
 
     public int setMoreSuggestionsHeight(final int remainingHeight) {
-        return mParams.setMoreSuggestionsHeight(remainingHeight);
+        return mLayoutHelper.setMoreSuggestionsHeight(remainingHeight);
     }
 
     public boolean isShowingAddToDictionaryHint() {
         return mSuggestionsStrip.getChildCount() > 0
-                && mParams.isAddToDictionaryShowing(mSuggestionsStrip.getChildAt(0));
+                && mLayoutHelper.isAddToDictionaryShowing(mSuggestionsStrip.getChildAt(0));
     }
 
     public void showAddToDictionaryHint(final String word, final CharSequence hintText) {
         clear();
-        mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth(), hintText, this);
+        mLayoutHelper.layoutAddToDictionaryHint(
+                word, mSuggestionsStrip, getWidth(), hintText, this);
     }
 
     public boolean dismissAddToDictionaryHint() {
@@ -739,30 +212,30 @@
         if (parentKeyboard == null) {
             return false;
         }
-        final SuggestionStripViewParams params = mParams;
-        if (!params.mMoreSuggestionsAvailable) {
+        final SuggestionStripLayoutHelper layoutHelper = mLayoutHelper;
+        if (!layoutHelper.mMoreSuggestionsAvailable) {
             return false;
         }
         final int stripWidth = getWidth();
         final View container = mMoreSuggestionsContainer;
         final int maxWidth = stripWidth - container.getPaddingLeft() - container.getPaddingRight();
         final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder;
-        builder.layout(mSuggestedWords, params.mSuggestionsCountInStrip, maxWidth,
-                (int)(maxWidth * params.mMinMoreSuggestionsWidth),
-                params.getMaxMoreSuggestionsRow(), parentKeyboard);
+        builder.layout(mSuggestedWords, layoutHelper.mSuggestionsCountInStrip, maxWidth,
+                (int)(maxWidth * layoutHelper.mMinMoreSuggestionsWidth),
+                layoutHelper.getMaxMoreSuggestionsRow(), parentKeyboard);
         mMoreSuggestionsView.setKeyboard(builder.build());
         container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
 
         final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView;
         final int pointX = stripWidth / 2;
-        final int pointY = -params.mMoreSuggestionsBottomGap;
+        final int pointY = -layoutHelper.mMoreSuggestionsBottomGap;
         moreKeysPanel.showMoreKeysPanel(this, mMoreSuggestionsController, pointX, pointY,
                 mMoreSuggestionsListener);
         mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING;
         mOriginX = mLastX;
         mOriginY = mLastY;
-        for (int i = 0; i < params.mSuggestionsCountInStrip; i++) {
-            mWords.get(i).setPressed(false);
+        for (int i = 0; i < layoutHelper.mSuggestionsCountInStrip; i++) {
+            mWordViews.get(i).setPressed(false);
         }
         return true;
     }
@@ -832,18 +305,20 @@
 
     @Override
     public void onClick(final View view) {
-        if (mParams.isAddToDictionaryShowing(view)) {
-            mListener.addWordToUserDictionary(mParams.getAddToDictionaryWord().toString());
+        if (mLayoutHelper.isAddToDictionaryShowing(view)) {
+            mListener.addWordToUserDictionary(mLayoutHelper.getAddToDictionaryWord().toString());
             clear();
             return;
         }
 
         final Object tag = view.getTag();
-        if (!(tag instanceof Integer))
+        if (!(tag instanceof Integer)) {
             return;
+        }
         final int index = (Integer) tag;
-        if (index >= mSuggestedWords.size())
+        if (index >= mSuggestedWords.size()) {
             return;
+        }
 
         final SuggestedWordInfo wordInfo = mSuggestedWords.getInfo(index);
         mListener.pickSuggestionManually(index, wordInfo);
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
index cbe9515..1b758c6 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -46,17 +46,9 @@
     jni_common.cpp
 
 LATIN_IME_CORE_SRC_FILES := \
-    additional_proximity_chars.cpp \
     bigram_dictionary.cpp \
-    char_utils.cpp \
     correction.cpp \
-    dictionary.cpp \
     dic_traverse_wrapper.cpp \
-    digraph_utils.cpp \
-    proximity_info.cpp \
-    proximity_info_params.cpp \
-    proximity_info_state.cpp \
-    proximity_info_state_utils.cpp \
     unigram_dictionary.cpp \
     words_priority_queue.cpp \
     suggest/core/suggest.cpp \
@@ -64,6 +56,16 @@
         dic_node.cpp \
         dic_node_utils.cpp \
         dic_nodes_cache.cpp) \
+    $(addprefix suggest/core/dictionary/, \
+        char_utils.cpp \
+        dictionary.cpp \
+        digraph_utils.cpp) \
+    $(addprefix suggest/core/layout/, \
+        additional_proximity_chars.cpp \
+        proximity_info.cpp \
+        proximity_info_params.cpp \
+        proximity_info_state.cpp \
+        proximity_info_state_utils.cpp) \
     suggest/core/policy/weighting.cpp \
     suggest/core/session/dic_traverse_session.cpp \
     suggest/policyimpl/gesture/gesture_suggest_policy_factory.cpp \
diff --git a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
index dedb02a..e312aea 100644
--- a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
+++ b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
@@ -17,10 +17,11 @@
 #define LOG_TAG "LatinIME: jni: ProximityInfo"
 
 #include "com_android_inputmethod_keyboard_ProximityInfo.h"
+
 #include "defines.h"
 #include "jni.h"
 #include "jni_common.h"
-#include "proximity_info.h"
+#include "suggest/core/layout/proximity_info.h"
 
 namespace latinime {
 
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 0e9c292..9f5e2ae 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -29,13 +29,14 @@
 #include <cstdio> // for fopen() etc.
 #endif // USE_MMAP_FOR_DICTIONARY
 
-#include "binary_format.h"
 #include "com_android_inputmethod_latin_BinaryDictionary.h"
+
 #include "correction.h"
-#include "dictionary.h"
 #include "jni.h"
 #include "jni_common.h"
 #include "suggest_options.h"
+#include "suggest/core/dictionary/binary_format.h"
+#include "suggest/core/dictionary/dictionary.h"
 
 namespace latinime {
 
@@ -162,7 +163,7 @@
     const jsize numberOfOptions = env->GetArrayLength(suggestOptions);
     int options[numberOfOptions];
     env->GetIntArrayRegion(suggestOptions, 0, numberOfOptions, options);
-    SuggestOptions givenOptions(options, numberOfOptions);
+    SuggestOptions givenSuggestOptions(options, numberOfOptions);
 
     // Output values
     /* By the way, let's check the output array length here to make sure */
@@ -190,12 +191,11 @@
     memset(outputTypes, 0, sizeof(outputTypes));
 
     int count;
-    if (givenOptions.isGesture() || inputSize > 0) {
+    if (givenSuggestOptions.isGesture() || inputSize > 0) {
         count = dictionary->getSuggestions(pInfo, traverseSession, xCoordinates, yCoordinates,
                 times, pointerIds, inputCodePoints, inputSize, prevWordCodePoints,
-                prevWordCodePointsLength, commitPoint, givenOptions.isGesture(),
-                givenOptions.useFullEditDistance(), outputCodePoints, scores,
-                spaceIndices, outputTypes);
+                prevWordCodePointsLength, commitPoint, &givenSuggestOptions, outputCodePoints,
+                scores, spaceIndices, outputTypes);
     } else {
         count = dictionary->getBigrams(prevWordCodePoints, prevWordCodePointsLength,
                 inputCodePoints, inputSize, outputCodePoints, scores, outputTypes);
diff --git a/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp b/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp
index dfe3b09..08124ff 100644
--- a/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp
+++ b/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp
@@ -34,12 +34,14 @@
     void *ts = reinterpret_cast<void *>(traverseSession);
     Dictionary *dict = reinterpret_cast<Dictionary *>(dictionary);
     if (!previousWord) {
-        DicTraverseWrapper::initDicTraverseSession(ts, dict, 0, 0);
+        DicTraverseWrapper::initDicTraverseSession(
+                ts, dict, 0 /* prevWord */, 0 /* prevWordLength*/, 0 /* suggestOptions */);
         return;
     }
     int prevWord[previousWordLength];
     env->GetIntArrayRegion(previousWord, 0, previousWordLength, prevWord);
-    DicTraverseWrapper::initDicTraverseSession(ts, dict, prevWord, previousWordLength);
+    DicTraverseWrapper::initDicTraverseSession(
+            ts, dict, prevWord, previousWordLength, 0 /* suggestOptions */);
 }
 
 static void latinime_releaseDicTraverseSession(JNIEnv *env, jclass clazz, jlong traverseSession) {
diff --git a/native/jni/src/bigram_dictionary.cpp b/native/jni/src/bigram_dictionary.cpp
index 9053e72..ebe2799 100644
--- a/native/jni/src/bigram_dictionary.cpp
+++ b/native/jni/src/bigram_dictionary.cpp
@@ -19,11 +19,12 @@
 #define LOG_TAG "LatinIME: bigram_dictionary.cpp"
 
 #include "bigram_dictionary.h"
-#include "binary_format.h"
-#include "bloom_filter.h"
-#include "char_utils.h"
+
 #include "defines.h"
-#include "dictionary.h"
+#include "suggest/core/dictionary/binary_format.h"
+#include "suggest/core/dictionary/bloom_filter.h"
+#include "suggest/core/dictionary/char_utils.h"
+#include "suggest/core/dictionary/dictionary.h"
 
 namespace latinime {
 
@@ -51,7 +52,7 @@
     int insertAt = 0;
     while (insertAt < MAX_RESULTS) {
         if (probability > bigramProbability[insertAt] || (bigramProbability[insertAt] == probability
-                && length < getCodePointCount(MAX_WORD_LENGTH,
+                && length < CharUtils::getCodePointCount(MAX_WORD_LENGTH,
                         bigramCodePoints + insertAt * MAX_WORD_LENGTH))) {
             break;
         }
@@ -195,9 +196,9 @@
     // what user typed.
 
     int maxAlt = MAX_ALTERNATIVES;
-    const int firstBaseLowerCodePoint = toBaseLowerCase(*word);
+    const int firstBaseLowerCodePoint = CharUtils::toBaseLowerCase(*word);
     while (maxAlt > 0) {
-        if (toBaseLowerCase(*inputCodePoints) == firstBaseLowerCodePoint) {
+        if (CharUtils::toBaseLowerCase(*inputCodePoints) == firstBaseLowerCodePoint) {
             return true;
         }
         inputCodePoints++;
diff --git a/native/jni/src/char_utils.h b/native/jni/src/char_utils.h
deleted file mode 100644
index b429f40..0000000
--- a/native/jni/src/char_utils.h
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_CHAR_UTILS_H
-#define LATINIME_CHAR_UTILS_H
-
-#include <cctype>
-
-#include "defines.h"
-
-namespace latinime {
-
-inline static bool isAsciiUpper(int c) {
-    // Note: isupper(...) reports false positives for some Cyrillic characters, causing them to
-    // be incorrectly lower-cased using toAsciiLower(...) rather than latin_tolower(...).
-    return (c >= 'A' && c <= 'Z');
-}
-
-inline static int toAsciiLower(int c) {
-    return c - 'A' + 'a';
-}
-
-inline static bool isAscii(int c) {
-    return isascii(c) != 0;
-}
-
-unsigned short latin_tolower(const unsigned short c);
-
-/**
- * Table mapping most combined Latin, Greek, and Cyrillic characters
- * to their base characters.  If c is in range, BASE_CHARS[c] == c
- * if c is not a combined character, or the base character if it
- * is combined.
- */
-static const int BASE_CHARS_SIZE = 0x0500;
-extern const unsigned short BASE_CHARS[BASE_CHARS_SIZE];
-
-inline static int toBaseCodePoint(int c) {
-    if (c < BASE_CHARS_SIZE) {
-        return static_cast<int>(BASE_CHARS[c]);
-    }
-    return c;
-}
-
-AK_FORCE_INLINE static int toLowerCase(const int c) {
-    if (isAsciiUpper(c)) {
-        return toAsciiLower(c);
-    }
-    if (isAscii(c)) {
-        return c;
-    }
-    return static_cast<int>(latin_tolower(static_cast<unsigned short>(c)));
-}
-
-AK_FORCE_INLINE static int toBaseLowerCase(const int c) {
-    return toLowerCase(toBaseCodePoint(c));
-}
-
-inline static bool isIntentionalOmissionCodePoint(const int codePoint) {
-    // TODO: Do not hardcode here
-    return codePoint == KEYCODE_SINGLE_QUOTE || codePoint == KEYCODE_HYPHEN_MINUS;
-}
-
-inline static int getCodePointCount(const int arraySize, const int *const codePoints) {
-    int size = 0;
-    for (; size < arraySize; ++size) {
-        if (codePoints[size] == '\0') {
-            break;
-        }
-    }
-    return size;
-}
-
-} // namespace latinime
-#endif // LATINIME_CHAR_UTILS_H
diff --git a/native/jni/src/correction.cpp b/native/jni/src/correction.cpp
index 61bf3f6..3dc2f37 100644
--- a/native/jni/src/correction.cpp
+++ b/native/jni/src/correction.cpp
@@ -18,11 +18,11 @@
 
 #include <cmath>
 
-#include "char_utils.h"
 #include "correction.h"
 #include "defines.h"
-#include "proximity_info_state.h"
-#include "suggest_utils.h"
+#include "suggest/core/dictionary/char_utils.h"
+#include "suggest/core/layout/proximity_info_state.h"
+#include "suggest/core/layout/touch_position_correction_utils.h"
 #include "suggest/policyimpl/utils/edit_distance.h"
 #include "suggest/policyimpl/utils/damerau_levenshtein_edit_distance_policy.h"
 
@@ -528,7 +528,7 @@
 }
 
 inline static bool isUpperCase(unsigned short c) {
-    return isAsciiUpper(toBaseCodePoint(c));
+    return CharUtils::isAsciiUpper(CharUtils::toBaseCodePoint(c));
 }
 
 //////////////////////
@@ -676,8 +676,8 @@
             if (i < adjustedProximityMatchedCount) {
                 multiplyIntCapped(typedLetterMultiplier, &finalFreq);
             }
-            const float factor =
-                    SuggestUtils::getLengthScalingFactor(static_cast<float>(squaredDistance));
+            const float factor = TouchPositionCorrectionUtils::getLengthScalingFactor(
+                    static_cast<float>(squaredDistance));
             if (factor > 0.0f) {
                 multiplyRate(static_cast<int>(factor * 100.0f), &finalFreq);
             } else if (squaredDistance == PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO) {
diff --git a/native/jni/src/correction.h b/native/jni/src/correction.h
index a9e9b48..3f60d48 100644
--- a/native/jni/src/correction.h
+++ b/native/jni/src/correction.h
@@ -21,7 +21,8 @@
 
 #include "correction_state.h"
 #include "defines.h"
-#include "proximity_info_state.h"
+#include "suggest/core/dictionary/char_utils.h"
+#include "suggest/core/layout/proximity_info_state.h"
 
 namespace latinime {
 
@@ -342,13 +343,13 @@
     const int *const prevprev =
             outputLength >= 2 ? editDistanceTable + (outputLength - 2) * (inputSize + 1) : 0;
     current[0] = outputLength;
-    const int co = toBaseLowerCase(output[outputLength - 1]);
-    const int prevCO = outputLength >= 2 ? toBaseLowerCase(output[outputLength - 2]) : 0;
+    const int co = CharUtils::toBaseLowerCase(output[outputLength - 1]);
+    const int prevCO = outputLength >= 2 ? CharUtils::toBaseLowerCase(output[outputLength - 2]) : 0;
     for (int i = 1; i <= inputSize; ++i) {
-        const int ci = toBaseLowerCase(input[i - 1]);
+        const int ci = CharUtils::toBaseLowerCase(input[i - 1]);
         const int cost = (ci == co) ? 0 : 1;
         current[i] = min(current[i - 1] + 1, min(prev[i] + 1, prev[i - 1] + cost));
-        if (i >= 2 && prevprev && ci == prevCO && co == toBaseLowerCase(input[i - 2])) {
+        if (i >= 2 && prevprev && ci == prevCO && co == CharUtils::toBaseLowerCase(input[i - 2])) {
             current[i] = min(current[i], prevprev[i - 2] + 1);
         }
     }
diff --git a/native/jni/src/dic_traverse_wrapper.cpp b/native/jni/src/dic_traverse_wrapper.cpp
index 88ca9fa..ec8c62d 100644
--- a/native/jni/src/dic_traverse_wrapper.cpp
+++ b/native/jni/src/dic_traverse_wrapper.cpp
@@ -22,5 +22,5 @@
 void *(*DicTraverseWrapper::sDicTraverseSessionFactoryMethod)(JNIEnv *, jstring) = 0;
 void (*DicTraverseWrapper::sDicTraverseSessionReleaseMethod)(void *) = 0;
 void (*DicTraverseWrapper::sDicTraverseSessionInitMethod)(
-        void *, const Dictionary *const, const int *, const int) = 0;
+        void *, const Dictionary *const, const int *, const int, const SuggestOptions *const) = 0;
 } // namespace latinime
diff --git a/native/jni/src/dic_traverse_wrapper.h b/native/jni/src/dic_traverse_wrapper.h
index 1108a45..43b4c9a 100644
--- a/native/jni/src/dic_traverse_wrapper.h
+++ b/native/jni/src/dic_traverse_wrapper.h
@@ -22,6 +22,7 @@
 
 namespace latinime {
 class Dictionary;
+class SuggestOptions;
 // TODO: Remove
 class DicTraverseWrapper {
  public:
@@ -32,9 +33,11 @@
         return 0;
     }
     static void initDicTraverseSession(void *traverseSession, const Dictionary *const dictionary,
-            const int *prevWord, const int prevWordLength) {
+            const int *prevWord, const int prevWordLength,
+            const SuggestOptions *const suggestOptions) {
         if (sDicTraverseSessionInitMethod) {
-            sDicTraverseSessionInitMethod(traverseSession, dictionary, prevWord, prevWordLength);
+            sDicTraverseSessionInitMethod(
+                    traverseSession, dictionary, prevWord, prevWordLength, suggestOptions);
         }
     }
     static void releaseDicTraverseSession(void *traverseSession) {
@@ -46,7 +49,8 @@
         sDicTraverseSessionFactoryMethod = factoryMethod;
     }
     static void setTraverseSessionInitMethod(
-            void (*initMethod)(void *, const Dictionary *const, const int *, const int)) {
+            void (*initMethod)(void *, const Dictionary *const, const int *, const int,
+                    const SuggestOptions *const)) {
         sDicTraverseSessionInitMethod = initMethod;
     }
     static void setTraverseSessionReleaseMethod(void (*releaseMethod)(void *)) {
@@ -57,7 +61,7 @@
     DISALLOW_IMPLICIT_CONSTRUCTORS(DicTraverseWrapper);
     static void *(*sDicTraverseSessionFactoryMethod)(JNIEnv *, jstring);
     static void (*sDicTraverseSessionInitMethod)(
-            void *, const Dictionary *const, const int *, const int);
+            void *, const Dictionary *const, const int *, const int, const SuggestOptions *const);
     static void (*sDicTraverseSessionReleaseMethod)(void *);
 };
 } // namespace latinime
diff --git a/native/jni/src/geometry_utils.h b/native/jni/src/geometry_utils.h
deleted file mode 100644
index 4cbb127..0000000
--- a/native/jni/src/geometry_utils.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_GEOMETRY_UTILS_H
-#define LATINIME_GEOMETRY_UTILS_H
-
-#include <cmath>
-
-#include "defines.h"
-
-#define ROUND_FLOAT_10000(f) ((f) < 1000.0f && (f) > 0.001f) \
-        ? (floorf((f) * 10000.0f) / 10000.0f) : (f)
-
-namespace latinime {
-
-static inline float SQUARE_FLOAT(const float x) { return x * x; }
-
-static AK_FORCE_INLINE float getAngle(const int x1, const int y1, const int x2, const int y2) {
-    const int dx = x1 - x2;
-    const int dy = y1 - y2;
-    if (dx == 0 && dy == 0) return 0.0f;
-    return atan2f(static_cast<float>(dy), static_cast<float>(dx));
-}
-
-static AK_FORCE_INLINE float getAngleDiff(const float a1, const float a2) {
-    const float deltaA = fabsf(a1 - a2);
-    const float diff = ROUND_FLOAT_10000(deltaA);
-    if (diff > M_PI_F) {
-        const float normalizedDiff = 2.0f * M_PI_F - diff;
-        return ROUND_FLOAT_10000(normalizedDiff);
-    }
-    return diff;
-}
-
-static AK_FORCE_INLINE int getDistanceInt(const int x1, const int y1, const int x2,
-        const int y2) {
-    return static_cast<int>(hypotf(static_cast<float>(x1 - x2), static_cast<float>(y1 - y2)));
-}
-} // namespace latinime
-#endif // LATINIME_GEOMETRY_UTILS_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_node.cpp b/native/jni/src/suggest/core/dicnode/dic_node.cpp
index 8c48c58..de088c7 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_node.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "dic_node.h"
+#include "suggest/core/dicnode/dic_node.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/suggest/core/dicnode/dic_node.h b/native/jni/src/suggest/core/dicnode/dic_node.h
index 4225bb3..14bd2d5 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node.h
@@ -17,13 +17,13 @@
 #ifndef LATINIME_DIC_NODE_H
 #define LATINIME_DIC_NODE_H
 
-#include "char_utils.h"
 #include "defines.h"
-#include "dic_node_state.h"
-#include "dic_node_profiler.h"
-#include "dic_node_properties.h"
-#include "dic_node_release_listener.h"
-#include "digraph_utils.h"
+#include "suggest/core/dicnode/dic_node_state.h"
+#include "suggest/core/dicnode/dic_node_profiler.h"
+#include "suggest/core/dicnode/dic_node_properties.h"
+#include "suggest/core/dicnode/dic_node_release_listener.h"
+#include "suggest/core/dictionary/char_utils.h"
+#include "suggest/core/dictionary/digraph_utils.h"
 
 #if DEBUG_DICT
 #define LOGI_SHOW_ADD_COST_PROP \
@@ -221,7 +221,7 @@
 
     bool isFirstCharUppercase() const {
         const int c = getOutputWordBuf()[0];
-        return isAsciiUpper(c);
+        return CharUtils::isAsciiUpper(c);
     }
 
     bool isFirstWord() const {
@@ -375,7 +375,7 @@
     // Whether the current codepoint can be an intentional omission, in which case the traversal
     // algorithm will always check for a possible omission here.
     bool canBeIntentionalOmission() const {
-        return isIntentionalOmissionCodePoint(getNodeCodePoint());
+        return CharUtils::isIntentionalOmissionCodePoint(getNodeCodePoint());
     }
 
     // Whether the omission is so frequent that it should incur zero cost.
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h b/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
index d3f28a8..970e3bd 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
@@ -21,8 +21,8 @@
 #include <vector>
 
 #include "defines.h"
-#include "dic_node.h"
-#include "dic_node_release_listener.h"
+#include "suggest/core/dicnode/dic_node.h"
+#include "suggest/core/dicnode/dic_node_release_listener.h"
 
 #define MAX_DIC_NODE_PRIORITY_QUEUE_CAPACITY 200
 
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_properties.h b/native/jni/src/suggest/core/dicnode/dic_node_properties.h
index 63a6b13..d2f87c1 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_properties.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_properties.h
@@ -19,8 +19,8 @@
 
 #include <stdint.h>
 
-#include "binary_format.h"
 #include "defines.h"
+#include "suggest/core/dictionary/binary_format.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_state.h b/native/jni/src/suggest/core/dicnode/dic_node_state.h
index 239b63c..d35e7d7 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_state.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_state.h
@@ -18,10 +18,10 @@
 #define LATINIME_DIC_NODE_STATE_H
 
 #include "defines.h"
-#include "dic_node_state_input.h"
-#include "dic_node_state_output.h"
-#include "dic_node_state_prevword.h"
-#include "dic_node_state_scoring.h"
+#include "suggest/core/dicnode/dic_node_state_input.h"
+#include "suggest/core/dicnode/dic_node_state_output.h"
+#include "suggest/core/dicnode/dic_node_state_prevword.h"
+#include "suggest/core/dicnode/dic_node_state_scoring.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_state_prevword.h b/native/jni/src/suggest/core/dicnode/dic_node_state_prevword.h
index e3b892b..c3968c0 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_state_prevword.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_state_prevword.h
@@ -21,7 +21,7 @@
 #include <stdint.h>
 
 #include "defines.h"
-#include "dic_node_utils.h"
+#include "suggest/core/dicnode/dic_node_utils.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_state_scoring.h b/native/jni/src/suggest/core/dicnode/dic_node_state_scoring.h
index dca9d60..4c88422 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_state_scoring.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_state_scoring.h
@@ -20,7 +20,7 @@
 #include <stdint.h>
 
 #include "defines.h"
-#include "digraph_utils.h"
+#include "suggest/core/dictionary/digraph_utils.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
index 5357c37..c754a5e 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
@@ -17,13 +17,14 @@
 #include <cstring>
 #include <vector>
 
-#include "binary_format.h"
-#include "dic_node.h"
-#include "dic_node_utils.h"
-#include "dic_node_vector.h"
-#include "multi_bigram_map.h"
-#include "proximity_info.h"
-#include "proximity_info_state.h"
+#include "suggest/core/dicnode/dic_node.h"
+#include "suggest/core/dicnode/dic_node_utils.h"
+#include "suggest/core/dicnode/dic_node_vector.h"
+#include "suggest/core/dictionary/binary_format.h"
+#include "suggest/core/dictionary/char_utils.h"
+#include "suggest/core/dictionary/multi_bigram_map.h"
+#include "suggest/core/layout/proximity_info.h"
+#include "suggest/core/layout/proximity_info_state.h"
 
 namespace latinime {
 
@@ -62,9 +63,9 @@
         DicNodeVector *childDicNodes) {
     // Passing multiple chars node. No need to traverse child
     const int codePoint = dicNode->getNodeTypedCodePoint();
-    const int baseLowerCaseCodePoint = toBaseLowerCase(codePoint);
+    const int baseLowerCaseCodePoint = CharUtils::toBaseLowerCase(codePoint);
     const bool isMatch = isMatchedNodeCodePoint(pInfoState, pointIndex, exactOnly, codePoint);
-    if (isMatch || isIntentionalOmissionCodePoint(baseLowerCaseCodePoint)) {
+    if (isMatch || CharUtils::isIntentionalOmissionCodePoint(baseLowerCaseCodePoint)) {
         childDicNodes->pushPassingChild(dicNode);
     }
 }
@@ -125,13 +126,13 @@
         return false;
     }
     if (pInfo && (pInfo->getKeyIndexOf(nodeCodePoint) == NOT_AN_INDEX
-            || isIntentionalOmissionCodePoint(nodeCodePoint))) {
+            || CharUtils::isIntentionalOmissionCodePoint(nodeCodePoint))) {
         // If normalized nodeCodePoint is not on the keyboard or skippable, this child is never
         // filtered.
         return false;
     }
-    const int lowerCodePoint = toLowerCase(nodeCodePoint);
-    const int baseLowerCodePoint = toBaseCodePoint(lowerCodePoint);
+    const int lowerCodePoint = CharUtils::toLowerCase(nodeCodePoint);
+    const int baseLowerCodePoint = CharUtils::toBaseCodePoint(lowerCodePoint);
     // TODO: Avoid linear search
     for (int i = 0; i < filterSize; ++i) {
         // Checking if a normalized code point is in filter characters when pInfo is not
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_vector.h b/native/jni/src/suggest/core/dicnode/dic_node_vector.h
index ca07eda..e23c411 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_vector.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_vector.h
@@ -20,7 +20,7 @@
 #include <vector>
 
 #include "defines.h"
-#include "dic_node.h"
+#include "suggest/core/dicnode/dic_node.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.cpp b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.cpp
index b9a6078..c3d2a2e 100644
--- a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.cpp
@@ -17,9 +17,9 @@
 #include <list>
 
 #include "defines.h"
-#include "dic_node_priority_queue.h"
-#include "dic_node_utils.h"
-#include "dic_nodes_cache.h"
+#include "suggest/core/dicnode/dic_node_priority_queue.h"
+#include "suggest/core/dicnode/dic_node_utils.h"
+#include "suggest/core/dicnode/dic_nodes_cache.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
index a62aa42..7f5bdbc 100644
--- a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
+++ b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
@@ -20,7 +20,7 @@
 #include <stdint.h>
 
 #include "defines.h"
-#include "dic_node_priority_queue.h"
+#include "suggest/core/dicnode/dic_node_priority_queue.h"
 
 #define INITIAL_QUEUE_ID_ACTIVE 0
 #define INITIAL_QUEUE_ID_NEXT_ACTIVE 1
diff --git a/native/jni/src/binary_format.h b/native/jni/src/suggest/core/dictionary/binary_format.h
similarity index 99%
rename from native/jni/src/binary_format.h
rename to native/jni/src/suggest/core/dictionary/binary_format.h
index 9824153..ef9fd37 100644
--- a/native/jni/src/binary_format.h
+++ b/native/jni/src/suggest/core/dictionary/binary_format.h
@@ -21,9 +21,9 @@
 #include <map>
 #include <stdint.h>
 
-#include "bloom_filter.h"
-#include "char_utils.h"
 #include "hash_map_compat.h"
+#include "suggest/core/dictionary/bloom_filter.h"
+#include "suggest/core/dictionary/char_utils.h"
 
 namespace latinime {
 
@@ -473,7 +473,8 @@
         // there was no match (or we would have found it).
         if (wordPos >= length) return NOT_VALID_WORD;
         int charGroupCount = BinaryFormat::getGroupCountAndForwardPointer(root, &pos);
-        const int wChar = forceLowerCaseSearch ? toLowerCase(inWord[wordPos]) : inWord[wordPos];
+        const int wChar = forceLowerCaseSearch
+                ? CharUtils::toLowerCase(inWord[wordPos]) : inWord[wordPos];
         while (true) {
             // If there are no more character groups in this node, it means we could not
             // find a matching character for this depth, therefore there is no match.
diff --git a/native/jni/src/bloom_filter.h b/native/jni/src/suggest/core/dictionary/bloom_filter.h
similarity index 100%
rename from native/jni/src/bloom_filter.h
rename to native/jni/src/suggest/core/dictionary/bloom_filter.h
diff --git a/native/jni/src/char_utils.cpp b/native/jni/src/suggest/core/dictionary/char_utils.cpp
similarity index 98%
rename from native/jni/src/char_utils.cpp
rename to native/jni/src/suggest/core/dictionary/char_utils.cpp
index e219beb..8d40e54 100644
--- a/native/jni/src/char_utils.cpp
+++ b/native/jni/src/suggest/core/dictionary/char_utils.cpp
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
+#include "suggest/core/dictionary/char_utils.h"
+
 #include <cstdlib>
 
-#include "char_utils.h"
 #include "defines.h"
 
 namespace latinime {
@@ -36,8 +37,7 @@
  *    $ apt-get install libicu-dev
  *
  * 3. Build the following code
- *    (You need this file, char_utils.h, and defines.h)
- *    $ g++ -o char_utils -DUPDATING_CHAR_UTILS char_utils.cpp -licuuc
+ *    $ g++ -o char_utils -I../../.. -DUPDATING_CHAR_UTILS char_utils.cpp -licuuc
  */
 #ifdef UPDATING_CHAR_UTILS
 #include <stdio.h>
@@ -47,7 +47,7 @@
     for (unsigned short c = 0; c < 0xFFFF; c++) {
         if (c <= 0x7F) continue;
         const unsigned short icu4cLowerC = u_tolower(c);
-        const unsigned short myLowerC = latin_tolower(c);
+        const unsigned short myLowerC = CharUtils::latin_tolower(c);
         if (c != icu4cLowerC) {
 #ifdef CONFIRMING_CHAR_UTILS
             if (icu4cLowerC != myLowerC) {
@@ -70,7 +70,8 @@
  *
  * 5. Update the SORTED_CHAR_MAP[] array below with the output above.
  *    Then, rebuild with -DCONFIRMING_CHAR_UTILS and confirm the program exits successfully.
- *    $ g++ -o char_utils -DUPDATING_CHAR_UTILS -DCONFIRMING_CHAR_UTILS char_utils.cpp -licuuc
+ *    $ g++ -o char_utils -I../../.. -DUPDATING_CHAR_UTILS -DCONFIRMING_CHAR_UTILS char_utils.cpp \
+ *      -licuuc
  *    $ ./char_utils
  *    $
  */
@@ -1054,7 +1055,7 @@
             - static_cast<int>((static_cast<const struct LatinCapitalSmallPair *>(b))->capital);
 }
 
-unsigned short latin_tolower(const unsigned short c) {
+/* static */ unsigned short CharUtils::latin_tolower(const unsigned short c) {
     struct LatinCapitalSmallPair *p =
             static_cast<struct LatinCapitalSmallPair *>(bsearch(&c, SORTED_CHAR_MAP,
                     NELEMS(SORTED_CHAR_MAP), sizeof(SORTED_CHAR_MAP[0]), compare_pair_capital));
@@ -1063,7 +1064,7 @@
 
 /*
  * Table mapping most combined Latin, Greek, and Cyrillic characters
- * to their base characters.  If c is in range, BASE_CHARS[c] == c
+ * to their base characters.  If c is in range, CharUtils::BASE_CHARS[c] == c
  * if c is not a combined character, or the base character if it
  * is combined.
  *
@@ -1074,7 +1075,7 @@
  *   for ($j = $i; $j < $i + 8; $j++) { \
  *   printf("0x%04X, ", $base[$j] ? $base[$j] : $j)}; print "\n"; }'
  */
-const unsigned short BASE_CHARS[BASE_CHARS_SIZE] = {
+/* static */ const unsigned short CharUtils::BASE_CHARS[CharUtils::BASE_CHARS_SIZE] = {
     /* U+0000 */ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
     /* U+0008 */ 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
     /* U+0010 */ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
diff --git a/native/jni/src/suggest/core/dictionary/char_utils.h b/native/jni/src/suggest/core/dictionary/char_utils.h
new file mode 100644
index 0000000..2e735a8
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/char_utils.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_CHAR_UTILS_H
+#define LATINIME_CHAR_UTILS_H
+
+#include <cctype>
+
+#include "defines.h"
+
+namespace latinime {
+
+class CharUtils {
+ public:
+    static AK_FORCE_INLINE bool isAsciiUpper(int c) {
+        // Note: isupper(...) reports false positives for some Cyrillic characters, causing them to
+        // be incorrectly lower-cased using toAsciiLower(...) rather than latin_tolower(...).
+        return (c >= 'A' && c <= 'Z');
+    }
+
+    static AK_FORCE_INLINE int toAsciiLower(int c) {
+        return c - 'A' + 'a';
+    }
+
+    static AK_FORCE_INLINE bool isAscii(int c) {
+        return isascii(c) != 0;
+    }
+
+    static AK_FORCE_INLINE int toLowerCase(const int c) {
+        if (isAsciiUpper(c)) {
+            return toAsciiLower(c);
+        }
+        if (isAscii(c)) {
+            return c;
+        }
+        return static_cast<int>(latin_tolower(static_cast<unsigned short>(c)));
+    }
+
+    static AK_FORCE_INLINE int toBaseLowerCase(const int c) {
+        return toLowerCase(toBaseCodePoint(c));
+    }
+
+    static AK_FORCE_INLINE bool isIntentionalOmissionCodePoint(const int codePoint) {
+        // TODO: Do not hardcode here
+        return codePoint == KEYCODE_SINGLE_QUOTE || codePoint == KEYCODE_HYPHEN_MINUS;
+    }
+
+    static AK_FORCE_INLINE int getCodePointCount(const int arraySize, const int *const codePoints) {
+        int size = 0;
+        for (; size < arraySize; ++size) {
+            if (codePoints[size] == '\0') {
+                break;
+            }
+        }
+        return size;
+    }
+
+    static AK_FORCE_INLINE int toBaseCodePoint(int c) {
+        if (c < BASE_CHARS_SIZE) {
+            return static_cast<int>(BASE_CHARS[c]);
+        }
+        return c;
+    }
+
+    static unsigned short latin_tolower(const unsigned short c);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(CharUtils);
+
+    /**
+     * Table mapping most combined Latin, Greek, and Cyrillic characters
+     * to their base characters.  If c is in range, BASE_CHARS[c] == c
+     * if c is not a combined character, or the base character if it
+     * is combined.
+     */
+    static const int BASE_CHARS_SIZE = 0x0500;
+    static const unsigned short BASE_CHARS[BASE_CHARS_SIZE];
+};
+} // namespace latinime
+#endif // LATINIME_CHAR_UTILS_H
diff --git a/native/jni/src/dictionary.cpp b/native/jni/src/suggest/core/dictionary/dictionary.cpp
similarity index 89%
rename from native/jni/src/dictionary.cpp
rename to native/jni/src/suggest/core/dictionary/dictionary.cpp
index dadb2ba..1939c74 100644
--- a/native/jni/src/dictionary.cpp
+++ b/native/jni/src/suggest/core/dictionary/dictionary.cpp
@@ -16,16 +16,17 @@
 
 #define LOG_TAG "LatinIME: dictionary.cpp"
 
-#include "dictionary.h"
+#include "suggest/core/dictionary/dictionary.h"
 
 #include <map> // TODO: remove
 #include <stdint.h>
 
 #include "bigram_dictionary.h"
-#include "binary_format.h"
 #include "defines.h"
 #include "dic_traverse_wrapper.h"
+#include "suggest_options.h"
 #include "suggest/core/suggest.h"
+#include "suggest/core/dictionary/binary_format.h"
 #include "suggest/policyimpl/gesture/gesture_suggest_policy_factory.h"
 #include "suggest/policyimpl/typing/typing_suggest_policy_factory.h"
 #include "unigram_dictionary.h"
@@ -53,13 +54,13 @@
 
 int Dictionary::getSuggestions(ProximityInfo *proximityInfo, void *traverseSession,
         int *xcoordinates, int *ycoordinates, int *times, int *pointerIds, int *inputCodePoints,
-        int inputSize, int *prevWordCodePoints, int prevWordLength, int commitPoint, bool isGesture,
-        bool useFullEditDistance, int *outWords, int *frequencies, int *spaceIndices,
-        int *outputTypes) const {
+        int inputSize, int *prevWordCodePoints, int prevWordLength, int commitPoint,
+        const SuggestOptions *const suggestOptions, int *outWords, int *frequencies,
+        int *spaceIndices, int *outputTypes) const {
     int result = 0;
-    if (isGesture) {
+    if (suggestOptions->isGesture()) {
         DicTraverseWrapper::initDicTraverseSession(
-                traverseSession, this, prevWordCodePoints, prevWordLength);
+                traverseSession, this, prevWordCodePoints, prevWordLength, suggestOptions);
         result = mGestureSuggest->getSuggestions(proximityInfo, traverseSession, xcoordinates,
                 ycoordinates, times, pointerIds, inputCodePoints, inputSize, commitPoint, outWords,
                 frequencies, spaceIndices, outputTypes);
@@ -70,7 +71,7 @@
     } else {
         if (USE_SUGGEST_INTERFACE_FOR_TYPING) {
             DicTraverseWrapper::initDicTraverseSession(
-                    traverseSession, this, prevWordCodePoints, prevWordLength);
+                    traverseSession, this, prevWordCodePoints, prevWordLength, suggestOptions);
             result = mTypingSuggest->getSuggestions(proximityInfo, traverseSession, xcoordinates,
                     ycoordinates, times, pointerIds, inputCodePoints, inputSize, commitPoint,
                     outWords, frequencies, spaceIndices, outputTypes);
@@ -84,8 +85,8 @@
             mBigramDictionary->fillBigramAddressToProbabilityMapAndFilter(prevWordCodePoints,
                     prevWordLength, &bigramMap, bigramFilter);
             result = mUnigramDictionary->getSuggestions(proximityInfo, xcoordinates, ycoordinates,
-                    inputCodePoints, inputSize, &bigramMap, bigramFilter, useFullEditDistance,
-                    outWords, frequencies, outputTypes);
+                    inputCodePoints, inputSize, &bigramMap, bigramFilter,
+                    suggestOptions->useFullEditDistance(), outWords, frequencies, outputTypes);
             return result;
         }
     }
diff --git a/native/jni/src/dictionary.h b/native/jni/src/suggest/core/dictionary/dictionary.h
similarity index 95%
rename from native/jni/src/dictionary.h
rename to native/jni/src/suggest/core/dictionary/dictionary.h
index edec83f..e6861a3 100644
--- a/native/jni/src/dictionary.h
+++ b/native/jni/src/suggest/core/dictionary/dictionary.h
@@ -26,6 +26,7 @@
 class BigramDictionary;
 class ProximityInfo;
 class SuggestInterface;
+class SuggestOptions;
 class UnigramDictionary;
 
 class Dictionary {
@@ -54,9 +55,9 @@
 
     int getSuggestions(ProximityInfo *proximityInfo, void *traverseSession, int *xcoordinates,
             int *ycoordinates, int *times, int *pointerIds, int *inputCodePoints, int inputSize,
-            int *prevWordCodePoints, int prevWordLength, int commitPoint, bool isGesture,
-            bool useFullEditDistance, int *outWords, int *frequencies, int *spaceIndices,
-            int *outputTypes) const;
+            int *prevWordCodePoints, int prevWordLength, int commitPoint,
+            const SuggestOptions *const suggestOptions, int *outWords, int *frequencies,
+            int *spaceIndices, int *outputTypes) const;
 
     int getBigrams(const int *word, int length, int *inputCodePoints, int inputSize, int *outWords,
             int *frequencies, int *outputTypes) const;
diff --git a/native/jni/src/digraph_utils.cpp b/native/jni/src/suggest/core/dictionary/digraph_utils.cpp
similarity index 95%
rename from native/jni/src/digraph_utils.cpp
rename to native/jni/src/suggest/core/dictionary/digraph_utils.cpp
index 0834426..e8cdd53 100644
--- a/native/jni/src/digraph_utils.cpp
+++ b/native/jni/src/suggest/core/dictionary/digraph_utils.cpp
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-#include "char_utils.h"
-#include "binary_format.h"
+#include "suggest/core/dictionary/digraph_utils.h"
+
 #include "defines.h"
-#include "digraph_utils.h"
+#include "suggest/core/dictionary/binary_format.h"
+#include "suggest/core/dictionary/char_utils.h"
 
 namespace latinime {
 
@@ -121,7 +122,7 @@
 /* static */ const DigraphUtils::digraph_t *DigraphUtils::getDigraphForDigraphTypeAndCodePoint(
         const DigraphUtils::DigraphType digraphType, const int compositeGlyphCodePoint) {
     const DigraphUtils::digraph_t *digraphs = 0;
-    const int compositeGlyphLowerCodePoint = toLowerCase(compositeGlyphCodePoint);
+    const int compositeGlyphLowerCodePoint = CharUtils::toLowerCase(compositeGlyphCodePoint);
     const int digraphsSize =
             DigraphUtils::getAllDigraphsForDictionaryAndReturnSize(digraphType, &digraphs);
     for (int i = 0; i < digraphsSize; i++) {
diff --git a/native/jni/src/digraph_utils.h b/native/jni/src/suggest/core/dictionary/digraph_utils.h
similarity index 98%
rename from native/jni/src/digraph_utils.h
rename to native/jni/src/suggest/core/dictionary/digraph_utils.h
index 9443522..c120594 100644
--- a/native/jni/src/digraph_utils.h
+++ b/native/jni/src/suggest/core/dictionary/digraph_utils.h
@@ -17,6 +17,8 @@
 #ifndef DIGRAPH_UTILS_H
 #define DIGRAPH_UTILS_H
 
+#include "defines.h"
+
 namespace latinime {
 
 class DigraphUtils {
diff --git a/native/jni/src/multi_bigram_map.h b/native/jni/src/suggest/core/dictionary/multi_bigram_map.h
similarity index 98%
rename from native/jni/src/multi_bigram_map.h
rename to native/jni/src/suggest/core/dictionary/multi_bigram_map.h
index 7e1b630..fcac98f 100644
--- a/native/jni/src/multi_bigram_map.h
+++ b/native/jni/src/suggest/core/dictionary/multi_bigram_map.h
@@ -17,12 +17,11 @@
 #ifndef LATINIME_MULTI_BIGRAM_MAP_H
 #define LATINIME_MULTI_BIGRAM_MAP_H
 
-#include <cstring>
 #include <stdint.h>
 
 #include "defines.h"
-#include "binary_format.h"
 #include "hash_map_compat.h"
+#include "suggest/core/dictionary/binary_format.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/suggest/core/dictionary/shortcut_utils.h b/native/jni/src/suggest/core/dictionary/shortcut_utils.h
index c411408..601ac5f 100644
--- a/native/jni/src/suggest/core/dictionary/shortcut_utils.h
+++ b/native/jni/src/suggest/core/dictionary/shortcut_utils.h
@@ -19,7 +19,7 @@
 
 #include "defines.h"
 #include "suggest/core/dicnode/dic_node_utils.h"
-#include "terminal_attributes.h"
+#include "suggest/core/dictionary/terminal_attributes.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/terminal_attributes.h b/native/jni/src/suggest/core/dictionary/terminal_attributes.h
similarity index 97%
rename from native/jni/src/terminal_attributes.h
rename to native/jni/src/suggest/core/dictionary/terminal_attributes.h
index 92ef71c..8377c60 100644
--- a/native/jni/src/terminal_attributes.h
+++ b/native/jni/src/suggest/core/dictionary/terminal_attributes.h
@@ -18,7 +18,8 @@
 #define LATINIME_TERMINAL_ATTRIBUTES_H
 
 #include <stdint.h>
-#include "binary_format.h"
+
+#include "suggest/core/dictionary/binary_format.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/additional_proximity_chars.cpp b/native/jni/src/suggest/core/layout/additional_proximity_chars.cpp
similarity index 95%
rename from native/jni/src/additional_proximity_chars.cpp
rename to native/jni/src/suggest/core/layout/additional_proximity_chars.cpp
index 661c50e..34b8b37 100644
--- a/native/jni/src/additional_proximity_chars.cpp
+++ b/native/jni/src/suggest/core/layout/additional_proximity_chars.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "additional_proximity_chars.h"
+#include "suggest/core/layout/additional_proximity_chars.h"
 
 namespace latinime {
 // TODO: Stop using hardcoded additional proximity characters.
diff --git a/native/jni/src/additional_proximity_chars.h b/native/jni/src/suggest/core/layout/additional_proximity_chars.h
similarity index 100%
rename from native/jni/src/additional_proximity_chars.h
rename to native/jni/src/suggest/core/layout/additional_proximity_chars.h
diff --git a/native/jni/src/suggest/core/layout/geometry_utils.h b/native/jni/src/suggest/core/layout/geometry_utils.h
new file mode 100644
index 0000000..b667df6
--- /dev/null
+++ b/native/jni/src/suggest/core/layout/geometry_utils.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_GEOMETRY_UTILS_H
+#define LATINIME_GEOMETRY_UTILS_H
+
+#include <cmath>
+
+#include "defines.h"
+
+#define ROUND_FLOAT_10000(f) ((f) < 1000.0f && (f) > 0.001f) \
+        ? (floorf((f) * 10000.0f) / 10000.0f) : (f)
+
+namespace latinime {
+
+class GeometryUtils {
+ public:
+    static inline float SQUARE_FLOAT(const float x) { return x * x; }
+
+    static AK_FORCE_INLINE float getAngle(const int x1, const int y1, const int x2, const int y2) {
+        const int dx = x1 - x2;
+        const int dy = y1 - y2;
+        if (dx == 0 && dy == 0) return 0.0f;
+        return atan2f(static_cast<float>(dy), static_cast<float>(dx));
+    }
+
+    static AK_FORCE_INLINE float getAngleDiff(const float a1, const float a2) {
+        const float deltaA = fabsf(a1 - a2);
+        const float diff = ROUND_FLOAT_10000(deltaA);
+        if (diff > M_PI_F) {
+            const float normalizedDiff = 2.0f * M_PI_F - diff;
+            return ROUND_FLOAT_10000(normalizedDiff);
+        }
+        return diff;
+    }
+
+    static AK_FORCE_INLINE int getDistanceInt(const int x1, const int y1, const int x2,
+            const int y2) {
+        return static_cast<int>(hypotf(static_cast<float>(x1 - x2), static_cast<float>(y1 - y2)));
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(GeometryUtils);
+};
+} // namespace latinime
+#endif // LATINIME_GEOMETRY_UTILS_H
diff --git a/native/jni/src/proximity_info.cpp b/native/jni/src/suggest/core/layout/proximity_info.cpp
similarity index 93%
rename from native/jni/src/proximity_info.cpp
rename to native/jni/src/suggest/core/layout/proximity_info.cpp
index 88d670d..0b5d71a 100644
--- a/native/jni/src/proximity_info.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info.cpp
@@ -14,18 +14,19 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "LatinIME: proximity_info.cpp"
+
+#include "suggest/core/layout/proximity_info.h"
+
 #include <cstring>
 #include <cmath>
 
-#define LOG_TAG "LatinIME: proximity_info.cpp"
-
-#include "additional_proximity_chars.h"
-#include "char_utils.h"
 #include "defines.h"
-#include "geometry_utils.h"
 #include "jni.h"
-#include "proximity_info.h"
-#include "proximity_info_params.h"
+#include "suggest/core/dictionary/char_utils.h"
+#include "suggest/core/layout/additional_proximity_chars.h"
+#include "suggest/core/layout/geometry_utils.h"
+#include "suggest/core/layout/proximity_info_params.h"
 
 namespace latinime {
 
@@ -58,7 +59,7 @@
           MOST_COMMON_KEY_WIDTH_SQUARE(mostCommonKeyWidth * mostCommonKeyWidth),
           MOST_COMMON_KEY_HEIGHT(mostCommonKeyHeight),
           NORMALIZED_SQUARED_MOST_COMMON_KEY_HYPOTENUSE(1.0f +
-                  SQUARE_FLOAT(static_cast<float>(mostCommonKeyHeight) /
+                  GeometryUtils::SQUARE_FLOAT(static_cast<float>(mostCommonKeyHeight) /
                           static_cast<float>(mostCommonKeyWidth))),
           CELL_WIDTH((keyboardWidth + gridWidth - 1) / gridWidth),
           CELL_HEIGHT((keyboardHeight + gridHeight - 1) / gridHeight),
@@ -150,7 +151,7 @@
     const float touchY = static_cast<float>(y);
     const float keyWidth = static_cast<float>(getMostCommonKeyWidth());
     return ProximityInfoUtils::getSquaredDistanceFloat(centerX, centerY, touchX, touchY)
-            / SQUARE_FLOAT(keyWidth);
+            / GeometryUtils::SQUARE_FLOAT(keyWidth);
 }
 
 int ProximityInfo::getCodePointOf(const int keyIndex) const {
@@ -164,7 +165,7 @@
     // TODO: Optimize
     for (int i = 0; i < KEY_COUNT; ++i) {
         const int code = mKeyCodePoints[i];
-        const int lowerCode = toLowerCase(code);
+        const int lowerCode = CharUtils::toLowerCase(code);
         mCenterXsG[i] = mKeyXCoordinates[i] + mKeyWidths[i] / 2;
         mCenterYsG[i] = mKeyYCoordinates[i] + mKeyHeights[i] / 2;
         mCodeToKeyMap[lowerCode] = i;
@@ -173,7 +174,7 @@
     for (int i = 0; i < KEY_COUNT; i++) {
         mKeyKeyDistancesG[i][i] = 0;
         for (int j = i + 1; j < KEY_COUNT; j++) {
-            mKeyKeyDistancesG[i][j] = getDistanceInt(
+            mKeyKeyDistancesG[i][j] = GeometryUtils::getDistanceInt(
                     mCenterXsG[i], mCenterYsG[i], mCenterXsG[j], mCenterYsG[j]);
             mKeyKeyDistancesG[j][i] = mKeyKeyDistancesG[i][j];
         }
diff --git a/native/jni/src/proximity_info.h b/native/jni/src/suggest/core/layout/proximity_info.h
similarity index 98%
rename from native/jni/src/proximity_info.h
rename to native/jni/src/suggest/core/layout/proximity_info.h
index deb9ae0..6d2ddd4 100644
--- a/native/jni/src/proximity_info.h
+++ b/native/jni/src/suggest/core/layout/proximity_info.h
@@ -20,7 +20,7 @@
 #include "defines.h"
 #include "hash_map_compat.h"
 #include "jni.h"
-#include "proximity_info_utils.h"
+#include "suggest/core/layout/proximity_info_utils.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/proximity_info_params.cpp b/native/jni/src/suggest/core/layout/proximity_info_params.cpp
similarity index 98%
rename from native/jni/src/proximity_info_params.cpp
rename to native/jni/src/suggest/core/layout/proximity_info_params.cpp
index 2675d9e..0e887f7 100644
--- a/native/jni/src/proximity_info_params.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info_params.cpp
@@ -15,7 +15,7 @@
  */
 
 #include "defines.h"
-#include "proximity_info_params.h"
+#include "suggest/core/layout/proximity_info_params.h"
 
 namespace latinime {
 const float ProximityInfoParams::NOT_A_DISTANCE_FLOAT = -1.0f;
diff --git a/native/jni/src/proximity_info_params.h b/native/jni/src/suggest/core/layout/proximity_info_params.h
similarity index 100%
rename from native/jni/src/proximity_info_params.h
rename to native/jni/src/suggest/core/layout/proximity_info_params.h
diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/suggest/core/layout/proximity_info_state.cpp
similarity index 95%
rename from native/jni/src/proximity_info_state.cpp
rename to native/jni/src/suggest/core/layout/proximity_info_state.cpp
index cc5b736..412d550 100644
--- a/native/jni/src/proximity_info_state.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info_state.cpp
@@ -14,17 +14,19 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "LatinIME: proximity_info_state.cpp"
+
+#include "suggest/core/layout/proximity_info_state.h"
+
 #include <cstring> // for memset() and memcpy()
 #include <sstream> // for debug prints
 #include <vector>
 
-#define LOG_TAG "LatinIME: proximity_info_state.cpp"
-
 #include "defines.h"
-#include "geometry_utils.h"
-#include "proximity_info.h"
-#include "proximity_info_state.h"
-#include "proximity_info_state_utils.h"
+#include "suggest/core/dictionary/char_utils.h"
+#include "suggest/core/layout/geometry_utils.h"
+#include "suggest/core/layout/proximity_info.h"
+#include "suggest/core/layout/proximity_info_state_utils.h"
 
 namespace latinime {
 
@@ -174,7 +176,7 @@
         const int index = inputIndex * mProximityInfo->getKeyCount() + keyId;
         return min(mSampledNormalizedSquaredLengthCache[index], mMaxPointToKeyLength);
     }
-    if (isIntentionalOmissionCodePoint(codePoint)) {
+    if (CharUtils::isIntentionalOmissionCodePoint(codePoint)) {
         return 0.0f;
     }
     // If the char is not a key on the keyboard then return the max length.
@@ -202,7 +204,7 @@
         const bool checkProximityChars, int *proximityIndex) const {
     const int *currentCodePoints = getProximityCodePointsAt(index);
     const int firstCodePoint = currentCodePoints[0];
-    const int baseLowerC = toBaseLowerCase(codePoint);
+    const int baseLowerC = CharUtils::toBaseLowerCase(codePoint);
 
     // The first char in the array is what user typed. If it matches right away, that means the
     // user typed that same char for this pos.
@@ -214,7 +216,7 @@
 
     // If the non-accented, lowercased version of that first character matches c, then we have a
     // non-accented version of the accented character the user typed. Treat it as a close char.
-    if (toBaseLowerCase(firstCodePoint) == baseLowerC) {
+    if (CharUtils::toBaseLowerCase(firstCodePoint) == baseLowerC) {
         return PROXIMITY_CHAR;
     }
 
@@ -256,8 +258,8 @@
     if (!isUsed()) {
         return UNRELATED_CHAR;
     }
-    const int lowerCodePoint = toLowerCase(codePoint);
-    const int baseLowerCodePoint = toBaseCodePoint(lowerCodePoint);
+    const int lowerCodePoint = CharUtils::toLowerCase(codePoint);
+    const int baseLowerCodePoint = CharUtils::toBaseCodePoint(lowerCodePoint);
     for (int i = 0; i < static_cast<int>(mSampledSearchKeyVectors[index].size()); ++i) {
         if (mSampledSearchKeyVectors[index][i] == lowerCodePoint
                 || mSampledSearchKeyVectors[index][i] == baseLowerCodePoint) {
diff --git a/native/jni/src/proximity_info_state.h b/native/jni/src/suggest/core/layout/proximity_info_state.h
similarity index 98%
rename from native/jni/src/proximity_info_state.h
rename to native/jni/src/suggest/core/layout/proximity_info_state.h
index bbe8af2..a971294 100644
--- a/native/jni/src/proximity_info_state.h
+++ b/native/jni/src/suggest/core/layout/proximity_info_state.h
@@ -20,11 +20,10 @@
 #include <cstring> // for memset()
 #include <vector>
 
-#include "char_utils.h"
 #include "defines.h"
 #include "hash_map_compat.h"
-#include "proximity_info_params.h"
-#include "proximity_info_state_utils.h"
+#include "suggest/core/layout/proximity_info_params.h"
+#include "suggest/core/layout/proximity_info_state_utils.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/proximity_info_state_utils.cpp b/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp
similarity index 95%
rename from native/jni/src/proximity_info_state_utils.cpp
rename to native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp
index 359673c..6f88833 100644
--- a/native/jni/src/proximity_info_state_utils.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp
@@ -14,16 +14,17 @@
  * limitations under the License.
  */
 
+#include "suggest/core/layout/proximity_info_state_utils.h"
+
 #include <cmath>
 #include <cstring> // for memset()
 #include <sstream> // for debug prints
 #include <vector>
 
 #include "defines.h"
-#include "geometry_utils.h"
-#include "proximity_info.h"
-#include "proximity_info_params.h"
-#include "proximity_info_state_utils.h"
+#include "suggest/core/layout/geometry_utils.h"
+#include "suggest/core/layout/proximity_info.h"
+#include "suggest/core/layout/proximity_info_params.h"
 
 namespace latinime {
 
@@ -103,12 +104,12 @@
             const int time = times ? times[i] : -1;
 
             if (i > 1) {
-                const float prevAngle = getAngle(
+                const float prevAngle = GeometryUtils::getAngle(
                         inputXCoordinates[i - 2], inputYCoordinates[i - 2],
                         inputXCoordinates[i - 1], inputYCoordinates[i - 1]);
-                const float currentAngle = getAngle(
+                const float currentAngle = GeometryUtils::getAngle(
                         inputXCoordinates[i - 1], inputYCoordinates[i - 1], x, y);
-                sumAngle += getAngleDiff(prevAngle, currentAngle);
+                sumAngle += GeometryUtils::getAngleDiff(prevAngle, currentAngle);
             }
 
             if (pushTouchPoint(proximityInfo, maxPointToKeyLength, i, c, x, y, time,
@@ -157,7 +158,8 @@
     const float sweetSpotCenterY = proximityInfo->getSweetSpotCenterYAt(keyIndex);
     const float inputX = static_cast<float>((*sampledInputXs)[inputIndex]);
     const float inputY = static_cast<float>((*sampledInputYs)[inputIndex]);
-    return SQUARE_FLOAT(inputX - sweetSpotCenterX) + SQUARE_FLOAT(inputY - sweetSpotCenterY);
+    return GeometryUtils::SQUARE_FLOAT(inputX - sweetSpotCenterX)
+            + GeometryUtils::SQUARE_FLOAT(inputY - sweetSpotCenterY);
 }
 
 /* static */ float ProximityInfoStateUtils::calculateNormalizedSquaredDistance(
@@ -174,7 +176,8 @@
     }
     const float squaredDistance = calculateSquaredDistanceFromSweetSpotCenter(proximityInfo,
             sampledInputXs, sampledInputYs, keyIndex, inputIndex);
-    const float squaredRadius = SQUARE_FLOAT(proximityInfo->getSweetSpotRadiiAt(keyIndex));
+    const float squaredRadius = GeometryUtils::SQUARE_FLOAT(
+            proximityInfo->getSweetSpotRadiiAt(keyIndex));
     return squaredDistance / squaredRadius;
 }
 
@@ -285,7 +288,7 @@
             if (i < sampledInputSize - 1 && j >= (*sampledInputIndice)[i + 1]) {
                 break;
             }
-            length += getDistanceInt(xCoordinates[j], yCoordinates[j],
+            length += GeometryUtils::getDistanceInt(xCoordinates[j], yCoordinates[j],
                     xCoordinates[j + 1], yCoordinates[j + 1]);
             duration += times[j + 1] - times[j];
         }
@@ -296,7 +299,7 @@
                 break;
             }
             // TODO: use mSampledLengthCache instead?
-            length += getDistanceInt(xCoordinates[j], yCoordinates[j],
+            length += GeometryUtils::getDistanceInt(xCoordinates[j], yCoordinates[j],
                     xCoordinates[j + 1], yCoordinates[j + 1]);
             duration += times[j + 1] - times[j];
         }
@@ -349,7 +352,7 @@
     const int y1 = (*sampledInputYs)[index0];
     const int x2 = (*sampledInputXs)[index1];
     const int y2 = (*sampledInputYs)[index1];
-    return getAngle(x1, y1, x2, y2);
+    return GeometryUtils::getAngle(x1, y1, x2, y2);
 }
 
 // Calculating point to key distance for all near keys and returning the distance between
@@ -411,9 +414,9 @@
     }
 
     const int baseSampleRate = mostCommonKeyWidth;
-    const int distPrev = getDistanceInt(sampledInputXs->back(), sampledInputYs->back(),
-            (*sampledInputXs)[size - 2], (*sampledInputYs)[size - 2])
-                    * ProximityInfoParams::DISTANCE_BASE_SCALE;
+    const int distPrev = GeometryUtils::getDistanceInt(sampledInputXs->back(),
+            sampledInputYs->back(), (*sampledInputXs)[size - 2],
+            (*sampledInputYs)[size - 2]) * ProximityInfoParams::DISTANCE_BASE_SCALE;
     float score = 0.0f;
 
     // Location
@@ -425,10 +428,11 @@
         score += ProximityInfoParams::LOCALMIN_DISTANCE_AND_NEAR_TO_KEY_SCORE;
     }
     // Angle
-    const float angle1 = getAngle(x, y, sampledInputXs->back(), sampledInputYs->back());
-    const float angle2 = getAngle(sampledInputXs->back(), sampledInputYs->back(),
+    const float angle1 = GeometryUtils::getAngle(x, y, sampledInputXs->back(),
+            sampledInputYs->back());
+    const float angle2 = GeometryUtils::getAngle(sampledInputXs->back(), sampledInputYs->back(),
             (*sampledInputXs)[size - 2], (*sampledInputYs)[size - 2]);
-    const float angleDiff = getAngleDiff(angle1, angle2);
+    const float angleDiff = GeometryUtils::getAngleDiff(angle1, angle2);
 
     // Save corner
     if (distPrev > baseSampleRate * ProximityInfoParams::CORNER_CHECK_DISTANCE_THRESHOLD_SCALE
@@ -472,13 +476,13 @@
         }
         // Check if the last point should be skipped.
         if (isLastPoint && size > 0) {
-            if (getDistanceInt(x, y, sampledInputXs->back(), sampledInputYs->back())
+            if (GeometryUtils::getDistanceInt(x, y, sampledInputXs->back(), sampledInputYs->back())
                     * ProximityInfoParams::LAST_POINT_SKIP_DISTANCE_SCALE < mostCommonKeyWidth) {
                 // This point is not used because it's too close to the previous point.
                 if (DEBUG_GEO_FULL) {
                     AKLOGI("p0: size = %zd, x = %d, y = %d, lx = %d, ly = %d, dist = %d, "
                            "width = %d", size, x, y, sampledInputXs->back(),
-                           sampledInputYs->back(), getDistanceInt(
+                           sampledInputYs->back(), GeometryUtils::getDistanceInt(
                                    x, y, sampledInputXs->back(), sampledInputYs->back()),
                            mostCommonKeyWidth
                                    / ProximityInfoParams::LAST_POINT_SKIP_DISTANCE_SCALE);
@@ -499,7 +503,7 @@
     // Pushing point information.
     if (size > 0) {
         sampledLengthCache->push_back(
-                sampledLengthCache->back() + getDistanceInt(
+                sampledLengthCache->back() + GeometryUtils::getDistanceInt(
                         x, y, sampledInputXs->back(), sampledInputYs->back()));
     } else {
         sampledLengthCache->push_back(0);
@@ -540,7 +544,8 @@
     while (start > 0 && tempBeelineDistance < lookupRadius) {
         tempTime += times[start] - times[start - 1];
         --start;
-        tempBeelineDistance = getDistanceInt(x0, y0, xCoordinates[start], yCoordinates[start]);
+        tempBeelineDistance = GeometryUtils::getDistanceInt(x0, y0, xCoordinates[start],
+                yCoordinates[start]);
     }
     // Exclusive unless this is an edge point
     if (start > 0 && start < actualInputIndex) {
@@ -553,7 +558,8 @@
     while (end < (inputSize - 1) && tempBeelineDistance < lookupRadius) {
         tempTime += times[end + 1] - times[end];
         ++end;
-        tempBeelineDistance = getDistanceInt(x0, y0, xCoordinates[end], yCoordinates[end]);
+        tempBeelineDistance = GeometryUtils::getDistanceInt(x0, y0, xCoordinates[end],
+                yCoordinates[end]);
     }
     // Exclusive unless this is an edge point
     if (end > actualInputIndex && end < (inputSize - 1)) {
@@ -571,7 +577,7 @@
     const int y2 = yCoordinates[start];
     const int x3 = xCoordinates[end];
     const int y3 = yCoordinates[end];
-    const int beelineDistance = getDistanceInt(x2, y2, x3, y3);
+    const int beelineDistance = GeometryUtils::getDistanceInt(x2, y2, x3, y3);
     int adjustedStartTime = times[start];
     if (start == 0 && actualInputIndex == 0 && inputSize > 1) {
         adjustedStartTime += ProximityInfoParams::FIRST_POINT_TIME_OFFSET_MILLIS;
@@ -613,7 +619,7 @@
     }
     const float previousDirection = getDirection(sampledInputXs, sampledInputYs, index - 1, index);
     const float nextDirection = getDirection(sampledInputXs, sampledInputYs, index, index + 1);
-    const float directionDiff = getAngleDiff(previousDirection, nextDirection);
+    const float directionDiff = GeometryUtils::getAngleDiff(previousDirection, nextDirection);
     return directionDiff;
 }
 
@@ -636,7 +642,7 @@
     }
     const float previousDirection = getDirection(sampledInputXs, sampledInputYs, index0, index1);
     const float nextDirection = getDirection(sampledInputXs, sampledInputYs, index1, index2);
-    return getAngleDiff(previousDirection, nextDirection);
+    return GeometryUtils::getAngleDiff(previousDirection, nextDirection);
 }
 
 // This function basically converts from a length to an edit distance. Accordingly, it's obviously
diff --git a/native/jni/src/proximity_info_state_utils.h b/native/jni/src/suggest/core/layout/proximity_info_state_utils.h
similarity index 100%
rename from native/jni/src/proximity_info_state_utils.h
rename to native/jni/src/suggest/core/layout/proximity_info_state_utils.h
diff --git a/native/jni/src/proximity_info_utils.h b/native/jni/src/suggest/core/layout/proximity_info_utils.h
similarity index 92%
rename from native/jni/src/proximity_info_utils.h
rename to native/jni/src/suggest/core/layout/proximity_info_utils.h
index 71c97e3..3588f4d 100644
--- a/native/jni/src/proximity_info_utils.h
+++ b/native/jni/src/suggest/core/layout/proximity_info_utils.h
@@ -19,11 +19,11 @@
 
 #include <cmath>
 
-#include "additional_proximity_chars.h"
-#include "char_utils.h"
 #include "defines.h"
-#include "geometry_utils.h"
 #include "hash_map_compat.h"
+#include "suggest/core/dictionary/char_utils.h"
+#include "suggest/core/layout/additional_proximity_chars.h"
+#include "suggest/core/layout/geometry_utils.h"
 
 namespace latinime {
 class ProximityInfoUtils {
@@ -37,7 +37,7 @@
         if (c == NOT_A_CODE_POINT) {
             return NOT_AN_INDEX;
         }
-        const int lowerCode = toLowerCase(c);
+        const int lowerCode = CharUtils::toLowerCase(c);
         hash_map_compat<int, int>::const_iterator mapPos = codeToKeyMap->find(lowerCode);
         if (mapPos != codeToKeyMap->end()) {
             return mapPos->second;
@@ -87,7 +87,7 @@
 
     static inline float getSquaredDistanceFloat(const float x1, const float y1, const float x2,
             const float y2) {
-        return SQUARE_FLOAT(x1 - x2) + SQUARE_FLOAT(y1 - y2);
+        return GeometryUtils::SQUARE_FLOAT(x1 - x2) + GeometryUtils::SQUARE_FLOAT(y1 - y2);
     }
 
     static inline float pointToLineSegSquaredDistanceFloat(const float x, const float y,
@@ -98,7 +98,8 @@
         const float ray2y = y2 - y1;
 
         const float dotProduct = ray1x * ray2x + ray1y * ray2y;
-        const float lineLengthSqr = SQUARE_FLOAT(ray2x) + SQUARE_FLOAT(ray2y);
+        const float lineLengthSqr = GeometryUtils::SQUARE_FLOAT(ray2x)
+                + GeometryUtils::SQUARE_FLOAT(ray2y);
         const float projectionLengthSqr = dotProduct / lineLengthSqr;
 
         float projectionX;
@@ -121,12 +122,14 @@
      public:
         NormalDistribution(const float u, const float sigma)
                 : mU(u), mSigma(sigma),
-                  mPreComputedNonExpPart(1.0f / sqrtf(2.0f * M_PI_F * SQUARE_FLOAT(sigma))),
-                  mPreComputedExponentPart(-1.0f / (2.0f * SQUARE_FLOAT(sigma))) {}
+                  mPreComputedNonExpPart(1.0f / sqrtf(2.0f * M_PI_F
+                          * GeometryUtils::SQUARE_FLOAT(sigma))),
+                  mPreComputedExponentPart(-1.0f / (2.0f * GeometryUtils::SQUARE_FLOAT(sigma))) {}
 
         float getProbabilityDensity(const float x) const {
             const float shiftedX = x - mU;
-            return mPreComputedNonExpPart * expf(mPreComputedExponentPart * SQUARE_FLOAT(shiftedX));
+            return mPreComputedNonExpPart
+                    * expf(mPreComputedExponentPart * GeometryUtils::SQUARE_FLOAT(shiftedX));
         }
 
      private:
diff --git a/native/jni/src/suggest_utils.h b/native/jni/src/suggest/core/layout/touch_position_correction_utils.h
similarity index 89%
rename from native/jni/src/suggest_utils.h
rename to native/jni/src/suggest/core/layout/touch_position_correction_utils.h
index e053dd6..429dcae 100644
--- a/native/jni/src/suggest_utils.h
+++ b/native/jni/src/suggest/core/layout/touch_position_correction_utils.h
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-#ifndef LATINIME_SUGGEST_UTILS_H
-#define LATINIME_SUGGEST_UTILS_H
+#ifndef LATINIME_TOUCH_POSITION_CORRECTION_UTILS_H
+#define LATINIME_TOUCH_POSITION_CORRECTION_UTILS_H
 
 #include "defines.h"
-#include "proximity_info_params.h"
+#include "suggest/core/layout/proximity_info_params.h"
 
 namespace latinime {
-class SuggestUtils {
+class TouchPositionCorrectionUtils {
  public:
     // TODO: (OLD) Remove
     static float getLengthScalingFactor(const float normalizedSquaredDistance) {
@@ -82,7 +82,7 @@
         }
     }
  private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(SuggestUtils);
+    DISALLOW_IMPLICIT_CONSTRUCTORS(TouchPositionCorrectionUtils);
 };
 } // namespace latinime
-#endif // LATINIME_SUGGEST_UTILS_H
+#endif // LATINIME_TOUCH_POSITION_CORRECTION_UTILS_H
diff --git a/native/jni/src/suggest/core/policy/weighting.cpp b/native/jni/src/suggest/core/policy/weighting.cpp
index d01531f..7ad568e 100644
--- a/native/jni/src/suggest/core/policy/weighting.cpp
+++ b/native/jni/src/suggest/core/policy/weighting.cpp
@@ -16,7 +16,6 @@
 
 #include "suggest/core/policy/weighting.h"
 
-#include "char_utils.h"
 #include "defines.h"
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_profiler.h"
diff --git a/native/jni/src/suggest/core/session/dic_traverse_session.cpp b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
index 6408f01..4e63450 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.cpp
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
@@ -16,12 +16,12 @@
 
 #include "suggest/core/session/dic_traverse_session.h"
 
-#include "binary_format.h"
 #include "defines.h"
-#include "dictionary.h"
 #include "dic_traverse_wrapper.h"
 #include "jni.h"
 #include "suggest/core/dicnode/dic_node_utils.h"
+#include "suggest/core/dictionary/binary_format.h"
+#include "suggest/core/dictionary/dictionary.h"
 
 namespace latinime {
 
@@ -34,10 +34,11 @@
 
 // TODO: Pass "DicTraverseSession *traverseSession" when the source code structure settles down.
 static void initSessionInstance(void *traverseSession, const Dictionary *const dictionary,
-        const int *prevWord, const int prevWordLength) {
+        const int *prevWord, const int prevWordLength,
+        const SuggestOptions *const suggestOptions) {
     if (traverseSession) {
         DicTraverseSession *tSession = static_cast<DicTraverseSession *>(traverseSession);
-        tSession->init(dictionary, prevWord, prevWordLength);
+        tSession->init(dictionary, prevWord, prevWordLength, suggestOptions);
     }
 }
 
@@ -62,10 +63,11 @@
 static TraverseSessionFactoryRegisterer traverseSessionFactoryRegisterer;
 
 void DicTraverseSession::init(const Dictionary *const dictionary, const int *prevWord,
-        int prevWordLength) {
+        int prevWordLength, const SuggestOptions *const suggestOptions) {
     mDictionary = dictionary;
     mMultiWordCostMultiplier = BinaryFormat::getMultiWordCostMultiplier(mDictionary->getDict(),
             mDictionary->getDictSize());
+    mSuggestOptions = suggestOptions;
     if (!prevWord) {
         mPrevWordPos = NOT_VALID_WORD;
         return;
diff --git a/native/jni/src/suggest/core/session/dic_traverse_session.h b/native/jni/src/suggest/core/session/dic_traverse_session.h
index d88be5b..e5c7f8e 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.h
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.h
@@ -22,20 +22,21 @@
 
 #include "defines.h"
 #include "jni.h"
-#include "multi_bigram_map.h"
-#include "proximity_info_state.h"
 #include "suggest/core/dicnode/dic_nodes_cache.h"
+#include "suggest/core/dictionary/multi_bigram_map.h"
+#include "suggest/core/layout/proximity_info_state.h"
 
 namespace latinime {
 
 class Dictionary;
 class ProximityInfo;
+class SuggestOptions;
 
 class DicTraverseSession {
  public:
     AK_FORCE_INLINE DicTraverseSession(JNIEnv *env, jstring localeStr)
             : mPrevWordPos(NOT_VALID_WORD), mProximityInfo(0),
-              mDictionary(0), mDicNodesCache(), mMultiBigramMap(),
+              mDictionary(0), mSuggestOptions(0), mDicNodesCache(), mMultiBigramMap(),
               mInputSize(0), mPartiallyCommited(false), mMaxPointerCount(1),
               mMultiWordCostMultiplier(1.0f) {
         // NOTE: mProximityInfoStates is an array of instances.
@@ -45,7 +46,8 @@
     // Non virtual inline destructor -- never inherit this class
     AK_FORCE_INLINE ~DicTraverseSession() {}
 
-    void init(const Dictionary *dictionary, const int *prevWord, int prevWordLength);
+    void init(const Dictionary *dictionary, const int *prevWord, int prevWordLength,
+            const SuggestOptions *const suggestOptions);
     // TODO: Remove and merge into init
     void setupForGetSuggestions(const ProximityInfo *pInfo, const int *inputCodePoints,
             const int inputSize, const int *const inputXs, const int *const inputYs,
@@ -61,6 +63,7 @@
     // getters and setters
     //--------------------
     const ProximityInfo *getProximityInfo() const { return mProximityInfo; }
+    const SuggestOptions *getSuggestOptions() const { return mSuggestOptions; }
     int getPrevWordPos() const { return mPrevWordPos; }
     // TODO: REMOVE
     void setPrevWordPos(int pos) { mPrevWordPos = pos; }
@@ -167,6 +170,7 @@
     int mPrevWordPos;
     const ProximityInfo *mProximityInfo;
     const Dictionary *mDictionary;
+    const SuggestOptions *mSuggestOptions;
 
     DicNodesCache mDicNodesCache;
     // Temporary cache for bigram frequencies
diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp
index a187948..7202223 100644
--- a/native/jni/src/suggest/core/suggest.cpp
+++ b/native/jni/src/suggest/core/suggest.cpp
@@ -16,19 +16,18 @@
 
 #include "suggest/core/suggest.h"
 
-#include "char_utils.h"
-#include "dictionary.h"
-#include "digraph_utils.h"
-#include "proximity_info.h"
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_priority_queue.h"
 #include "suggest/core/dicnode/dic_node_vector.h"
+#include "suggest/core/dictionary/dictionary.h"
+#include "suggest/core/dictionary/digraph_utils.h"
 #include "suggest/core/dictionary/shortcut_utils.h"
+#include "suggest/core/dictionary/terminal_attributes.h"
+#include "suggest/core/layout/proximity_info.h"
 #include "suggest/core/policy/scoring.h"
 #include "suggest/core/policy/traversal.h"
 #include "suggest/core/policy/weighting.h"
 #include "suggest/core/session/dic_traverse_session.h"
-#include "terminal_attributes.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_traversal.h b/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
index 12110d5..e066418 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
@@ -19,11 +19,11 @@
 
 #include <stdint.h>
 
-#include "char_utils.h"
 #include "defines.h"
-#include "proximity_info_state.h"
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_vector.h"
+#include "suggest/core/dictionary/char_utils.h"
+#include "suggest/core/layout/proximity_info_state.h"
 #include "suggest/core/policy/traversal.h"
 #include "suggest/core/session/dic_traverse_session.h"
 #include "suggest/policyimpl/typing/scoring_params.h"
@@ -64,9 +64,9 @@
         }
         const int point0Index = dicNode->getInputIndex(0);
         const int currentBaseLowerCodePoint =
-                toBaseLowerCase(childDicNode->getNodeCodePoint());
+                CharUtils::toBaseLowerCase(childDicNode->getNodeCodePoint());
         const int typedBaseLowerCodePoint =
-                toBaseLowerCase(traverseSession->getProximityInfoState(0)
+                CharUtils::toBaseLowerCase(traverseSession->getProximityInfoState(0)
                         ->getPrimaryCodePointAt(point0Index));
         return (currentBaseLowerCodePoint != typedBaseLowerCodePoint);
     }
@@ -172,7 +172,7 @@
         }
         const int c = dicNode->getOutputWordBuf()[0];
         const bool shortCappedWord = dicNode->getDepth()
-                < ScoringParams::THRESHOLD_SHORT_WORD_LENGTH && isAsciiUpper(c);
+                < ScoringParams::THRESHOLD_SHORT_WORD_LENGTH && CharUtils::isAsciiUpper(c);
         return !shortCappedWord
                 || probability >= ScoringParams::THRESHOLD_NEXT_WORD_PROBABILITY_FOR_CAPPED;
     }
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
index 3938c0e..9e0c0d2 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
@@ -18,8 +18,9 @@
 #define LATINIME_TYPING_WEIGHTING_H
 
 #include "defines.h"
-#include "suggest_utils.h"
 #include "suggest/core/dicnode/dic_node_utils.h"
+#include "suggest/core/dictionary/char_utils.h"
+#include "suggest/core/layout/touch_position_correction_utils.h"
 #include "suggest/core/policy/weighting.h"
 #include "suggest/core/session/dic_traverse_session.h"
 #include "suggest/policyimpl/typing/scoring_params.h"
@@ -74,7 +75,7 @@
         // the keyboard (like accented letters)
         const float normalizedSquaredLength = traverseSession->getProximityInfoState(0)
                 ->getPointToKeyLength(pointIndex, dicNode->getNodeCodePoint());
-        const float normalizedDistance = SuggestUtils::getSweetSpotFactor(
+        const float normalizedDistance = TouchPositionCorrectionUtils::getSweetSpotFactor(
                 traverseSession->isTouchPositionCorrectionEnabled(), normalizedSquaredLength);
         const float weightedDistance = ScoringParams::DISTANCE_WEIGHT_LENGTH * normalizedDistance;
 
@@ -98,9 +99,9 @@
     bool isProximityDicNode(const DicTraverseSession *const traverseSession,
             const DicNode *const dicNode) const {
         const int pointIndex = dicNode->getInputIndex(0);
-        const int primaryCodePoint = toBaseLowerCase(
+        const int primaryCodePoint = CharUtils::toBaseLowerCase(
                 traverseSession->getProximityInfoState(0)->getPrimaryCodePointAt(pointIndex));
-        const int dicNodeChar = toBaseLowerCase(dicNode->getNodeCodePoint());
+        const int dicNodeChar = CharUtils::toBaseLowerCase(dicNode->getNodeCodePoint());
         return primaryCodePoint != dicNodeChar;
     }
 
diff --git a/native/jni/src/suggest/policyimpl/utils/damerau_levenshtein_edit_distance_policy.h b/native/jni/src/suggest/policyimpl/utils/damerau_levenshtein_edit_distance_policy.h
index ec14574..09f986a 100644
--- a/native/jni/src/suggest/policyimpl/utils/damerau_levenshtein_edit_distance_policy.h
+++ b/native/jni/src/suggest/policyimpl/utils/damerau_levenshtein_edit_distance_policy.h
@@ -17,7 +17,7 @@
 #ifndef LATINIME_DAEMARU_LEVENSHTEIN_EDIT_DISTANCE_POLICY_H
 #define LATINIME_DAEMARU_LEVENSHTEIN_EDIT_DISTANCE_POLICY_H
 
-#include "char_utils.h"
+#include "suggest/core/dictionary/char_utils.h"
 #include "suggest/policyimpl/utils/edit_distance_policy.h"
 
 namespace latinime {
@@ -31,8 +31,8 @@
     ~DamerauLevenshteinEditDistancePolicy() {}
 
     AK_FORCE_INLINE float getSubstitutionCost(const int index0, const int index1) const {
-        const int c0 = toBaseLowerCase(mString0[index0]);
-        const int c1 = toBaseLowerCase(mString1[index1]);
+        const int c0 = CharUtils::toBaseLowerCase(mString0[index0]);
+        const int c1 = CharUtils::toBaseLowerCase(mString1[index1]);
         return (c0 == c1) ? 0.0f : 1.0f;
     }
 
@@ -45,10 +45,10 @@
     }
 
     AK_FORCE_INLINE bool allowTransposition(const int index0, const int index1) const {
-        const int c0 = toBaseLowerCase(mString0[index0]);
-        const int c1 = toBaseLowerCase(mString1[index1]);
-        if (index0 > 0 && index1 > 0 && c0 == toBaseLowerCase(mString1[index1 - 1])
-                && c1 == toBaseLowerCase(mString0[index0 - 1])) {
+        const int c0 = CharUtils::toBaseLowerCase(mString0[index0]);
+        const int c1 = CharUtils::toBaseLowerCase(mString1[index1]);
+        if (index0 > 0 && index1 > 0 && c0 == CharUtils::toBaseLowerCase(mString1[index1 - 1])
+                && c1 == CharUtils::toBaseLowerCase(mString0[index0 - 1])) {
             return true;
         }
         return false;
diff --git a/native/jni/src/suggest_options.h b/native/jni/src/suggest_options.h
index 1bed47c..1b21aaf 100644
--- a/native/jni/src/suggest_options.h
+++ b/native/jni/src/suggest_options.h
@@ -23,6 +23,9 @@
 
 class SuggestOptions{
  public:
+    SuggestOptions(const int *const options, const int length)
+            : mOptions(options), mLength(length) {}
+
     AK_FORCE_INLINE bool isGesture() const {
         return getBoolOption(IS_GESTURE);
     }
@@ -31,14 +34,20 @@
         return getBoolOption(USE_FULL_EDIT_DISTANCE);
     }
 
-    SuggestOptions(const int *const options, const int length)
-            : mOptions(options), mLength(length) {}
+    AK_FORCE_INLINE bool getAdditionalFeaturesBoolOption(const int key) const {
+        return getBoolOption(key + ADDITIONAL_FEATURES_OPTIONS);
+    }
 
  private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(SuggestOptions);
+
     // Need to update com.android.inputmethod.latin.NativeSuggestOptions when you add, remove or
     // reorder options.
     static const int IS_GESTURE = 0;
     static const int USE_FULL_EDIT_DISTANCE = 1;
+    // Additional features options are stored after the other options and used as setting values of
+    // experimental features.
+    static const int ADDITIONAL_FEATURES_OPTIONS = 2;
 
     const int *const mOptions;
     const int mLength;
diff --git a/native/jni/src/unigram_dictionary.cpp b/native/jni/src/unigram_dictionary.cpp
index a672294..66a8b85 100644
--- a/native/jni/src/unigram_dictionary.cpp
+++ b/native/jni/src/unigram_dictionary.cpp
@@ -18,13 +18,13 @@
 
 #define LOG_TAG "LatinIME: unigram_dictionary.cpp"
 
-#include "binary_format.h"
-#include "char_utils.h"
 #include "defines.h"
-#include "dictionary.h"
-#include "digraph_utils.h"
-#include "proximity_info.h"
-#include "terminal_attributes.h"
+#include "suggest/core/dictionary/binary_format.h"
+#include "suggest/core/dictionary/char_utils.h"
+#include "suggest/core/dictionary/dictionary.h"
+#include "suggest/core/dictionary/digraph_utils.h"
+#include "suggest/core/dictionary/terminal_attributes.h"
+#include "suggest/core/layout/proximity_info.h"
 #include "unigram_dictionary.h"
 #include "words_priority_queue.h"
 #include "words_priority_queue_pool.h"
@@ -696,8 +696,8 @@
     const bool hasMultipleChars = (0 != (BinaryFormat::FLAG_HAS_MULTIPLE_CHARS & flags));
     int pos = startPos;
     int codePoint = BinaryFormat::getCodePointAndForwardPointer(root, &pos);
-    int baseChar = toBaseLowerCase(codePoint);
-    const int wChar = toBaseLowerCase(inWord[startInputIndex]);
+    int baseChar = CharUtils::toBaseLowerCase(codePoint);
+    const int wChar = CharUtils::toBaseLowerCase(inWord[startInputIndex]);
 
     if (baseChar != wChar) {
         *outPos = hasMultipleChars ? BinaryFormat::skipOtherCharacters(root, pos) : pos;
@@ -709,8 +709,9 @@
     if (hasMultipleChars) {
         codePoint = BinaryFormat::getCodePointAndForwardPointer(root, &pos);
         while (NOT_A_CODE_POINT != codePoint) {
-            baseChar = toBaseLowerCase(codePoint);
-            if (inputIndex + 1 >= inputSize || toBaseLowerCase(inWord[++inputIndex]) != baseChar) {
+            baseChar = CharUtils::toBaseLowerCase(codePoint);
+            if (inputIndex + 1 >= inputSize
+                    || CharUtils::toBaseLowerCase(inWord[++inputIndex]) != baseChar) {
                 *outPos = BinaryFormat::skipOtherCharacters(root, pos);
                 *outInputIndex = startInputIndex;
                 return false;
diff --git a/native/jni/src/unigram_dictionary.h b/native/jni/src/unigram_dictionary.h
index a64a539..a505032 100644
--- a/native/jni/src/unigram_dictionary.h
+++ b/native/jni/src/unigram_dictionary.h
@@ -19,8 +19,9 @@
 
 #include <map>
 #include <stdint.h>
+
 #include "defines.h"
-#include "digraph_utils.h"
+#include "suggest/core/dictionary/digraph_utils.h"
 
 namespace latinime {