Merge "Remove Row object from MiniKeyboardBuilder"
diff --git a/java/Android.mk b/java/Android.mk
index be3b0b9..60c321a 100644
--- a/java/Android.mk
+++ b/java/Android.mk
@@ -13,7 +13,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := android-common
 
-#LOCAL_AAPT_FLAGS := -0 .dict
+# Do not compress dictionary files to mmap dict data runtime
+LOCAL_AAPT_FLAGS := -0 .dict
 
 # Include all the resources regardless of system supported locales
 LOCAL_AAPT_INCLUDE_ALL_RESOURCES := true
diff --git a/java/res/xml/dictionary.xml b/java/res/xml/dictionary.xml
deleted file mode 100644
index 7b770a8..0000000
--- a/java/res/xml/dictionary.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 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.
-*/
--->
-
-<dictionary>
-    <part name = "main" />
-</dictionary>
\ No newline at end of file
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index 777a795..e7a9d85 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -79,8 +79,7 @@
      *
      * @return Allocates and returns an array that can hold all key indices returned by
      *         {@link #getKeyIndexAndNearbyCodes} method. All elements in the returned array are
-     *         initialized by {@link com.android.inputmethod.latin.LatinKeyboardView.NOT_A_KEY}
-     *         value.
+     *         initialized by {@link #NOT_A_KEY} value.
      */
     public int[] newCodeArray() {
         int[] codes = new int[getMaxNearbyKeys()];
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
index 00f3a51..734a55a 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
@@ -19,64 +19,56 @@
 public interface KeyboardActionListener {
 
     /**
-     * Called when the user presses a key. This is sent before the
-     * {@link #onCodeInput} is called. For keys that repeat, this is only
-     * called once.
+     * Called when the user presses a key. This is sent before the {@link #onCodeInput} is called.
+     * For keys that repeat, this is only called once.
      *
-     * @param primaryCode
-     *            the unicode of the key being pressed. If the touch is
-     *            not on a valid key, the value will be zero.
+     * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid key,
+     *            the value will be zero.
      */
-    void onPress(int primaryCode);
+    public void onPress(int primaryCode);
 
     /**
-     * Called when the user releases a key. This is sent after the
-     * {@link #onCodeInput} is called. For keys that repeat, this is only
-     * called once.
+     * Called when the user releases a key. This is sent after the {@link #onCodeInput} is called.
+     * For keys that repeat, this is only called once.
      *
-     * @param primaryCode
-     *            the code of the key that was released
+     * @param primaryCode the code of the key that was released
      */
-    void onRelease(int primaryCode);
+    public void onRelease(int primaryCode);
 
     /**
      * Send a key code to the listener.
      *
-     * @param primaryCode
-     *            this is the code of the key that was pressed
-     * @param keyCodes
-     *            the codes for all the possible alternative keys with
-     *            the primary code being the first. If the primary key
-     *            code is a single character such as an alphabet or
-     *            number or symbol, the alternatives will include other
-     *            characters that may be on the same key or adjacent
-     *            keys. These codes are useful to correct for
-     *            accidental presses of a key adjacent to the intended
-     *            key.
-     * @param x
-     *            x-coordinate pixel of touched event. If {@link #onCodeInput} is not called by
-     *            onTouchEvent, the value should be NOT_A_TOUCH_COORDINATE.
-     * @param y
-     *            y-coordinate pixel of touched event. If {@link #onCodeInput} is not called by
-     *            onTouchEvent, the value should be NOT_A_TOUCH_COORDINATE.
+     * @param primaryCode this is the code of the key that was pressed
+     * @param keyCodes the codes for all the possible alternative keys with the primary code being
+     *            the first. If the primary key code is a single character such as an alphabet or
+     *            number or symbol, the alternatives will include other characters that may be on
+     *            the same key or adjacent keys. These codes are useful to correct for accidental
+     *            presses of a key adjacent to the intended key.
+     * @param x x-coordinate pixel of touched event. If {@link #onCodeInput} is not called by
+     *            {@link PointerTracker#onTouchEvent} or so, the value should be
+     *            {@link #NOT_A_TOUCH_COORDINATE}.
+     * @param y y-coordinate pixel of touched event. If {@link #onCodeInput} is not called by
+     *            {@link PointerTracker#onTouchEvent} or so, the value should be
+     *            {@link #NOT_A_TOUCH_COORDINATE}.
      */
-    void onCodeInput(int primaryCode, int[] keyCodes, int x, int y);
+    public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y);
+
+    public static final int NOT_A_TOUCH_COORDINATE = -1;
 
     /**
      * Sends a sequence of characters to the listener.
      *
-     * @param text
-     *            the sequence of characters to be displayed.
+     * @param text the sequence of characters to be displayed.
      */
-    void onTextInput(CharSequence text);
+    public void onTextInput(CharSequence text);
 
     /**
      * Called when user released a finger outside any key.
      */
-    void onCancelInput();
+    public void onCancelInput();
 
     /**
      * Called when the user quickly moves the finger from up to down.
      */
-    void onSwipeDown();
+    public void onSwipeDown();
 }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index e0835d7..b259e0c 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -79,8 +79,6 @@
     public static final int COLOR_SCHEME_WHITE = 0;
     public static final int COLOR_SCHEME_BLACK = 1;
 
-    public static final int NOT_A_TOUCH_COORDINATE = -1;
-
     // Timing constants
     private final int mKeyRepeatInterval;
 
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index cb3b430..6b052c7 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -95,8 +95,8 @@
 
     private boolean invokeOnKey(int primaryCode) {
         getOnKeyboardActionListener().onCodeInput(primaryCode, null,
-                KeyboardView.NOT_A_TOUCH_COORDINATE,
-                KeyboardView.NOT_A_TOUCH_COORDINATE);
+                KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
+                KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
         return true;
     }
 
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 961b49f..4bb64ee 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -71,8 +71,8 @@
      * @param context application context for reading resources
      * @param resId the resource containing the raw binary dictionary
      */
-    public BinaryDictionary(Context context, int[] resId, int dicTypeId) {
-        if (resId != null && resId.length > 0 && resId[0] != 0) {
+    public BinaryDictionary(Context context, int resId, int dicTypeId) {
+        if (resId != 0) {
             loadDictionary(context, resId);
         }
         mDicTypeId = dicTypeId;
@@ -104,30 +104,21 @@
             int fullWordMultiplier, int maxWordLength, int maxWords, int maxAlternatives);
     private native void closeNative(int dict);
     private native boolean isValidWordNative(int nativeData, char[] word, int wordLength);
-    private native int getSuggestionsNative(int dict, int[] inputCodes, int codesSize, 
+    private native int getSuggestionsNative(int dict, int[] inputCodes, int codesSize,
             char[] outputChars, int[] frequencies,
             int[] nextLettersFrequencies, int nextLettersSize);
     private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength,
             int[] inputCodes, int inputCodesLength, char[] outputChars, int[] frequencies,
             int maxWordLength, int maxBigrams, int maxAlternatives);
 
-    private final void loadDictionary(Context context, int[] resId) {
-        InputStream[] is = null;
+    private final void loadDictionary(Context context, int resId) {
+        InputStream is = null;
         try {
-            // merging separated dictionary into one if dictionary is separated
-            int total = 0;
-            is = new InputStream[resId.length];
-            for (int i = 0; i < resId.length; i++) {
-                is[i] = context.getResources().openRawResource(resId[i]);
-                total += is[i].available();
-            }
-
+            is = context.getResources().openRawResource(resId);
+            final int total = is.available();
             mNativeDictDirectBuffer =
                 ByteBuffer.allocateDirect(total).order(ByteOrder.nativeOrder());
-            int got = 0;
-            for (int i = 0; i < resId.length; i++) {
-                 got += Channels.newChannel(is[i]).read(mNativeDictDirectBuffer);
-            }
+            final int got = Channels.newChannel(is).read(mNativeDictDirectBuffer);
             if (got != total) {
                 Log.e(TAG, "Read " + got + " bytes, expected " + total);
             } else {
@@ -140,11 +131,7 @@
             Log.w(TAG, "No available memory for binary dictionary");
         } finally {
             try {
-                if (is != null) {
-                    for (int i = 0; i < is.length; i++) {
-                        is[i].close();
-                    }
-                }
+                if (is != null) is.close();
             } catch (IOException e) {
                 Log.w(TAG, "Failed to close input stream");
             }
diff --git a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
index 27e0fbe..faee38e 100644
--- a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
+++ b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
@@ -99,15 +99,15 @@
     }
 
     private boolean hasDictionary(Locale locale) {
-        Resources res = getResources();
-        Configuration conf = res.getConfiguration();
-        Locale saveLocale = conf.locale;
+        final Resources res = getResources();
+        final Configuration conf = res.getConfiguration();
+        final Locale saveLocale = conf.locale;
         boolean haveDictionary = false;
         conf.locale = locale;
         res.updateConfiguration(conf, res.getDisplayMetrics());
 
-        int[] dictionaries = LatinIME.getDictionary(res);
-        BinaryDictionary bd = new BinaryDictionary(this, dictionaries, Suggest.DIC_MAIN);
+        int mainDicResId = LatinIME.getMainDictionaryResourceId(res);
+        BinaryDictionary bd = new BinaryDictionary(this, mainDicResId, Suggest.DIC_MAIN);
 
         // Is the dictionary larger than a placeholder? Arbitrarily chose a lower limit of
         // 4000-5000 words, whereas the LARGE_DICTIONARY is about 20000+ words.
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index e785eb7..be98f4c 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -347,49 +347,19 @@
     }
 
     /**
-     * Loads a dictionary or multiple separated dictionary
-     * @return returns array of dictionary resource ids
+     * Returns a main dictionary resource id
+     * @return main dictionary resource id
      */
-    public static int[] getDictionary(Resources res) {
+    public static int getMainDictionaryResourceId(Resources res) {
+        final String MAIN_DIC_NAME = "main";
         String packageName = LatinIME.class.getPackage().getName();
-        XmlResourceParser xrp = res.getXml(R.xml.dictionary);
-        ArrayList<Integer> dictionaries = new ArrayList<Integer>();
-
-        try {
-            int current = xrp.getEventType();
-            while (current != XmlPullParser.END_DOCUMENT) {
-                if (current == XmlPullParser.START_TAG) {
-                    String tag = xrp.getName();
-                    if (tag != null) {
-                        if (tag.equals("part")) {
-                            String dictFileName = xrp.getAttributeValue(null, "name");
-                            dictionaries.add(res.getIdentifier(dictFileName, "raw", packageName));
-                        }
-                    }
-                }
-                xrp.next();
-                current = xrp.getEventType();
-            }
-        } catch (XmlPullParserException e) {
-            Log.e(TAG, "Dictionary XML parsing failure");
-        } catch (IOException e) {
-            Log.e(TAG, "Dictionary XML IOException");
-        }
-
-        int count = dictionaries.size();
-        int[] dict = new int[count];
-        for (int i = 0; i < count; i++) {
-            dict[i] = dictionaries.get(i);
-        }
-
-        return dict;
+        return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName);
     }
 
     private void initSuggest() {
         updateAutoTextEnabled();
         String locale = mSubtypeSwitcher.getInputLocaleStr();
 
-        Resources orig = getResources();
         Locale savedLocale = mSubtypeSwitcher.changeSystemLocale(new Locale(locale));
         if (mSuggest != null) {
             mSuggest.close();
@@ -397,8 +367,9 @@
         final SharedPreferences prefs = mPrefs;
         mQuickFixes = prefs.getBoolean(Settings.PREF_QUICK_FIXES, true);
 
-        int[] dictionaries = getDictionary(orig);
-        mSuggest = new Suggest(this, dictionaries);
+        final Resources res = mResources;
+        int mainDicResId = getMainDictionaryResourceId(res);
+        mSuggest = new Suggest(this, mainDicResId);
         loadAndSetAutoCorrectionThreshold(prefs);
         if (mUserDictionary != null) mUserDictionary.close();
         mUserDictionary = new UserDictionary(this, locale);
@@ -418,8 +389,8 @@
         mSuggest.setContactsDictionary(mContactsDictionary);
         mSuggest.setAutoDictionary(mAutoDictionary);
         updateCorrectionMode();
-        mWordSeparators = mResources.getString(R.string.word_separators);
-        mSentenceSeparators = mResources.getString(R.string.sentence_separators);
+        mWordSeparators = res.getString(R.string.word_separators);
+        mSentenceSeparators = res.getString(R.string.sentence_separators);
 
         mSubtypeSwitcher.changeSystemLocale(savedLocale);
     }
@@ -859,10 +830,11 @@
 
     @Override
     public boolean onEvaluateFullscreenMode() {
-        DisplayMetrics dm = getResources().getDisplayMetrics();
+        final Resources res = mResources;
+        DisplayMetrics dm = res.getDisplayMetrics();
         float displayHeight = dm.heightPixels;
         // If the display is more than X inches high, don't go to fullscreen mode
-        float dimen = getResources().getDimension(R.dimen.max_height_for_fullscreen);
+        float dimen = res.getDimension(R.dimen.max_height_for_fullscreen);
         if (displayHeight > dimen) {
             return false;
         } else {
@@ -1583,8 +1555,9 @@
             LatinImeLogger.logOnManualSuggestion(
                     "", suggestion.toString(), index, suggestions.mWords);
             final char primaryCode = suggestion.charAt(0);
-            onCodeInput(primaryCode, new int[]{primaryCode}, KeyboardView.NOT_A_TOUCH_COORDINATE,
-                    KeyboardView.NOT_A_TOUCH_COORDINATE);
+            onCodeInput(primaryCode, new int[] { primaryCode },
+                    KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
+                    KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
             if (ic != null) {
                 ic.endBatchEdit();
             }
@@ -1873,7 +1846,7 @@
         } else if (Settings.PREF_RECORRECTION_ENABLED.equals(key)) {
             mReCorrectionEnabled = sharedPreferences.getBoolean(
                     Settings.PREF_RECORRECTION_ENABLED,
-                    getResources().getBoolean(R.bool.default_recorrection_enabled));
+                    mResources.getBoolean(R.bool.default_recorrection_enabled));
         }
     }
 
@@ -2005,11 +1978,12 @@
     }
 
     private void updateSuggestionVisibility(SharedPreferences prefs) {
+        final Resources res = mResources;
         final String suggestionVisiblityStr = prefs.getString(
                 Settings.PREF_SHOW_SUGGESTIONS_SETTING,
-                mResources.getString(R.string.prefs_suggestion_visibility_default_value));
+                res.getString(R.string.prefs_suggestion_visibility_default_value));
         for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) {
-            if (suggestionVisiblityStr.equals(mResources.getString(visibility))) {
+            if (suggestionVisiblityStr.equals(res.getString(visibility))) {
                 mSuggestionVisibility = visibility;
                 break;
             }
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 2365902..9f979ff 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -103,7 +103,7 @@
 
     private int mCorrectionMode = CORRECTION_BASIC;
 
-    public Suggest(Context context, int[] dictionaryResId) {
+    public Suggest(Context context, int dictionaryResId) {
         mMainDict = new BinaryDictionary(context, dictionaryResId, DIC_MAIN);
         initPool();
     }
diff --git a/native/Android.mk b/native/Android.mk
index 2de3892..38465ac 100644
--- a/native/Android.mk
+++ b/native/Android.mk
@@ -12,13 +12,20 @@
 
 #FLAG_DBG := true
 
-ifneq ($(TARGET_ARCH),x86)
-ifneq ($(FLAG_DBG), true)
-    LOCAL_NDK_VERSION := 4
+TARGETING_UNBUNDLED_FROYO := true
+
+ifeq ($(TARGET_ARCH), x86)
+    TARGETING_UNBUNDLED_FROYO := false
 endif
 
-LOCAL_SDK_VERSION := 8
-endif #TARGET_ARCH = x86
+ifeq ($(FLAG_DBG), true)
+    TARGETING_UNBUNDLED_FROYO := false
+endif
+
+ifeq ($(TARGETING_UNBUNDLED_FROYO), true)
+    LOCAL_NDK_VERSION := 4
+    LOCAL_SDK_VERSION := 8
+endif
 
 LOCAL_MODULE := libjni_latinime
 
diff --git a/native/src/defines.h b/native/src/defines.h
index 73394ce..59eaa41 100644
--- a/native/src/defines.h
+++ b/native/src/defines.h
@@ -24,10 +24,56 @@
 #define LOG_TAG "LatinIME: "
 #endif
 #define DEBUG_DICT true
-#define DEBUG_DICT_FULL true
+#define DEBUG_DICT_FULL false
 #define DEBUG_SHOW_FOUND_WORD DEBUG_DICT_FULL
 #define DEBUG_NODE DEBUG_DICT_FULL
 #define DEBUG_TRACE DEBUG_DICT_FULL
+
+// Profiler
+#include <time.h>
+#define PROF_BUF_SIZE 100
+static double profile_buf[PROF_BUF_SIZE];
+static double profile_old[PROF_BUF_SIZE];
+static unsigned int profile_counter[PROF_BUF_SIZE];
+
+#define PROF_RESET prof_reset();
+#define PROF_COUNT(prof_buf_id) ++profile_counter[prof_buf_id];
+#define PROF_OPEN PROF_RESET;PROF_START(PROF_BUF_SIZE - 1);
+#define PROF_START(prof_buf_id) PROF_COUNT(prof_buf_id);profile_old[prof_buf_id] = (clock());
+#define PROF_CLOSE PROF_END(PROF_BUF_SIZE - 1);PROF_OUTALL;
+#define PROF_END(prof_buf_id) profile_buf[prof_buf_id] += ((clock()) - profile_old[prof_buf_id]);
+#define PROF_CLOCKOUT(prof_buf_id) LOGI("%s : clock is %f", __FUNCTION__,\
+        (clock() - profile_old[prof_buf_id]));
+#define PROF_OUTALL LOGI("--- %s ---", __FUNCTION__); prof_out();
+
+static void prof_reset(void){
+    for(int i = 0;i < PROF_BUF_SIZE;++i){
+        profile_buf[i] = 0;
+        profile_old[i] = 0;
+        profile_counter[i] = 0;
+    }
+}
+
+static void prof_out(void){
+    if (profile_counter[PROF_BUF_SIZE - 1] != 1) {
+        LOGI("Error: You must call PROF_OPEN before PROF_CLOSE.");
+    }
+    LOGI("Total time is %6.3f ms.",
+            profile_buf[PROF_BUF_SIZE - 1] * 1000 / (double) CLOCKS_PER_SEC);
+    double all = 0;
+    for(int i = 0; i < PROF_BUF_SIZE - 1; ++i){
+        all += profile_buf[i];
+    }
+    if(all == 0) all = 1;
+    for(int i = 0; i < PROF_BUF_SIZE - 1; ++i){
+        if(profile_buf[i] != 0) {
+            LOGI("(%d): Used %4.2f%%, %8.4f ms. Called %d times.",
+                    i, (profile_buf[i] * 100 /all),
+                    profile_buf[i] * 1000 / (double) CLOCKS_PER_SEC, profile_counter[i]);
+         }
+    }
+}
+
 #else // FLAG_DBG
 #define LOGI
 #define DEBUG_DICT false
@@ -35,6 +81,18 @@
 #define DEBUG_SHOW_FOUND_WORD false
 #define DEBUG_NODE false
 #define DEBUG_TRACE false
+
+#define PROF_BUF_SIZE 0
+#define PROF_RESET
+#define PROF_COUNT(prof_buf_id)
+#define PROF_OPEN
+#define PROF_START(prof_buf_id)
+#define PROF_CLOSE
+#define PROF_END(prof_buf_id)
+#define PROF_CLOCK_OUT(prof_buf_id)
+#define PROF_CLOCKOUT(prof_buf_id)
+#define PROF_OUTALL
+
 #endif // FLAG_DBG
 
 #ifndef U_SHORT_MAX
diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp
index f679001..af2cc97 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -42,14 +42,20 @@
 UnigramDictionary::~UnigramDictionary() {}
 
 int UnigramDictionary::getSuggestions(int *codes, int codesSize, unsigned short *outWords,
-        int *frequencies, int *nextLetters, int nextLettersSize)
-{
+        int *frequencies, int *nextLetters, int nextLettersSize) {
+    PROF_OPEN;
+    PROF_START(0);
     initSuggestions(codes, codesSize, outWords, frequencies);
     if (DEBUG_DICT) assert(codesSize == mInputLength);
 
     const int MAX_DEPTH = min(mInputLength * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH);
-    getSuggestionCandidates(-1, -1, -1, nextLetters, nextLettersSize, MAX_DEPTH);
+    PROF_END(0);
 
+    PROF_START(1);
+    getSuggestionCandidates(-1, -1, -1, nextLetters, nextLettersSize, MAX_DEPTH);
+    PROF_END(1);
+
+    PROF_START(2);
     // Suggestion with missing character
     if (SUGGEST_WORDS_WITH_MISSING_CHARACTER) {
         for (int i = 0; i < codesSize; ++i) {
@@ -57,7 +63,9 @@
             getSuggestionCandidates(i, -1, -1, NULL, 0, MAX_DEPTH);
         }
     }
+    PROF_END(2);
 
+    PROF_START(3);
     // Suggestion with excessive character
     if (SUGGEST_WORDS_WITH_EXCESSIVE_CHARACTER
             && mInputLength >= MIN_USER_TYPED_LENGTH_FOR_EXCESSIVE_CHARACTER_SUGGESTION) {
@@ -66,7 +74,9 @@
             getSuggestionCandidates(-1, i, -1, NULL, 0, MAX_DEPTH);
         }
     }
+    PROF_END(3);
 
+    PROF_START(4);
     // Suggestion with transposed characters
     // Only suggest words that length is mInputLength
     if (SUGGEST_WORDS_WITH_TRANSPOSED_CHARACTERS) {
@@ -75,7 +85,9 @@
             getSuggestionCandidates(-1, -1, i, NULL, 0, mInputLength - 1);
         }
     }
+    PROF_END(4);
 
+    PROF_START(5);
     // Suggestions with missing space
     if (SUGGEST_WORDS_WITH_MISSING_SPACE_CHARACTER
             && mInputLength >= MIN_USER_TYPED_LENGTH_FOR_MISSING_SPACE_SUGGESTION) {
@@ -84,7 +96,9 @@
             getMissingSpaceWords(mInputLength, i);
         }
     }
+    PROF_END(5);
 
+    PROF_START(6);
     // Get the word count
     int suggestedWordsCount = 0;
     while (suggestedWordsCount < MAX_WORDS && mFrequencies[suggestedWordsCount] > 0) {
@@ -101,7 +115,8 @@
         }
         LOGI("\n");
     }
-
+    PROF_END(6);
+    PROF_CLOSE;
     return suggestedWordsCount;
 }
 
@@ -254,6 +269,14 @@
     }
 }
 
+inline static void multiplyRate(const int rate, int *freq) {
+    if (rate > 1000000) {
+        *freq = (*freq / 100) * rate;
+    } else {
+        *freq = *freq * rate / 100;
+    }
+}
+
 bool UnigramDictionary::getMissingSpaceWords(const int inputLength, const int missingSpacePos) {
     if (missingSpacePos <= 0 || missingSpacePos >= inputLength
             || inputLength >= MAX_WORD_LENGTH) return false;
@@ -279,7 +302,7 @@
 
     int pairFreq = ((firstFreq + secondFreq) / 2);
     for (int i = 0; i < inputLength; ++i) pairFreq *= TYPED_LETTER_MULTIPLIER;
-    pairFreq = pairFreq * WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE / 100;
+    multiplyRate(WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE, &pairFreq);
     addWord(word, newWordLength, pairFreq);
     return true;
 }
@@ -330,14 +353,13 @@
         const bool sameLength) {
     // TODO: Demote by edit distance
     int finalFreq = freq * snr;
-    if (skipPos >= 0) finalFreq = finalFreq * WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE / 100;
-    if (transposedPos >= 0) finalFreq = finalFreq
-            * WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE / 100;
+    if (skipPos >= 0) multiplyRate(WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE, &finalFreq);
+    if (transposedPos >= 0) multiplyRate(
+            WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE, &finalFreq);
     if (excessivePos >= 0) {
-        finalFreq = finalFreq * WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE / 100;
+        multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE, &finalFreq);
         if (!existsAdjacentProximityChars(inputIndex, mInputLength)) {
-            finalFreq = finalFreq
-                    * WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE / 100;
+            multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE, &finalFreq);
         }
     }
     if (sameLength && skipPos < 0) finalFreq *= FULL_WORD_MULTIPLIER;