Merge "Update ExpandableDictionary.BASE_CHARS[]"
diff --git a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
index 3bed2c7..d5e638e 100644
--- a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
+++ b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
@@ -28,8 +28,8 @@
 
 import com.android.inputmethod.compat.DownloadManagerCompatUtils;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
 import com.android.inputmethod.latin.utils.DebugLogUtils;
-import com.android.inputmethod.latin.utils.Utils;
 
 import java.util.LinkedList;
 import java.util.Queue;
@@ -144,7 +144,7 @@
             // DownloadManager also stupidly cuts the extension to replace with its own that it
             // gets from the content-type. We need to circumvent this.
             final String disambiguator = "#" + System.currentTimeMillis()
-                    + Utils.getVersionName(context) + ".dict";
+                    + ApplicationUtils.getVersionName(context) + ".dict";
             final Uri uri = Uri.parse(mWordList.mRemoteFilename + disambiguator);
             final Request request = new Request(uri);
 
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
index 3b00723..767f895 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
@@ -22,7 +22,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.IBinder;
-import android.text.format.DateUtils;
 import android.util.Log;
 import android.widget.Toast;
 
@@ -30,6 +29,7 @@
 
 import java.util.Locale;
 import java.util.Random;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Service that handles background tasks for the dictionary provider.
@@ -77,19 +77,19 @@
      * How often, in milliseconds, we want to update the metadata. This is a
      * floor value; actually, it may happen several hours later, or even more.
      */
-    private static final long UPDATE_FREQUENCY = 4 * DateUtils.DAY_IN_MILLIS;
+    private static final long UPDATE_FREQUENCY = TimeUnit.DAYS.toMillis(4);
 
     /**
      * We are waked around midnight, local time. We want to wake between midnight and 6 am,
      * roughly. So use a random time between 0 and this delay.
      */
-    private static final int MAX_ALARM_DELAY = 6 * ((int)AlarmManager.INTERVAL_HOUR);
+    private static final int MAX_ALARM_DELAY = (int)TimeUnit.HOURS.toMillis(6);
 
     /**
      * How long we consider a "very long time". If no update took place in this time,
      * the content provider will trigger an update in the background.
      */
