Merge "Remove the whitelist dictionary." into jb-mr1-dev
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 9198500..489cc37 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -283,9 +283,10 @@
             public void load(String[] data) {
                 final int dataLength = data.length;
                 if (dataLength % TOUCH_POSITION_CORRECTION_RECORD_SIZE != 0) {
-                    if (LatinImeLogger.sDBG)
+                    if (LatinImeLogger.sDBG) {
                         throw new RuntimeException(
                                 "the size of touch position correction data is invalid");
+                    }
                     return;
                 }
 
@@ -324,7 +325,7 @@
 
             public boolean isValid() {
                 return mEnabled && mXs != null && mYs != null && mRadii != null
-                    && mXs.length > 0 && mYs.length > 0 && mRadii.length > 0;
+                        && mXs.length > 0 && mYs.length > 0 && mRadii.length > 0;
             }
         }
 
@@ -870,10 +871,12 @@
             final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
                     R.styleable.Keyboard);
             try {
-                if (a.hasValue(R.styleable.Keyboard_horizontalGap))
+                if (a.hasValue(R.styleable.Keyboard_horizontalGap)) {
                     throw new XmlParseUtils.IllegalAttribute(parser, "horizontalGap");
-                if (a.hasValue(R.styleable.Keyboard_verticalGap))
+                }
+                if (a.hasValue(R.styleable.Keyboard_verticalGap)) {
                     throw new XmlParseUtils.IllegalAttribute(parser, "verticalGap");
+                }
                 return new Row(mResources, mParams, parser, mCurrentY);
             } finally {
                 a.recycle();
@@ -921,7 +924,9 @@
                 throws XmlPullParserException, IOException {
             if (skip) {
                 XmlParseUtils.checkEndTag(TAG_KEY, parser);
-                if (DEBUG) startEndTag("<%s /> skipped", TAG_KEY);
+                if (DEBUG) {
+                    startEndTag("<%s /> skipped", TAG_KEY);
+                }
             } else {
                 final Key key = new Key(mResources, mParams, row, parser);
                 if (DEBUG) {
@@ -1099,9 +1104,9 @@
 
         private boolean parseCaseCondition(XmlPullParser parser) {
             final KeyboardId id = mParams.mId;
-            if (id == null)
+            if (id == null) {
                 return true;
-
+            }
             final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
                     R.styleable.Keyboard_Case);
             try {
@@ -1205,9 +1210,9 @@
             // If <case> does not have "index" attribute, that means this <case> is wild-card for
             // the attribute.
             final TypedValue v = a.peekValue(index);
-            if (v == null)
+            if (v == null) {
                 return true;
-
+            }
             if (isIntegerValue(v)) {
                 return intValue == a.getInt(index, 0);
             } else if (isStringValue(v)) {
@@ -1218,8 +1223,9 @@
 
         private static boolean stringArrayContains(String[] array, String value) {
             for (final String elem : array) {
-                if (elem.equals(value))
+                if (elem.equals(value)) {
                     return true;
+                }
             }
             return false;
         }
@@ -1242,16 +1248,18 @@
             TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
                     R.styleable.Keyboard_Key);
             try {
-                if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName))
+                if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName)) {
                     throw new XmlParseUtils.ParseException("<" + TAG_KEY_STYLE
                             + "/> needs styleName attribute", parser);
+                }
                 if (DEBUG) {
                     startEndTag("<%s styleName=%s />%s", TAG_KEY_STYLE,
-                        keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName),
-                        skip ? " skipped" : "");
+                            keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName),
+                            skip ? " skipped" : "");
                 }