-    private static final long VERY_LONG_TIME = 14 * DateUtils.DAY_IN_MILLIS;
+    private static final long VERY_LONG_TIME = TimeUnit.DAYS.toMillis(14);
 
     /**
      * The last seen start Id. This must be stored because we must only call stopSelfResult() with
diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
index 8a23acd..f66ef87 100644
--- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
@@ -38,8 +38,8 @@
 import com.android.inputmethod.compat.ConnectivityManagerCompatUtils;
 import com.android.inputmethod.compat.DownloadManagerCompatUtils;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
 import com.android.inputmethod.latin.utils.DebugLogUtils;
-import com.android.inputmethod.latin.utils.Utils;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -222,7 +222,7 @@
         // DownloadManager also stupidly cuts the extension to replace with its own that it
         // gets from the content-type. We need to circumvent this.
         final String disambiguator = "#" + System.currentTimeMillis()
-                + Utils.getVersionName(context) + ".json";
+                + ApplicationUtils.getVersionName(context) + ".json";
         final Request metadataRequest = new Request(Uri.parse(metadataUri + disambiguator));
         DebugLogUtils.l("Request =", metadataRequest);
 
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 4cee4cf..254b20b 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -37,6 +37,7 @@
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.TypefaceUtils;
 import com.android.inputmethod.research.ResearchLogger;
 
 import java.util.HashSet;
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index a4606f2..f85e604 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -70,7 +70,9 @@
 import com.android.inputmethod.latin.utils.ResourceUtils;
 import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
 import com.android.inputmethod.latin.utils.StringUtils;
-import com.android.inputmethod.latin.utils.Utils.UsabilityStudyLogUtils;
+import com.android.inputmethod.latin.utils.TypefaceUtils;
+import com.android.inputmethod.latin.utils.UsabilityStudyLogUtils;
+import com.android.inputmethod.latin.utils.ViewLayoutUtils;
 import com.android.inputmethod.research.ResearchLogger;
 
 import java.util.Locale;
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index 51f5446..d29e638 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -29,6 +29,7 @@
 import com.android.inputmethod.keyboard.internal.MoreKeySpec;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.TypefaceUtils;
 
 public final class MoreKeysKeyboard extends Keyboard {
     private final int mDefaultKeyCoordX;
diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index 01ec7f9..5dbc9b1 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -25,7 +25,7 @@
 import android.preference.PreferenceScreen;
 
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.latin.utils.Utils;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
 
 public final class DebugSettings extends PreferenceFragment
         implements SharedPreferences.OnSharedPreferenceChangeListener {
@@ -118,7 +118,7 @@
         }
         boolean isDebugMode = mDebugMode.isChecked();
         final String version = getResources().getString(
-                R.string.version_text, Utils.getVersionName(getActivity()));
+                R.string.version_text, ApplicationUtils.getVersionName(getActivity()));
         if (!isDebugMode) {
             mDebugMode.setTitle(version);
             mDebugMode.setSummary("");
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index f4f9dcc..8f5e571 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -76,19 +76,19 @@
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.latin.suggestions.SuggestionStripView;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
 import com.android.inputmethod.latin.utils.CapsModeUtils;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.CompletionInfoUtils;
 import com.android.inputmethod.latin.utils.InputTypeUtils;
 import com.android.inputmethod.latin.utils.IntentUtils;
 import com.android.inputmethod.latin.utils.JniUtils;
+import com.android.inputmethod.latin.utils.LatinImeLoggerUtils;
 import com.android.inputmethod.latin.utils.PositionalInfoForUserDictPendingAddition;
 import com.android.inputmethod.latin.utils.RecapitalizeStatus;
 import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
 import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask;
 import com.android.inputmethod.latin.utils.TextRange;
-import com.android.inputmethod.latin.utils.Utils;
-import com.android.inputmethod.latin.utils.Utils.Stats;
 import com.android.inputmethod.research.ResearchLogger;
 
 import java.io.FileDescriptor;
@@ -1551,7 +1551,7 @@
             if (SPACE_STATE_PHANTOM == spaceState) {
                 if (mSettings.isInternal()) {
                     if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) {
-                        Stats.onAutoCorrection(
+                        LatinImeLoggerUtils.onAutoCorrection(
                                 "", mWordComposer.getTypedWord(), " ", mWordComposer);
                     }
                 }
@@ -1612,7 +1612,8 @@
         if (mWordComposer.isComposingWord()) {
             if (mSettings.isInternal()) {
                 if (mWordComposer.isBatchMode()) {
-                    Stats.onAutoCorrection("", mWordComposer.getTypedWord(), " ", mWordComposer);
+                    LatinImeLoggerUtils.onAutoCorrection(
+                            "", mWordComposer.getTypedWord(), " ", mWordComposer);
                 }
             }
             final int wordComposerSize = mWordComposer.size();
@@ -1860,7 +1861,7 @@
         } else {
             if (mLastComposedWord.canRevertCommit()) {
                 if (mSettings.isInternal()) {
-                    Stats.onAutoCorrectionCancellation();
+                    LatinImeLoggerUtils.onAutoCorrectionCancellation();
                 }
                 revertCommit();
                 return;
@@ -2031,7 +2032,7 @@
         }
         mHandler.postUpdateSuggestionStrip();
         if (mSettings.isInternal()) {
-            Utils.Stats.onNonSeparator((char)primaryCode, x, y);
+            LatinImeLoggerUtils.onNonSeparator((char)primaryCode, x, y);
         }
     }
 
@@ -2136,7 +2137,7 @@
             setPunctuationSuggestions();
         }
         if (mSettings.isInternal()) {
-            Utils.Stats.onSeparator((char)primaryCode, x, y);
+            LatinImeLoggerUtils.onSeparator((char)primaryCode, x, y);
         }
 
         mKeyboardSwitcher.updateShiftState();
@@ -2325,7 +2326,8 @@
                         + "is empty? Impossible! I must commit suicide.");
             }
             if (mSettings.isInternal()) {
-                Stats.onAutoCorrection(typedWord, autoCorrection, separatorString, mWordComposer);
+                LatinImeLoggerUtils.onAutoCorrection(
+                        typedWord, autoCorrection, separatorString, mWordComposer);
             }
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                 final SuggestedWords suggestedWords = mSuggestedWords;
@@ -2429,7 +2431,7 @@
                         && !AutoCorrection.isValidWord(mSuggest, suggestion, true);
 
         if (mSettings.isInternal()) {
-            Stats.onSeparator((char)Constants.CODE_SPACE,
+            LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE,
                     Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
         }
         if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) {
@@ -2633,7 +2635,7 @@
         }
         mConnection.commitText(originallyTypedWord + mLastComposedWord.mSeparatorString, 1);
         if (mSettings.isInternal()) {
-            Stats.onSeparator(mLastComposedWord.mSeparatorString,
+            LatinImeLoggerUtils.onSeparator(mLastComposedWord.mSeparatorString,
                     Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
         }
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -2787,7 +2789,7 @@
         final CharSequence[] items = new CharSequence[] {
                 // TODO: Should use new string "Select active input modes".
                 getString(R.string.language_selection_title),
-                getString(Utils.getAcitivityTitleResId(this, SettingsActivity.class)),
+                getString(ApplicationUtils.getAcitivityTitleResId(this, SettingsActivity.class)),
         };
         final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
             @Override
diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java
index f52e564..8c41cf8 100644
--- a/java/src/com/android/inputmethod/latin/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java
@@ -42,8 +42,8 @@
 import com.android.inputmethod.latin.userdictionary.UserDictionaryList;
 import com.android.inputmethod.latin.userdictionary.UserDictionarySettings;
 import com.android.inputmethod.latin.utils.AdditionalFeaturesSettingUtils;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
 import com.android.inputmethod.latin.utils.FeedbackUtils;
-import com.android.inputmethod.latin.utils.Utils;
 import com.android.inputmethod.research.ResearchLogger;
 import com.android.inputmethodcommon.InputMethodSettingsFragment;
 
@@ -90,7 +90,7 @@
         final PreferenceScreen preferenceScreen = getPreferenceScreen();
         if (preferenceScreen != null) {
             preferenceScreen.setTitle(
-                    Utils.getAcitivityTitleResId(getActivity(), SettingsActivity.class));
+                    ApplicationUtils.getAcitivityTitleResId(getActivity(), SettingsActivity.class));
         }
 
         final Resources res = getResources();
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 5b47dda..22beaef 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -75,6 +75,21 @@
         return mSuggestedWordInfoList.get(index);
     }
 
+    public String getDebugString(final int pos) {
+        if (!LatinImeLogger.sDBG) {
+            return null;
+        }
+        final SuggestedWordInfo wordInfo = getInfo(pos);
+        if (wordInfo == null) {
+            return null;
+        }
+        final String debugString = wordInfo.getDebugString();
+        if (TextUtils.isEmpty(debugString)) {
+            return null;
+        }
+        return debugString;
+    }
+
     public boolean willAutoCorrect() {
         return mWillAutoCorrect;
     }
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
index da58126..999ca77 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
@@ -21,7 +21,7 @@
 import android.preference.PreferenceScreen;
 
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.utils.Utils;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
 
 /**
  * Preference screen.
@@ -39,7 +39,7 @@
         addPreferencesFromResource(R.xml.spell_checker_settings);
         final PreferenceScreen preferenceScreen = getPreferenceScreen();
         if (preferenceScreen != null) {
-            preferenceScreen.setTitle(Utils.getAcitivityTitleResId(
+            preferenceScreen.setTitle(ApplicationUtils.getAcitivityTitleResId(
                     getActivity(), SpellCheckerSettingsActivity.class));
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 2218b3b..e97069d 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -24,14 +24,13 @@
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
-import com.android.inputmethod.keyboard.TypefaceUtils;
 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.keyboard.internal.KeyboardParams;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.utils.Utils;
+import com.android.inputmethod.latin.utils.TypefaceUtils;
 
 public final class MoreSuggestions extends Keyboard {
     public static final int SUGGESTION_CODE_BASE = 1024;
@@ -207,7 +206,7 @@
                 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 String info = mSuggestedWords.getDebugString(index);
                 final int indexInMoreSuggestions = index + SUGGESTION_CODE_BASE;
                 final Key key = new Key(
                         params, word, info, KeyboardIconsSet.ICON_UNDEFINED, indexInMoreSuggestions,
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
index 9565f63..ce340b6 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -45,13 +45,12 @@
 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.SuggestedWords;
 import com.android.inputmethod.latin.utils.ResourceUtils;
-import com.android.inputmethod.latin.utils.Utils;
+import com.android.inputmethod.latin.utils.ViewLayoutUtils;
 
 import java.util.ArrayList;
 
@@ -446,7 +445,7 @@
             wordView.setTextColor(getSuggestionTextColor(positionInStrip, suggestedWords));
             if (SuggestionStripView.DBG) {
                 mDebugInfoViews.get(positionInStrip).setText(
-                        Utils.getDebugInfo(suggestedWords, indexInSuggestedWords));
+                        suggestedWords.getDebugString(indexInSuggestedWords));
             }
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java b/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
new file mode 100644
index 0000000..08a2a8c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
@@ -0,0 +1,65 @@
+/*
+ * 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.utils;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.Log;
+
+public final class ApplicationUtils {
+    private static final String TAG = ApplicationUtils.class.getSimpleName();
+
+    private ApplicationUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    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 (final NameNotFoundException e) {
+            Log.e(TAG, "Failed to get settings activity title res id.", e);
+        }
+        return 0;
+    }
+
+    /**
+     * A utility method to get the application's PackageInfo.versionName
+     * @return the application's PackageInfo.versionName
+     */
+    public static String getVersionName(final Context context) {
+        try {
+            if (context == null) {
+                return "";
+            }
+            final String packageName = context.getPackageName();
+            final PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
+            return info.versionName;
+        } catch (final NameNotFoundException e) {
+            Log.e(TAG, "Could not find version info.", e);
+        }
+        return "";
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
index b3d37d7..34eccd6 100644
--- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
-import android.text.format.DateUtils;
 import android.util.Log;
 
 import com.android.inputmethod.latin.AssetFileAddress;
@@ -35,6 +34,7 @@
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.Locale;
+import java.util.concurrent.TimeUnit;
 
 /**
  * This class encapsulates the logic for the Latin-IME side of dictionary information management.
@@ -74,8 +74,8 @@
             values.put(LOCALE_COLUMN, mLocale.toString());
             values.put(DESCRIPTION_COLUMN, mDescription);
             values.put(LOCAL_FILENAME_COLUMN, mFileAddress.mFilename);
-            values.put(DATE_COLUMN,
-                    new File(mFileAddress.mFilename).lastModified() / DateUtils.SECOND_IN_MILLIS);
+            values.put(DATE_COLUMN, TimeUnit.MILLISECONDS.toSeconds(
+                    new File(mFileAddress.mFilename).lastModified()));
             values.put(FILESIZE_COLUMN, mFileAddress.mLength);
             values.put(VERSION_COLUMN, mVersion);
             return values;
diff --git a/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java b/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
new file mode 100644
index 0000000..e958a7e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
@@ -0,0 +1,77 @@
+/*
+ * 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.utils;
+
+import android.text.TextUtils;
+
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.WordComposer;
+
+public final class LatinImeLoggerUtils {
+    private LatinImeLoggerUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static void onNonSeparator(final char code, final int x, final int y) {
+        UserLogRingCharBuffer.getInstance().push(code, x, y);
+        LatinImeLogger.logOnInputChar();
+    }
+
+    public static void onSeparator(final int code, final int x, final int y) {
+        // Helper method to log a single code point separator
+        // TODO: cache this mapping of a code point to a string in a sparse array in StringUtils
+        onSeparator(new String(new int[]{code}, 0, 1), x, y);
+    }
+
+    public static void onSeparator(final String separator, final int x, final int y) {
+        final int length = separator.length();
+        for (int i = 0; i < length; i = Character.offsetByCodePoints(separator, i, 1)) {
+            int codePoint = Character.codePointAt(separator, i);
+            // TODO: accept code points
+            UserLogRingCharBuffer.getInstance().push((char)codePoint, x, y);
+        }
+        LatinImeLogger.logOnInputSeparator();
+    }
+
+    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;
+        }
+        // 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.
+        final int codePoint = TextUtils.isEmpty(separatorString) ? Constants.NOT_A_CODE
+                : separatorString.codePointAt(0);
+        if (!isBatchMode) {
+            LatinImeLogger.logOnAutoCorrectionForTyping(typedWord, correctedWord, codePoint);
+        } else {
+            if (!TextUtils.isEmpty(correctedWord)) {
+                // We must make sure that InputPointer contains only the relative timestamps,
+                // not actual timestamps.
+                LatinImeLogger.logOnAutoCorrectionForGeometric(
+                        "", correctedWord, codePoint, wordComposer.getInputPointers());
+            }
+        }
+    }
+
+    public static void onAutoCorrectionCancellation() {
+        LatinImeLogger.logOnAutoCorrectionCancelled();
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/TypefaceUtils.java b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
similarity index 96%
rename from java/src/com/android/inputmethod/keyboard/TypefaceUtils.java
rename to java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
index c3b9520..544e4d2 100644
--- a/java/src/com/android/inputmethod/keyboard/TypefaceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
@@ -14,15 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.keyboard;
+package com.android.inputmethod.latin.utils;
 
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.Typeface;
 import android.util.SparseArray;
 
-import com.android.inputmethod.latin.utils.CollectionUtils;
-
 public final class TypefaceUtils {
     private TypefaceUtils() {
         // This utility class is not publicly instantiable.
diff --git a/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java b/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
new file mode 100644
index 0000000..ef9cacf
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2016 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.utils;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.inputmethodservice.InputMethodService;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.util.Log;
+
+import com.android.inputmethod.latin.LatinImeLogger;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.channels.FileChannel;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+public final class UsabilityStudyLogUtils {
+    // TODO: remove code duplication with ResearchLog class
+    private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
+    private static final String FILENAME = "log.txt";
+    private final Handler mLoggingHandler;
+    private File mFile;
+    private File mDirectory;
+    private InputMethodService mIms;
+    private PrintWriter mWriter;
+    private final Date mDate;
+    private final SimpleDateFormat mDateFormat;
+
+    private UsabilityStudyLogUtils() {
+        mDate = new Date();
+        mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ", Locale.US);
+
+        HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        handlerThread.start();
+        mLoggingHandler = new Handler(handlerThread.getLooper());
+    }
+
+    // Initialization-on-demand holder
+    private static final class OnDemandInitializationHolder {
+        public static final UsabilityStudyLogUtils sInstance = new UsabilityStudyLogUtils();
+    }
+
+    public static UsabilityStudyLogUtils getInstance() {
+        return OnDemandInitializationHolder.sInstance;
+    }
+
+    public void init(final InputMethodService ims) {
+        mIms = ims;
+        mDirectory = ims.getFilesDir();
+    }
+
+    private void createLogFileIfNotExist() {
+        if ((mFile == null || !mFile.exists())
+                && (mDirectory != null && mDirectory.exists())) {
+            try {
+                mWriter = getPrintWriter(mDirectory, FILENAME, false);
+            } catch (final IOException e) {
+                Log.e(USABILITY_TAG, "Can't create log file.");
+            }
+        }
+    }
+
+    public static void writeBackSpace(final int x, final int y) {
+        UsabilityStudyLogUtils.getInstance().write("<backspace>\t" + x + "\t" + y);
+    }
+
+    public static void writeChar(final char c, final int x, final int y) {
+        String inputChar = String.valueOf(c);
+        switch (c) {
+            case '\n':
+                inputChar = "<enter>";
+                break;
+            case '\t':
+                inputChar = "<tab>";
+                break;
+            case ' ':
+                inputChar = "<space>";
+                break;
+        }
+        UsabilityStudyLogUtils.getInstance().write(inputChar + "\t" + x + "\t" + y);
+        LatinImeLogger.onPrintAllUsabilityStudyLogs();
+    }
+
+    public void write(final String log) {
+        mLoggingHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                createLogFileIfNotExist();
+                final long currentTime = System.currentTimeMillis();
+                mDate.setTime(currentTime);
+
+                final String printString = String.format(Locale.US, "%s\t%d\t%s\n",
+                        mDateFormat.format(mDate), currentTime, log);
+                if (LatinImeLogger.sDBG) {
+                    Log.d(USABILITY_TAG, "Write: " + log);
+                }
+                mWriter.print(printString);
+            }
+        });
+    }
+
+    private synchronized String getBufferedLogs() {
+        mWriter.flush();
+        final StringBuilder sb = new StringBuilder();
+        final BufferedReader br = getBufferedReader();
+        String line;
+        try {
+            while ((line = br.readLine()) != null) {
+                sb.append('\n');
+                sb.append(line);
+            }
+        } catch (final IOException e) {
+            Log.e(USABILITY_TAG, "Can't read log file.");
+        } finally {
+            if (LatinImeLogger.sDBG) {
+                Log.d(USABILITY_TAG, "Got all buffered logs\n" + sb.toString());
+            }
+            try {
+                br.close();
+            } catch (final IOException e) {
+                // ignore.
+            }
+        }
+        return sb.toString();
+    }
+
+    public void emailResearcherLogsAll() {
+        mLoggingHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                final Date date = new Date();
+                date.setTime(System.currentTimeMillis());
+                final String currentDateTimeString =
+                        new SimpleDateFormat("yyyyMMdd-HHmmssZ", Locale.US).format(date);
+                if (mFile == null) {
+                    Log.w(USABILITY_TAG, "No internal log file found.");
+                    return;
+                }
+                if (mIms.checkCallingOrSelfPermission(
+                            android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
+                                    != PackageManager.PERMISSION_GRANTED) {
+                    Log.w(USABILITY_TAG, "Doesn't have the permission WRITE_EXTERNAL_STORAGE");
+                    return;
+                }
+                mWriter.flush();
+                final String destPath = Environment.getExternalStorageDirectory()
+                        + "/research-" + currentDateTimeString + ".log";
+                final File destFile = new File(destPath);
+                try {
+                    final FileInputStream srcStream = new FileInputStream(mFile);
+                    final FileOutputStream destStream = new FileOutputStream(destFile);
+                    final FileChannel src = srcStream.getChannel();
+                    final FileChannel dest = destStream.getChannel();
+                    src.transferTo(0, src.size(), dest);
+                    src.close();
+                    srcStream.close();
+                    dest.close();
+                    destStream.close();
+                } catch (final FileNotFoundException e1) {
+                    Log.w(USABILITY_TAG, e1);
+                    return;
+                } catch (final IOException e2) {
+                    Log.w(USABILITY_TAG, e2);
+                    return;
+                }
+                if (destFile == null || !destFile.exists()) {
+                    Log.w(USABILITY_TAG, "Dest file doesn't exist.");
+                    return;
+                }
+                final Intent intent = new Intent(Intent.ACTION_SEND);
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                if (LatinImeLogger.sDBG) {
+                    Log.d(USABILITY_TAG, "Destination file URI is " + destFile.toURI());
+                }
+                intent.setType("text/plain");
+                intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + destPath));
+                intent.putExtra(Intent.EXTRA_SUBJECT,
+                        "[Research Logs] " + currentDateTimeString);
+                mIms.startActivity(intent);
+            }
+        });
+    }
+
+    public void printAll() {
+        mLoggingHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mIms.getCurrentInputConnection().commitText(getBufferedLogs(), 0);
+            }
+        });
+    }
+
+    public void clearAll() {
+        mLoggingHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mFile != null && mFile.exists()) {
+                    if (LatinImeLogger.sDBG) {
+                        Log.d(USABILITY_TAG, "Delete log file.");
+                    }
+                    mFile.delete();
+                    mWriter.close();
+                }
+            }
+        });
+    }
+
+    private BufferedReader getBufferedReader() {
+        createLogFileIfNotExist();
+        try {
+            return new BufferedReader(new FileReader(mFile));
+        } catch (final FileNotFoundException e) {
+            return null;
+        }
+    }
+
+    private PrintWriter getPrintWriter(final File dir, final String filename,
+            final boolean renew) throws IOException {
+        mFile = new File(dir, filename);
+        if (mFile.exists()) {
+            if (renew) {
+                mFile.delete();
+            }
+        }
+        return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
index 9f842f9..713a45b 100644
--- a/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
@@ -16,9 +16,10 @@
 
 package com.android.inputmethod.latin.utils;
 
-import android.text.format.DateUtils;
 import android.util.Log;
 
+import java.util.concurrent.TimeUnit;
+
 public final class UserHistoryForgettingCurveUtils {
     private static final String TAG = UserHistoryForgettingCurveUtils.class.getSimpleName();
     private static final boolean DEBUG = false;
@@ -27,8 +28,8 @@
     private static final int FC_LEVEL_MAX = 3;
     /* package */ static final int ELAPSED_TIME_MAX = 15;
     private static final int ELAPSED_TIME_INTERVAL_HOURS = 6;
-    private static final long ELAPSED_TIME_INTERVAL_MILLIS = ELAPSED_TIME_INTERVAL_HOURS
-            * DateUtils.HOUR_IN_MILLIS;
+    private static final long ELAPSED_TIME_INTERVAL_MILLIS =
+            TimeUnit.HOURS.toMillis(ELAPSED_TIME_INTERVAL_HOURS);
     private static final int HALF_LIFE_HOURS = 48;
     private static final int MAX_PUSH_ELAPSED = (FC_LEVEL_MAX + 1) * (ELAPSED_TIME_MAX + 1);
 
diff --git a/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java b/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java
index 3e67e82..a2c6c45 100644
--- a/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java
@@ -20,7 +20,6 @@
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.Settings;
-import com.android.inputmethod.latin.utils.Utils.UsabilityStudyLogUtils;
 
 public final class UserLogRingCharBuffer {
     public /* for test */ static final int BUFSIZE = 20;
diff --git a/java/src/com/android/inputmethod/latin/utils/Utils.java b/java/src/com/android/inputmethod/latin/utils/Utils.java
deleted file mode 100644
index c4e18ed..0000000
--- a/java/src/com/android/inputmethod/latin/utils/Utils.java
+++ /dev/null
@@ -1,374 +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.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.inputmethodservice.InputMethodService;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.WordComposer;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.nio.channels.FileChannel;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-
-// TODO: Come up with a more descriptive class name
-public final class Utils {
-    private static final String TAG = Utils.class.getSimpleName();
-
-    private Utils() {
-        // This utility class is not publicly instantiable.
-    }
-
-    // TODO: Make this an external class
-    public static final class UsabilityStudyLogUtils {
-        // TODO: remove code duplication with ResearchLog class
-        private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
-        private static final String FILENAME = "log.txt";
-        private final Handler mLoggingHandler;
-        private File mFile;
-        private File mDirectory;
-        private InputMethodService mIms;
-        private PrintWriter mWriter;
-        private final Date mDate;
-        private final SimpleDateFormat mDateFormat;
-
-        private UsabilityStudyLogUtils() {
-            mDate = new Date();
-            mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ", Locale.US);
-
-            HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task",
-                    Process.THREAD_PRIORITY_BACKGROUND);
-            handlerThread.start();
-            mLoggingHandler = new Handler(handlerThread.getLooper());
-        }
-
-        // Initialization-on-demand holder
-        private static final class OnDemandInitializationHolder {
-            public static final UsabilityStudyLogUtils sInstance = new UsabilityStudyLogUtils();
-        }
-
-        public static UsabilityStudyLogUtils getInstance() {
-            return OnDemandInitializationHolder.sInstance;
-        }
-
-        public void init(final InputMethodService ims) {
-            mIms = ims;
-            mDirectory = ims.getFilesDir();
-        }
-
-        private void createLogFileIfNotExist() {
-            if ((mFile == null || !mFile.exists())
-                    && (mDirectory != null && mDirectory.exists())) {
-                try {
-                    mWriter = getPrintWriter(mDirectory, FILENAME, false);
-                } catch (final IOException e) {
-                    Log.e(USABILITY_TAG, "Can't create log file.");
-                }
-            }
-        }
-
-        public static void writeBackSpace(final int x, final int y) {
-            UsabilityStudyLogUtils.getInstance().write("<backspace>\t" + x + "\t" + y);
-        }
-
-        public static void writeChar(final char c, final int x, final int y) {
-            String inputChar = String.valueOf(c);
-            switch (c) {
-                case '\n':
-                    inputChar = "<enter>";
-                    break;
-                case '\t':
-                    inputChar = "<tab>";
-                    break;
-                case ' ':
-                    inputChar = "<space>";
-                    break;
-            }
-            UsabilityStudyLogUtils.getInstance().write(inputChar + "\t" + x + "\t" + y);
-            LatinImeLogger.onPrintAllUsabilityStudyLogs();
-        }
-
-        public void write(final String log) {
-            mLoggingHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    createLogFileIfNotExist();
-                    final long currentTime = System.currentTimeMillis();
-                    mDate.setTime(currentTime);
-
-                    final String printString = String.format(Locale.US, "%s\t%d\t%s\n",
-                            mDateFormat.format(mDate), currentTime, log);
-                    if (LatinImeLogger.sDBG) {
-                        Log.d(USABILITY_TAG, "Write: " + log);
-                    }
-                    mWriter.print(printString);
-                }
-            });
-        }
-
-        private synchronized String getBufferedLogs() {
-            mWriter.flush();
-            final StringBuilder sb = new StringBuilder();
-            final BufferedReader br = getBufferedReader();
-            String line;
-            try {
-                while ((line = br.readLine()) != null) {
-                    sb.append('\n');
-                    sb.append(line);
-                }
-            } catch (final IOException e) {
-                Log.e(USABILITY_TAG, "Can't read log file.");
-            } finally {
-                if (LatinImeLogger.sDBG) {
-                    Log.d(USABILITY_TAG, "Got all buffered logs\n" + sb.toString());
-                }
-                try {
-                    br.close();
-                } catch (final IOException e) {
-                    // ignore.
-                }
-            }
-            return sb.toString();
-        }
-
-        public void emailResearcherLogsAll() {
-            mLoggingHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    final Date date = new Date();
-                    date.setTime(System.currentTimeMillis());
-                    final String currentDateTimeString =
-                            new SimpleDateFormat("yyyyMMdd-HHmmssZ", Locale.US).format(date);
-                    if (mFile == null) {
-                        Log.w(USABILITY_TAG, "No internal log file found.");
-                        return;
-                    }
-                    if (mIms.checkCallingOrSelfPermission(
-                                android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
-                                        != PackageManager.PERMISSION_GRANTED) {
-                        Log.w(USABILITY_TAG, "Doesn't have the permission WRITE_EXTERNAL_STORAGE");
-                        return;
-                    }
-                    mWriter.flush();
-                    final String destPath = Environment.getExternalStorageDirectory()
-                            + "/research-" + currentDateTimeString + ".log";
-                    final File destFile = new File(destPath);
-                    try {
-                        final FileInputStream srcStream = new FileInputStream(mFile);
-                        final FileOutputStream destStream = new FileOutputStream(destFile);
-                        final FileChannel src = srcStream.getChannel();
-                        final FileChannel dest = destStream.getChannel();
-                        src.transferTo(0, src.size(), dest);
-                        src.close();
-                        srcStream.close();
-                        dest.close();
-                        destStream.close();
-                    } catch (final FileNotFoundException e1) {
-                        Log.w(USABILITY_TAG, e1);
-                        return;
-                    } catch (final IOException e2) {
-                        Log.w(USABILITY_TAG, e2);
-                        return;
-                    }
-                    if (destFile == null || !destFile.exists()) {
-                        Log.w(USABILITY_TAG, "Dest file doesn't exist.");
-                        return;
-                    }
-                    final Intent intent = new Intent(Intent.ACTION_SEND);
-                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                    if (LatinImeLogger.sDBG) {
-                        Log.d(USABILITY_TAG, "Destination file URI is " + destFile.toURI());
-                    }
-                    intent.setType("text/plain");
-                    intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + destPath));
-                    intent.putExtra(Intent.EXTRA_SUBJECT,
-                            "[Research Logs] " + currentDateTimeString);
-                    mIms.startActivity(intent);
-                }
-            });
-        }
-
-        public void printAll() {
-            mLoggingHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mIms.getCurrentInputConnection().commitText(getBufferedLogs(), 0);
-                }
-            });
-        }
-
-        public void clearAll() {
-            mLoggingHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    if (mFile != null && mFile.exists()) {
-                        if (LatinImeLogger.sDBG) {
-                            Log.d(USABILITY_TAG, "Delete log file.");
-                        }
-                        mFile.delete();
-                        mWriter.close();
-                    }
-                }
-            });
-        }
-
-        private BufferedReader getBufferedReader() {
-            createLogFileIfNotExist();
-            try {
-                return new BufferedReader(new FileReader(mFile));
-            } catch (final FileNotFoundException e) {
-                return null;
-            }
-        }
-
-        private PrintWriter getPrintWriter(final File dir, final String filename,
-                final boolean renew) throws IOException {
-            mFile = new File(dir, filename);
-            if (mFile.exists()) {
-                if (renew) {
-                    mFile.delete();
-                }
-            }
-            return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */);
-        }
-    }
-
-    // TODO: Make this an external class
-    public static final class Stats {
-        public static void onNonSeparator(final char code, final int x, final int y) {
-            UserLogRingCharBuffer.getInstance().push(code, x, y);
-            LatinImeLogger.logOnInputChar();
-        }
-
-        public static void onSeparator(final int code, final int x, final int y) {
-            // Helper method to log a single code point separator
-            // TODO: cache this mapping of a code point to a string in a sparse array in StringUtils
-            onSeparator(new String(new int[]{code}, 0, 1), x, y);
-        }
-
-        public static void onSeparator(final String separator, final int x, final int y) {
-            final int length = separator.length();
-            for (int i = 0; i < length; i = Character.offsetByCodePoints(separator, i, 1)) {
-                int codePoint = Character.codePointAt(separator, i);
-                // TODO: accept code points
-                UserLogRingCharBuffer.getInstance().push((char)codePoint, x, y);
-            }
-            LatinImeLogger.logOnInputSeparator();
-        }
-
-        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;
-            }
-            // 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.
-            final int codePoint = TextUtils.isEmpty(separatorString) ? Constants.NOT_A_CODE
-                    : separatorString.codePointAt(0);
-            if (!isBatchMode) {
-                LatinImeLogger.logOnAutoCorrectionForTyping(typedWord, correctedWord, codePoint);
-            } else {
-                if (!TextUtils.isEmpty(correctedWord)) {
-                    // We must make sure that InputPointer contains only the relative timestamps,
-                    // not actual timestamps.
-                    LatinImeLogger.logOnAutoCorrectionForGeometric(
-                            "", correctedWord, codePoint, wordComposer.getInputPointers());
-                }
-            }
-        }
-
-        public static void onAutoCorrectionCancellation() {
-            LatinImeLogger.logOnAutoCorrectionCancelled();
-        }
-    }
-
-    public static String getDebugInfo(final SuggestedWords suggestions, final int pos) {
-        if (!LatinImeLogger.sDBG) {
-            return null;
-        }
-        final SuggestedWordInfo wordInfo = suggestions.getInfo(pos);
-        if (wordInfo == null) {
-            return null;
-        }
-        final String info = wordInfo.getDebugString();
-        if (TextUtils.isEmpty(info)) {
-            return null;
-        }
-        return info;
-    }
-
-    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 (final NameNotFoundException e) {
-            Log.e(TAG, "Failed to get settings activity title res id.", e);
-        }
-        return 0;
-    }
-
-    /**
-     * A utility method to get the application's PackageInfo.versionName
-     * @return the application's PackageInfo.versionName
-     */
-    public static String getVersionName(final Context context) {
-        try {
-            if (context == null) {
-                return "";
-            }
-            final String packageName = context.getPackageName();
-            final PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
-            return info.versionName;
-        } catch (final NameNotFoundException e) {
-            Log.e(TAG, "Could not find version info.", e);
-        }
-        return "";
-    }
-}
diff --git a/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java b/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java
similarity index 86%
rename from java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java
rename to java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java
index dc12fa4..f9d8534 100644
--- a/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.keyboard;
+package com.android.inputmethod.latin.utils;
 
 import android.view.View;
 import android.view.ViewGroup;
@@ -27,7 +27,8 @@
         // This utility class is not publicly instantiable.
     }
 
-    public static MarginLayoutParams newLayoutParam(ViewGroup placer, int width, int height) {
+    public static MarginLayoutParams newLayoutParam(final ViewGroup placer, final int width,
+            final int height) {
         if (placer instanceof FrameLayout) {
             return new FrameLayout.LayoutParams(width, height);
         } else if (placer instanceof RelativeLayout) {
@@ -40,7 +41,8 @@
         }
     }
 
-    public static void placeViewAt(View view, int x, int y, int w, int h) {
+    public static void placeViewAt(final View view, final int x, final int y, final int w,
+            final int h) {
         final ViewGroup.LayoutParams lp = view.getLayoutParams();
         if (lp instanceof MarginLayoutParams) {
             final MarginLayoutParams marginLayoutParams = (MarginLayoutParams)lp;
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index 88207c0..46e620a 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -55,7 +55,7 @@
     private static final String TAG = ResearchLog.class.getSimpleName();
     private static final boolean DEBUG = false
             && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
-    private static final long FLUSH_DELAY_IN_MS = 1000 * 5;
+    private static final long FLUSH_DELAY_IN_MS = TimeUnit.SECONDS.toMillis(5);
 
     /* package */ final ScheduledExecutorService mExecutor;
     /* package */ final File mFile;
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 06a21bc..98b58c4 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -37,7 +37,6 @@
 import android.os.SystemClock;
 import android.preference.PreferenceManager;
 import android.text.TextUtils;
-import android.text.format.DateUtils;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -75,6 +74,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
 
 // TODO: Add a unit test for every "logging" method (i.e. that is called from the IME and calls
@@ -137,10 +137,10 @@
     private static final int MAX_INPUTVIEW_LENGTH_TO_CAPTURE = 8192; // must be >=1
     private static final String PREF_RESEARCH_SAVED_CHANNEL = "pref_research_saved_channel";
 
-    private static final long RESEARCHLOG_CLOSE_TIMEOUT_IN_MS = 5 * 1000;
-    private static final long RESEARCHLOG_ABORT_TIMEOUT_IN_MS = 5 * 1000;
-    private static final long DURATION_BETWEEN_DIR_CLEANUP_IN_MS = DateUtils.DAY_IN_MILLIS;
-    private static final long MAX_LOGFILE_AGE_IN_MS = 4 * DateUtils.DAY_IN_MILLIS;
+    private static final long RESEARCHLOG_CLOSE_TIMEOUT_IN_MS = TimeUnit.SECONDS.toMillis(5);
+    private static final long RESEARCHLOG_ABORT_TIMEOUT_IN_MS = TimeUnit.SECONDS.toMillis(5);
+    private static final long DURATION_BETWEEN_DIR_CLEANUP_IN_MS = TimeUnit.DAYS.toMillis(1);
+    private static final long MAX_LOGFILE_AGE_IN_MS = TimeUnit.DAYS.toMillis(4);
 
     private static final ResearchLogger sInstance = new ResearchLogger();
     private static String sAccountType = null;
@@ -195,7 +195,7 @@
     // not performed on text that the user types into the feedback dialog.
     private boolean mInFeedbackDialog = false;
     private Handler mUserRecordingTimeoutHandler;
-    private static final long USER_RECORDING_TIMEOUT_MS = 30L * DateUtils.SECOND_IN_MILLIS;
+    private static final long USER_RECORDING_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30);
 
     // Stores a temporary LogUnit while generating a phantom space.  Needed because phantom spaces
     // are issued out-of-order, immediately before the characters generated by other operations that
@@ -542,8 +542,8 @@
             toast.show();
             boolean isLogDeleted = abort();
             final long currentTime = System.currentTimeMillis();
-            final long resumeTime = currentTime + 1000 * 60 *
-                    SUSPEND_DURATION_IN_MINUTES;
+            final long resumeTime = currentTime
+                    + TimeUnit.MINUTES.toMillis(SUSPEND_DURATION_IN_MINUTES);
             suspendLoggingUntil(resumeTime);
             toast.cancel();
             Toast.makeText(latinIME, R.string.research_notify_logging_suspended,
@@ -635,7 +635,7 @@
                             mMotionEventReader.readMotionEventData(mUserRecordingFile);
                     mReplayer.replay(replayData, null);
                 }
-            }, 1000);
+            }, TimeUnit.SECONDS.toMillis(1));
         }
 
         if (FEEDBACK_DIALOG_SHOULD_PRESERVE_TEXT_FIELD) {
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
index e573ca0..fd323a1 100644
--- a/java/src/com/android/inputmethod/research/Statistics.java
+++ b/java/src/com/android/inputmethod/research/Statistics.java
@@ -21,6 +21,8 @@
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.define.ProductionFlag;
 
+import java.util.concurrent.TimeUnit;
+
 public class Statistics {
     private static final String TAG = Statistics.class.getSimpleName();
     private static final boolean DEBUG = false
@@ -102,8 +104,8 @@
 
     // To account for the interruptions when the user's attention is directed elsewhere, times
     // longer than MIN_TYPING_INTERMISSION are not counted when estimating this statistic.
-    public static final int MIN_TYPING_INTERMISSION = 2 * 1000;  // in milliseconds
-    public static final int MIN_DELETION_INTERMISSION = 10 * 1000;  // in milliseconds
+    public static final long MIN_TYPING_INTERMISSION = TimeUnit.SECONDS.toMillis(2);
+    public static final long MIN_DELETION_INTERMISSION = TimeUnit.SECONDS.toMillis(10);
 
     // The last time that a tap was performed
     private long mLastTapTime;
diff --git a/java/src/com/android/inputmethod/research/UploaderService.java b/java/src/com/android/inputmethod/research/UploaderService.java
index 8bd46c1..fd3f2f6 100644
--- a/java/src/com/android/inputmethod/research/UploaderService.java
+++ b/java/src/com/android/inputmethod/research/UploaderService.java
@@ -34,7 +34,6 @@
     public static final long RUN_INTERVAL = AlarmManager.INTERVAL_HOUR;
     public static final String EXTRA_UPLOAD_UNCONDITIONALLY = UploaderService.class.getName()
             + ".extra.UPLOAD_UNCONDITIONALLY";
-    protected static final int TIMEOUT_IN_MS = 1000 * 4;
 
     public UploaderService() {
         super("Research Uploader Service");
diff --git a/native/jni/src/suggest/core/dicnode/dic_node.h b/native/jni/src/suggest/core/dicnode/dic_node.h
index 017df34..be40c9d 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node.h
@@ -112,32 +112,23 @@
         mIsUsed = true;
         mIsCachedForNextSuggestion = false;
         mDicNodeProperties.init(
-                NOT_A_DICT_POS, 0 /* flags */, rootGroupPos, NOT_A_DICT_POS /* attributesPos */,
+                NOT_A_DICT_POS, rootGroupPos, NOT_A_DICT_POS /* attributesPos */,
                 NOT_A_CODE_POINT /* nodeCodePoint */, NOT_A_PROBABILITY /* probability */,
-                false /* isTerminal */, true /* hasChildren */, 0 /* depth */,
-                0 /* terminalDepth */);
+                false /* isTerminal */, true /* hasChildren */,
+                false /* isBlacklistedOrNotAWord */, 0 /* depth */, 0 /* terminalDepth */);
         mDicNodeState.init(prevWordNodePos);
         PROF_NODE_RESET(mProfiler);
     }
 
-    void initAsPassingChild(DicNode *parentNode) {
-        mIsUsed = true;
-        mIsCachedForNextSuggestion = parentNode->mIsCachedForNextSuggestion;
-        const int c = parentNode->getNodeTypedCodePoint();
-        mDicNodeProperties.init(&parentNode->mDicNodeProperties, c);
-        mDicNodeState.init(&parentNode->mDicNodeState);
-        PROF_NODE_COPY(&parentNode->mProfiler, mProfiler);
-    }
-
     // Init for root with previous word
     void initAsRootWithPreviousWord(DicNode *dicNode, const int rootGroupPos) {
         mIsUsed = true;
         mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
         mDicNodeProperties.init(
-                NOT_A_DICT_POS,  0 /* flags */, rootGroupPos, NOT_A_DICT_POS /* attributesPos */,
+                NOT_A_DICT_POS, rootGroupPos, NOT_A_DICT_POS /* attributesPos */,
                 NOT_A_CODE_POINT /* nodeCodePoint */, NOT_A_PROBABILITY /* probability */,
-                false /* isTerminal */, true /* hasChildren */, 0 /* depth */,
-                0 /* terminalDepth */);
+                false /* isTerminal */, true /* hasChildren */,
+                false /* isBlacklistedOrNotAWord */,  0 /* depth */, 0 /* terminalDepth */);
         // TODO: Move to dicNodeState?
         mDicNodeState.mDicNodeStateOutput.init(); // reset for next word
         mDicNodeState.mDicNodeStateInput.init(
@@ -157,18 +148,27 @@
         PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
     }
 
-    // TODO: minimize arguments by looking binary_format
-    void initAsChild(DicNode *dicNode, const int pos, const uint8_t flags, const int childrenPos,
+    void initAsPassingChild(DicNode *parentNode) {
+        mIsUsed = true;
+        mIsCachedForNextSuggestion = parentNode->mIsCachedForNextSuggestion;
+        const int c = parentNode->getNodeTypedCodePoint();
+        mDicNodeProperties.init(&parentNode->mDicNodeProperties, c);
+        mDicNodeState.init(&parentNode->mDicNodeState);
+        PROF_NODE_COPY(&parentNode->mProfiler, mProfiler);
+    }
+
+    void initAsChild(DicNode *dicNode, const int pos, const int childrenPos,
             const int attributesPos, const int probability, const bool isTerminal,
-            const bool hasChildren, const uint16_t mergedNodeCodePointCount,
-            const int *const mergedNodeCodePoints) {
+            const bool hasChildren, const bool isBlacklistedOrNotAWord,
+            const uint16_t mergedNodeCodePointCount, const int *const mergedNodeCodePoints) {
         mIsUsed = true;
         uint16_t newDepth = static_cast<uint16_t>(dicNode->getNodeCodePointCount() + 1);
         mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
         const uint16_t newLeavingDepth = static_cast<uint16_t>(
                 dicNode->mDicNodeProperties.getLeavingDepth() + mergedNodeCodePointCount);
-        mDicNodeProperties.init(pos, flags, childrenPos, attributesPos, mergedNodeCodePoints[0],
-                probability, isTerminal, hasChildren, newDepth, newLeavingDepth);
+        mDicNodeProperties.init(pos, childrenPos, attributesPos, mergedNodeCodePoints[0],
+                probability, isTerminal, hasChildren, isBlacklistedOrNotAWord, newDepth,
+                newLeavingDepth);
         mDicNodeState.init(&dicNode->mDicNodeState, mergedNodeCodePointCount,
                 mergedNodeCodePoints);
         PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
@@ -216,7 +216,7 @@
     }
 
     bool isImpossibleBigramWord() const {
-        if (mDicNodeProperties.hasBlacklistedOrNotAWordFlag()) {
+        if (isBlacklistedOrNotAWord()) {
             return true;
         }
         const int prevWordLen = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength()
@@ -463,8 +463,8 @@
         return mDicNodeState.mDicNodeStateScoring.isExactMatch();
     }
 
-    uint8_t getFlags() const {
-        return mDicNodeProperties.getFlags();
+    bool isBlacklistedOrNotAWord() const {
+        return mDicNodeProperties.isBlacklistedOrNotAWord();
     }
 
     int getAttributesPos() const {
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 7e8aa49..d98000d 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_properties.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_properties.h
@@ -20,7 +20,6 @@
 #include <stdint.h>
 
 #include "defines.h"
-#include "suggest/core/dictionary/binary_format.h"
 
 namespace latinime {
 
@@ -32,24 +31,25 @@
 class DicNodeProperties {
  public:
     AK_FORCE_INLINE DicNodeProperties()
-            : mPos(0), mFlags(0), mChildrenPos(0), mAttributesPos(0), mProbability(0),
-              mNodeCodePoint(0), mDepth(0), mLeavingDepth(0), mIsTerminal(false),
-              mHasChildren(false) {}
+            : mPos(0), mChildrenPos(0), mAttributesPos(0), mProbability(0),
+              mNodeCodePoint(0), mIsTerminal(false), mHasChildren(false),
+              mIsBlacklistedOrNotAWord(false), mDepth(0), mLeavingDepth(0) {}
 
     virtual ~DicNodeProperties() {}
 
     // Should be called only once per DicNode is initialized.
-    void init(const int pos, const uint8_t flags, const int childrenPos, const int attributesPos,
+    void init(const int pos, const int childrenPos, const int attributesPos,
             const int nodeCodePoint, const int probability, const bool isTerminal,
-            const bool hasChildren, const uint16_t depth, const uint16_t leavingDepth) {
+            const bool hasChildren, const bool isBlacklistedOrNotAWord,
+            const uint16_t depth, const uint16_t leavingDepth) {
         mPos = pos;
-        mFlags = flags;
         mChildrenPos = childrenPos;
         mAttributesPos = attributesPos;
         mNodeCodePoint = nodeCodePoint;
         mProbability = probability;
         mIsTerminal = isTerminal;
         mHasChildren = hasChildren;
+        mIsBlacklistedOrNotAWord = isBlacklistedOrNotAWord;
         mDepth = depth;
         mLeavingDepth = leavingDepth;
     }
@@ -57,13 +57,13 @@
     // Init for copy
     void init(const DicNodeProperties *const nodeProp) {
         mPos = nodeProp->mPos;
-        mFlags = nodeProp->mFlags;
         mChildrenPos = nodeProp->mChildrenPos;
         mAttributesPos = nodeProp->mAttributesPos;
         mNodeCodePoint = nodeProp->mNodeCodePoint;
         mProbability = nodeProp->mProbability;
         mIsTerminal = nodeProp->mIsTerminal;
         mHasChildren = nodeProp->mHasChildren;
+        mIsBlacklistedOrNotAWord = nodeProp->mIsBlacklistedOrNotAWord;
         mDepth = nodeProp->mDepth;
         mLeavingDepth = nodeProp->mLeavingDepth;
     }
@@ -71,13 +71,13 @@
     // Init as passing child
     void init(const DicNodeProperties *const nodeProp, const int codePoint) {
         mPos = nodeProp->mPos;
-        mFlags = nodeProp->mFlags;
         mChildrenPos = nodeProp->mChildrenPos;
         mAttributesPos = nodeProp->mAttributesPos;
         mNodeCodePoint = codePoint; // Overwrite the node char of a passing child
         mProbability = nodeProp->mProbability;
         mIsTerminal = nodeProp->mIsTerminal;
         mHasChildren = nodeProp->mHasChildren;
+        mIsBlacklistedOrNotAWord = nodeProp->mIsBlacklistedOrNotAWord;
         mDepth = nodeProp->mDepth + 1; // Increment the depth of a passing child
         mLeavingDepth = nodeProp->mLeavingDepth;
     }
@@ -86,10 +86,6 @@
         return mPos;
     }
 
-    uint8_t getFlags() const {
-        return mFlags;
-    }
-
     int getChildrenPos() const {
         return mChildrenPos;
     }
@@ -123,8 +119,8 @@
         return mHasChildren || mDepth != mLeavingDepth;
     }
 
-    bool hasBlacklistedOrNotAWordFlag() const {
-        return BinaryFormat::hasBlacklistedOrNotAWordFlag(mFlags);
+    bool isBlacklistedOrNotAWord() const {
+        return mIsBlacklistedOrNotAWord;
     }
 
  private:
@@ -132,15 +128,15 @@
     // Use a default copy constructor and an assign operator because shallow copies are ok
     // for this class
     int mPos;
-    uint8_t mFlags;
     int mChildrenPos;
     int mAttributesPos;
     int mProbability;
     int mNodeCodePoint;
-    uint16_t mDepth;
-    uint16_t mLeavingDepth;
     bool mIsTerminal;
     bool mHasChildren;
+    bool mIsBlacklistedOrNotAWord;
+    uint16_t mDepth;
+    uint16_t mLeavingDepth;
 };
 } // namespace latinime
 #endif // LATINIME_DIC_NODE_PROPERTIES_H
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 c7c8d2a..6c7f666 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
@@ -78,6 +78,7 @@
     const bool isTerminal = (0 != (BinaryFormat::FLAG_IS_TERMINAL & flags));
     const bool hasChildren = BinaryFormat::hasChildrenInFlags(flags);
     const bool hasShortcuts = (0 != (BinaryFormat::FLAG_HAS_SHORTCUT_TARGETS & flags));
+    const bool isBlacklistedOrNotAWord = BinaryFormat::hasBlacklistedOrNotAWordFlag(flags);
 
     int codePoint = BinaryFormat::getCodePointAndForwardPointer(
             binaryDictionaryInfo->getDictRoot(), &pos);
@@ -111,8 +112,9 @@
     if (childrenFilter->isFilteredOut(mergedNodeCodePoints[0])) {
         return siblingPos;
     }
-    childDicNodes->pushLeavingChild(dicNode, nextPos, flags, childrenPos, attributesPos,
-            probability, isTerminal, hasChildren, mergedNodeCodePointCount, mergedNodeCodePoints);
+    childDicNodes->pushLeavingChild(dicNode, nextPos, childrenPos, attributesPos,
+            probability, isTerminal, hasChildren, isBlacklistedOrNotAWord,
+            mergedNodeCodePointCount, mergedNodeCodePoints);
     return siblingPos;
 }
 
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 9641cc1..5ac4eea 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_vector.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_vector.h
@@ -62,14 +62,15 @@
         mDicNodes.back().initAsPassingChild(dicNode);
     }
 
-    void pushLeavingChild(DicNode *dicNode, const int pos, const uint8_t flags,
-            const int childrenPos, const int attributesPos, const int probability,
-            const bool isTerminal, const bool hasChildren, const uint16_t mergedNodeCodePointCount,
-            const int *const mergedNodeCodePoints) {
+    void pushLeavingChild(DicNode *dicNode, const int pos, const int childrenPos,
+            const int attributesPos, const int probability, const bool isTerminal,
+            const bool hasChildren, const bool isBlacklistedOrNotAWord,
+            const uint16_t mergedNodeCodePointCount, const int *const mergedNodeCodePoints) {
         ASSERT(!mLock);
         mDicNodes.push_back(mEmptyNode);
-        mDicNodes.back().initAsChild(dicNode, pos, flags, childrenPos, attributesPos, probability,
-                isTerminal, hasChildren, mergedNodeCodePointCount, mergedNodeCodePoints);
+        mDicNodes.back().initAsChild(dicNode, pos, childrenPos, attributesPos, probability,
+                isTerminal, hasChildren, isBlacklistedOrNotAWord, mergedNodeCodePointCount,
+                mergedNodeCodePoints);
     }
 
     DicNode *operator[](const int id) {
diff --git a/native/jni/src/suggest/core/dictionary/terminal_attributes.h b/native/jni/src/suggest/core/dictionary/terminal_attributes.h
index a8520b1..0da6504 100644
--- a/native/jni/src/suggest/core/dictionary/terminal_attributes.h
+++ b/native/jni/src/suggest/core/dictionary/terminal_attributes.h
@@ -21,7 +21,6 @@
 
 #include "suggest/core/dictionary/binary_dictionary_info.h"
 #include "suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h"
-#include "suggest/core/dictionary/binary_format.h"
 
 namespace latinime {
 
@@ -71,13 +70,12 @@
     };
 
     TerminalAttributes(const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const uint8_t nodeFlags, const int shortcutPos)
-            : mBinaryDictionaryInfo(binaryDictionaryInfo),
-              mNodeFlags(nodeFlags), mShortcutListSizePos(shortcutPos) {}
+            const int shortcutPos)
+            : mBinaryDictionaryInfo(binaryDictionaryInfo), mShortcutListSizePos(shortcutPos) {}
 
     inline ShortcutIterator getShortcutIterator() const {
         int shortcutPos = mShortcutListSizePos;
-        const bool hasShortcutList = 0 != (mNodeFlags & BinaryFormat::FLAG_HAS_SHORTCUT_TARGETS);
+        const bool hasShortcutList = shortcutPos != NOT_A_DICT_POS;
         if (hasShortcutList) {
             BinaryDictionaryTerminalAttributesReadingUtils::getShortcutListSizeAndForwardPointer(
                     mBinaryDictionaryInfo, &shortcutPos);
@@ -86,14 +84,9 @@
         return ShortcutIterator(mBinaryDictionaryInfo, shortcutPos, hasShortcutList);
     }
 
-    bool isBlacklistedOrNotAWord() const {
-        return BinaryFormat::hasBlacklistedOrNotAWordFlag(mNodeFlags);
-    }
-
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(TerminalAttributes);
     const BinaryDictionaryInfo *const mBinaryDictionaryInfo;
-    const uint8_t mNodeFlags;
     const int mShortcutListSizePos;
 };
 } // namespace latinime
diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp
index 9a0f10c..6e9aff5 100644
--- a/native/jni/src/suggest/core/suggest.cpp
+++ b/native/jni/src/suggest/core/suggest.cpp
@@ -173,8 +173,6 @@
                 terminalIndex, doubleLetterTerminalIndex, doubleLetterLevel);
         const float compoundDistance = terminalDicNode->getCompoundDistance(languageWeight)
                 + doubleLetterCost;
-        const TerminalAttributes terminalAttributes(traverseSession->getBinaryDictionaryInfo(),
-                terminalDicNode->getFlags(), terminalDicNode->getAttributesPos());
         const bool isPossiblyOffensiveWord = terminalDicNode->getProbability() <= 0;
         const bool isExactMatch = terminalDicNode->isExactMatch();
         const bool isFirstCharUppercase = terminalDicNode->isFirstCharUppercase();
@@ -187,7 +185,7 @@
                 | (isSafeExactMatch ? Dictionary::KIND_FLAG_EXACT_MATCH : 0);
 
         // Entries that are blacklisted or do not represent a word should not be output.
-        const bool isValidWord = !terminalAttributes.isBlacklistedOrNotAWord();
+        const bool isValidWord = !terminalDicNode->isBlacklistedOrNotAWord();
 
         // Increase output score of top typing suggestion to ensure autocorrection.
         // TODO: Better integration with java side autocorrection logic.
@@ -233,6 +231,8 @@
         }
 
         if (!terminalDicNode->hasMultipleWords()) {
+            const TerminalAttributes terminalAttributes(traverseSession->getBinaryDictionaryInfo(),
+                    terminalDicNode->getAttributesPos());
             // Shortcut is not supported for multiple words suggestions.
             // TODO: Check shortcuts during traversal for multiple words suggestions.
             const bool sameAsTyped = TRAVERSAL->sameAsTyped(traverseSession, terminalDicNode);