-                if (!skip)
+                if (!skip) {
                     mParams.mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
+                }
             } finally {
                 keyStyleAttr.recycle();
                 keyAttrs.recycle();
@@ -1272,8 +1280,9 @@
         }
 
         private void endRow(Row row) {
-            if (mCurrentRow == null)
+            if (mCurrentRow == null) {
                 throw new InflateException("orphan end row tag");
+            }
             if (mRightEdgeKey != null) {
                 mRightEdgeKey.markAsRightEdge(mParams);
                 mRightEdgeKey = null;
@@ -1309,8 +1318,9 @@
         public static float getDimensionOrFraction(TypedArray a, int index, int base,
                 float defValue) {
             final TypedValue value = a.peekValue(index);
-            if (value == null)
+            if (value == null) {
                 return defValue;
+            }
             if (isFractionValue(value)) {
                 return a.getFraction(index, base, base, defValue);
             } else if (isDimensionValue(value)) {
@@ -1321,8 +1331,9 @@
 
         public static int getEnumValue(TypedArray a, int index, int defValue) {
             final TypedValue value = a.peekValue(index);
-            if (value == null)
+            if (value == null) {
                 return defValue;
+            }
             if (isIntegerValue(value)) {
                 return a.getInt(index, defValue);
             }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index fcf97b9..ccbb081 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -362,9 +362,9 @@
 
         final TypedArray a = context.obtainStyledAttributes(
                 attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
-
         mKeyDrawParams = new KeyDrawParams(a);
         mKeyPreviewDrawParams = new KeyPreviewDrawParams(a, mKeyDrawParams);
+        mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout;
         mKeyPreviewLayoutId = a.getResourceId(R.styleable.KeyboardView_keyPreviewLayout, 0);
         if (mKeyPreviewLayoutId == 0) {
             mShowKeyPreviewPopup = false;
@@ -373,11 +373,9 @@
                 R.styleable.KeyboardView_verticalCorrection, 0);
         mMoreKeysLayout = a.getResourceId(R.styleable.KeyboardView_moreKeysLayout, 0);
         mBackgroundDimAlpha = a.getInt(R.styleable.KeyboardView_backgroundDimAlpha, 0);
-        mPreviewPlacerView = new PreviewPlacerView(context, a);
         a.recycle();
 
-        mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout;
-
+        mPreviewPlacerView = new PreviewPlacerView(context, attrs);
         mPaint.setAntiAlias(true);
     }
 
@@ -462,12 +460,7 @@
         if (bufferNeedsUpdates || mOffscreenBuffer == null) {
             if (maybeAllocateOffscreenBuffer()) {
                 mInvalidateAllKeys = true;
-                // TODO: Stop using the offscreen canvas even when in software rendering
-                if (mOffscreenCanvas != null) {
-                    mOffscreenCanvas.setBitmap(mOffscreenBuffer);
-                } else {
-                    mOffscreenCanvas = new Canvas(mOffscreenBuffer);
-                }
+                maybeCreateOffscreenCanvas();
             }
             onDrawKeyboard(mOffscreenCanvas);
         }
@@ -496,6 +489,15 @@
         }
     }
 
+    private void maybeCreateOffscreenCanvas() {
+        // TODO: Stop using the offscreen canvas even when in software rendering
+        if (mOffscreenCanvas != null) {
+            mOffscreenCanvas.setBitmap(mOffscreenBuffer);
+        } else {
+            mOffscreenCanvas = new Canvas(mOffscreenBuffer);
+        }
+    }
+
     private void onDrawKeyboard(final Canvas canvas) {
         if (mKeyboard == null) return;
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
index d0fecf0..9e9c9e2 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
@@ -23,6 +23,7 @@
 import android.graphics.Paint.Align;
 import android.os.Message;
 import android.text.TextUtils;
+import android.util.AttributeSet;
 import android.util.SparseArray;
 import android.widget.RelativeLayout;
 
@@ -89,10 +90,16 @@
         }
     }
 
-    public PreviewPlacerView(Context context, TypedArray keyboardViewAttr) {
+    public PreviewPlacerView(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.keyboardViewStyle);
+    }
+
+    public PreviewPlacerView(Context context, AttributeSet attrs, int defStyle) {
         super(context);
         setWillNotDraw(false);
 
+        final TypedArray keyboardViewAttr = context.obtainStyledAttributes(
+                attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
         final int gestureFloatingPreviewTextSize = keyboardViewAttr.getDimensionPixelSize(
                 R.styleable.KeyboardView_gestureFloatingPreviewTextSize, 0);
         mGestureFloatingPreviewTextColor = keyboardViewAttr.getColor(
@@ -117,6 +124,7 @@
                 R.styleable.KeyboardView_gesturePreviewTrailColor, 0);
         final int gesturePreviewTrailWidth = keyboardViewAttr.getDimensionPixelSize(
                 R.styleable.KeyboardView_gesturePreviewTrailWidth, 0);
+        keyboardViewAttr.recycle();
 
         mGesturePaint = new Paint();
         mGesturePaint.setAntiAlias(true);
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 063243e..dd11aaa 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -23,6 +25,7 @@
 import android.util.Log;
 
 import java.io.File;
+import java.io.RandomAccessFile;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Locale;
@@ -51,6 +54,9 @@
     private static final String MAIN_DICTIONARY_CATEGORY = "main";
     public static final String ID_CATEGORY_SEPARATOR = ":";
 
+    // The key considered to read the version attribute in a dictionary file.
+    private static String VERSION_KEY = "version";
+
     // Prevents this from being instantiated
     private BinaryDictionaryGetter() {}
 
@@ -336,6 +342,42 @@
         return MAIN_DICTIONARY_CATEGORY.equals(idArray[0]);
     }
 
+    // ## HACK ## we prevent usage of a dictionary before version 18 for English only. The reason
+    // for this is, since those do not include whitelist entries, the new code with an old version
+    // of the dictionary would lose whitelist functionality.
+    private static boolean hackCanUseDictionaryFile(final Locale locale, final File f) {
+        // Only for English - other languages didn't have a whitelist, hence this
+        // ad-hock ## HACK ##
+        if (!Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) return true;
+
+        try {
+            // Read the version of the file
+            final RandomAccessFile raf = new RandomAccessFile(f, "r");
+            final int magic = raf.readInt();
+            if (magic != BinaryDictInputOutput.VERSION_2_MAGIC_NUMBER) {
+                return false;
+            }
+            final int formatVersion = raf.readInt();
+            final int headerSize = raf.readInt();
+            final HashMap<String, String> options = new HashMap<String, String>();
+            BinaryDictInputOutput.populateOptionsFromFile(raf, headerSize, options);
+            final String version = options.get(VERSION_KEY);
+            if (null == version) {
+                // No version in the options : the format is unexpected
+                return false;
+            }
+            // Version 18 is the first one to include the whitelist
+            // Obviously this is a big ## HACK ##
+            return Integer.parseInt(version) >= 18;
+        } catch (java.io.FileNotFoundException e) {
+            return false;
+        } catch (java.io.IOException e) {
+            return false;
+        } catch (NumberFormatException e) {
+            return false;
+        }
+    }
+
     /**
      * Returns a list of file addresses for a given locale, trying relevant methods in order.
      *
@@ -366,14 +408,15 @@
         // cachedWordLists may not be null, see doc for getCachedDictionaryList
         for (final File f : cachedWordLists) {
             final String wordListId = getWordListIdFromFileName(f.getName());
-            if (isMainWordListId(wordListId)) {
+            final boolean canUse = f.canRead() && hackCanUseDictionaryFile(locale, f);
+            if (canUse && isMainWordListId(wordListId)) {
                 foundMainDict = true;
             }
             if (!dictPackSettings.isWordListActive(wordListId)) continue;
-            if (f.canRead()) {
+            if (canUse) {
                 fileList.add(AssetFileAddress.makeFromFileName(f.getPath()));
             } else {
-                Log.e(TAG, "Found a cached dictionary file but cannot read it");
+                Log.e(TAG, "Found a cached dictionary file but cannot read or use it");
             }
         }
 
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 884e6db..c20f3a3 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -2001,6 +2001,7 @@
 
     private CharSequence addToUserHistoryDictionary(final CharSequence suggestion) {
         if (TextUtils.isEmpty(suggestion)) return null;
+        if (mSuggest == null) return null;
 
         // If correction is not enabled, we don't add words to the user history dictionary.
         // That's to avoid unintended additions in some sensitive fields, or fields that
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
index 3bb670c..d516e72 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
@@ -52,14 +52,14 @@
     private static final int FREQUENCY_FOR_TYPED = 2;
 
     /** Maximum number of pairs. Pruning will start when databases goes above this number. */
-    private static int sMaxHistoryBigrams = 10000;
+    public static final int sMaxHistoryBigrams = 10000;
 
     /**
      * When it hits maximum bigram pair, it will delete until you are left with
      * only (sMaxHistoryBigrams - sDeleteHistoryBigrams) pairs.
      * Do not keep this number small to avoid deleting too often.
      */
-    private static int sDeleteHistoryBigrams = 1000;
+    public static final int sDeleteHistoryBigrams = 1000;
 
     /**
      * Database version should increase if the database structure changes
@@ -109,12 +109,8 @@
 
     private static DatabaseHelper sOpenHelper = null;
 
-    public void setDatabaseMax(int maxHistoryBigram) {
-        sMaxHistoryBigrams = maxHistoryBigram;
-    }
-
-    public void setDatabaseDelete(int deleteHistoryBigram) {
-        sDeleteHistoryBigrams = deleteHistoryBigram;
+    public String getLocale() {
+        return mLocale;
     }
 
     public synchronized static UserHistoryDictionary getInstance(
@@ -502,9 +498,11 @@
                                     needsToSave(fc, isValid, addLevel0Bigram)) {
                                 freq = fc;
                             } else {
+                                // Delete this entry
                                 freq = -1;
                             }
                         } else {
+                            // Delete this entry
                             freq = -1;
                         }
                     }
@@ -541,6 +539,7 @@
                                     getContentValues(word1, word2, mLocale));
                             pairId = pairIdLong.intValue();
                         }
+                        // Eliminate freq == 0 because that word is profanity.
                         if (freq > 0) {
                             if (PROFILE_SAVE_RESTORE) {
                                 ++profInsert;
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index 2c3eee7..b23b7db 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -124,7 +124,7 @@
      */
 
     private static final int VERSION_1_MAGIC_NUMBER = 0x78B1;
-    private static final int VERSION_2_MAGIC_NUMBER = 0x9BC13AFE;
+    public static final int VERSION_2_MAGIC_NUMBER = 0x9BC13AFE;
     private static final int MINIMUM_SUPPORTED_VERSION = 1;
     private static final int MAXIMUM_SUPPORTED_VERSION = 2;
     private static final int NOT_A_VERSION_NUMBER = -1;
@@ -783,10 +783,10 @@
         // their lower bound and exclude their higher bound so we need to have the first step
         // start at exactly 1 unit higher than floor(unigramFreq + half a step).
         // Note : to reconstruct the score, the dictionary reader will need to divide
-        // MAX_TERMINAL_FREQUENCY - unigramFreq by 16.5 likewise, and add
-        // (discretizedFrequency + 0.5) times this value to get the median value of the step,
-        // which is the best approximation. This is how we get the most precise result with
-        // only four bits.
+        // MAX_TERMINAL_FREQUENCY - unigramFreq by 16.5 likewise to get the value of the step,
+        // and add (discretizedFrequency + 0.5 + 0.5) times this value to get the best
+        // approximation. (0.5 to get the first step start, and 0.5 to get the middle of the
+        // step pointed by the discretized frequency.
         final float stepSize =
                 (MAX_TERMINAL_FREQUENCY - unigramFrequency) / (1.5f + MAX_BIGRAM_FREQUENCY);
         final float firstStepStart = 1 + unigramFrequency + (stepSize / 2.0f);
@@ -1328,6 +1328,21 @@
     }
 
     /**
+     * Reads options from a file and populate a map with their contents.
+     *
+     * The file is read at the current file pointer, so the caller must take care the pointer
+     * is in the right place before calling this.
+     */
+    public static void populateOptionsFromFile(final RandomAccessFile source, final long headerSize,
+            final HashMap<String, String> options) throws IOException {
+        while (source.getFilePointer() < headerSize) {
+            final String key = CharEncoding.readString(source);
+            final String value = CharEncoding.readString(source);
+            options.put(key, value);
+        }
+    }
+
+    /**
      * Reads a random access file and returns the memory representation of the dictionary.
      *
      * This high-level method takes a binary file and reads its contents, populating a
@@ -1358,11 +1373,7 @@
         } else {
             headerSize = (source.readUnsignedByte() << 24) + (source.readUnsignedByte() << 16)
                     + (source.readUnsignedByte() << 8) + source.readUnsignedByte();
-            while (source.getFilePointer() < headerSize) {
-                final String key = CharEncoding.readString(source);
-                final String value = CharEncoding.readString(source);
-                options.put(key, value);
-            }
+            populateOptionsFromFile(source, headerSize, options);
             source.seek(headerSize);
         }
 
@@ -1410,4 +1421,22 @@
             return false;
         }
     }
+
+    /**
+     * Calculate bigram frequency from compressed value
+     *
+     * @see #makeBigramFlags
+     *
+     * @param unigramFrequency
+     * @param bigramFrequency compressed frequency
+     * @return approximate bigram frequency
+     */
+    public static int reconstructBigramFrequency(final int unigramFrequency,
+            final int bigramFrequency) {
+        final float stepSize = (MAX_TERMINAL_FREQUENCY - unigramFrequency)
+                / (1.5f + MAX_BIGRAM_FREQUENCY);
+        final float resultFreqFloat = (float)unigramFrequency
+                + stepSize * (bigramFrequency + 1.0f);
+        return (int)resultFreqFloat;
+    }
 }
diff --git a/native/jni/src/bigram_dictionary.h b/native/jni/src/bigram_dictionary.h
index d676cca..5f11ae8 100644
--- a/native/jni/src/bigram_dictionary.h
+++ b/native/jni/src/bigram_dictionary.h
@@ -29,8 +29,6 @@
     BigramDictionary(const unsigned char *dict, int maxWordLength, int maxPredictions);
     int getBigrams(const int32_t *word, int length, int *inputCodes, int codesSize,
             unsigned short *outWords, int *frequencies, int *outputTypes) const;
-    int getBigramListPositionForWord(const int32_t *prevWord, const int prevWordLength,
-            const bool forceLowerCaseSearch) const;
     void fillBigramAddressToFrequencyMapAndFilter(const int32_t *prevWord, const int prevWordLength,
             std::map<int, int> *map, uint8_t *filter) const;
     bool isValidBigram(const int32_t *word1, int length1, const int32_t *word2, int length2) const;
@@ -45,6 +43,8 @@
     bool getFirstBitOfByte(int *pos) { return (DICT[*pos] & 0x80) > 0; }
     bool getSecondBitOfByte(int *pos) { return (DICT[*pos] & 0x40) > 0; }
     bool checkFirstCharacter(unsigned short *word, int *inputCodes) const;
+    int getBigramListPositionForWord(const int32_t *prevWord, const int prevWordLength,
+            const bool forceLowerCaseSearch) const;
 
     const unsigned char *DICT;
     const int MAX_WORD_LENGTH;
diff --git a/native/jni/src/binary_format.h b/native/jni/src/binary_format.h
index 4cabc84..d8f3e83 100644
--- a/native/jni/src/binary_format.h
+++ b/native/jni/src/binary_format.h
@@ -61,13 +61,6 @@
     static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20;
     static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30;
 
- private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryFormat);
-    const static int32_t MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
-    const static int32_t CHARACTER_ARRAY_TERMINATOR = 0x1F;
-    const static int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2;
-
- public:
     const static int UNKNOWN_FORMAT = -1;
     // Originally, format version 1 had a 16-bit magic number, then the version number `01'
     // then options that must be 0. Hence the first 32-bits of the format are always as follow
@@ -94,7 +87,6 @@
     static int skipFrequency(const uint8_t flags, const int pos);
     static int skipShortcuts(const uint8_t *const dict, const uint8_t flags, const int pos);
     static int skipBigrams(const uint8_t *const dict, const uint8_t flags, const int pos);
-    static int skipAllAttributes(const uint8_t *const dict, const uint8_t flags, const int pos);
     static int skipChildrenPosAndAttributes(const uint8_t *const dict, const uint8_t flags,
             const int pos);
     static int readChildrenPosition(const uint8_t *const dict, const uint8_t flags, const int pos);
@@ -118,6 +110,13 @@
         REQUIRES_FRENCH_LIGATURES_PROCESSING = 0x4
     };
     const static unsigned int NO_FLAGS = 0;
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryFormat);
+    const static int32_t MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
+    const static int32_t CHARACTER_ARRAY_TERMINATOR = 0x1F;
+    const static int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2;
+    static int skipAllAttributes(const uint8_t *const dict, const uint8_t flags, const int pos);
 };
 
 inline int BinaryFormat::detectFormat(const uint8_t *const dict) {
diff --git a/native/jni/src/char_utils.cpp b/native/jni/src/char_utils.cpp
index 223291f..9d886da31 100644
--- a/native/jni/src/char_utils.cpp
+++ b/native/jni/src/char_utils.cpp
@@ -889,7 +889,7 @@
             - static_cast<int>((static_cast<const struct LatinCapitalSmallPair *>(b))->capital);
 }
 
-unsigned short latin_tolower(unsigned short c) {
+unsigned short latin_tolower(const unsigned short c) {
     struct LatinCapitalSmallPair *p =
             static_cast<struct LatinCapitalSmallPair *>(bsearch(&c, SORTED_CHAR_MAP,
                     sizeof(SORTED_CHAR_MAP) / sizeof(SORTED_CHAR_MAP[0]),
diff --git a/native/jni/src/char_utils.h b/native/jni/src/char_utils.h
index edd96bb..b30677f 100644
--- a/native/jni/src/char_utils.h
+++ b/native/jni/src/char_utils.h
@@ -17,21 +17,23 @@
 #ifndef LATINIME_CHAR_UTILS_H
 #define LATINIME_CHAR_UTILS_H
 
+#include <cctype>
+
 namespace latinime {
 
-inline static int isAsciiUpper(unsigned short c) {
-    return c >= 'A' && c <= 'Z';
+inline static bool isAsciiUpper(unsigned short c) {
+    return isupper(static_cast<int>(c)) != 0;
 }
 
 inline static unsigned short toAsciiLower(unsigned short c) {
     return c - 'A' + 'a';
 }
 
-inline static int isAscii(unsigned short c) {
-    return c <= 127;
+inline static bool isAscii(unsigned short c) {
+    return isascii(static_cast<int>(c)) != 0;
 }
 
-unsigned short latin_tolower(unsigned short c);
+unsigned short latin_tolower(const unsigned short c);
 
 /**
  * Table mapping most combined Latin, Greek, and Cyrillic characters
diff --git a/tools/dicttool/Android.mk b/tools/dicttool/Android.mk
index df8cb10..b0b47af 100644
--- a/tools/dicttool/Android.mk
+++ b/tools/dicttool/Android.mk
@@ -26,7 +26,6 @@
 LOCAL_JAR_MANIFEST := etc/manifest.txt
 LOCAL_MODULE := dicttool_aosp
 LOCAL_JAVA_LIBRARIES := junit
-LOCAL_MODULE_TAGS := eng
 
 include $(BUILD_HOST_JAVA_LIBRARY)
 include $(LOCAL_PATH)/etc/Android.mk
diff --git a/tools/dicttool/etc/Android.mk b/tools/dicttool/etc/Android.mk
index 8952827..0c611b7 100644
--- a/tools/dicttool/etc/Android.mk
+++ b/tools/dicttool/etc/Android.mk
@@ -15,6 +15,5 @@
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_MODULE_TAGS := eng
 LOCAL_PREBUILT_EXECUTABLES := dicttool_aosp makedict_aosp
 include $(BUILD_HOST_PREBUILT)
diff --git a/tools/maketext/Android.mk b/tools/maketext/Android.mk
index 98731b7..77914ca 100644
--- a/tools/maketext/Android.mk
+++ b/tools/maketext/Android.mk
@@ -19,7 +19,6 @@
 LOCAL_SRC_FILES += $(call all-java-files-under,src)
 LOCAL_JAR_MANIFEST := etc/manifest.txt
 LOCAL_JAVA_RESOURCE_DIRS := res
-LOCAL_MODULE_TAGS := eng
 LOCAL_MODULE := maketext
 
 include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/maketext/etc/Android.mk b/tools/maketext/etc/Android.mk
index 4fa194b..475676b 100644
--- a/tools/maketext/etc/Android.mk
+++ b/tools/maketext/etc/Android.mk
@@ -15,7 +15,6 @@
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_MODULE_TAGS := eng
-
 LOCAL_PREBUILT_EXECUTABLES := maketext
+
 include $(BUILD_HOST_PREBUILT)