Merge "Stop meaningless bigram computations" into jb-dev
diff --git a/java/res/drawable-hdpi/sym_keyboard_voice_holo.png b/java/res/drawable-hdpi/sym_keyboard_voice_holo.png
index bc9f908..8a6336a 100644
--- a/java/res/drawable-hdpi/sym_keyboard_voice_holo.png
+++ b/java/res/drawable-hdpi/sym_keyboard_voice_holo.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_voice_off_holo.png b/java/res/drawable-hdpi/sym_keyboard_voice_off_holo.png
index e852eec..edf1379 100644
--- a/java/res/drawable-hdpi/sym_keyboard_voice_off_holo.png
+++ b/java/res/drawable-hdpi/sym_keyboard_voice_off_holo.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_voice_holo.png b/java/res/drawable-mdpi/sym_keyboard_voice_holo.png
index 960c608..0795fcc 100644
--- a/java/res/drawable-mdpi/sym_keyboard_voice_holo.png
+++ b/java/res/drawable-mdpi/sym_keyboard_voice_holo.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_voice_off_holo.png b/java/res/drawable-mdpi/sym_keyboard_voice_off_holo.png
index 72086a9..f76da57 100644
--- a/java/res/drawable-mdpi/sym_keyboard_voice_off_holo.png
+++ b/java/res/drawable-mdpi/sym_keyboard_voice_off_holo.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_voice_holo.png b/java/res/drawable-xhdpi/sym_keyboard_voice_holo.png
index d338cf2..b2bb9b8 100644
--- a/java/res/drawable-xhdpi/sym_keyboard_voice_holo.png
+++ b/java/res/drawable-xhdpi/sym_keyboard_voice_holo.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_voice_off_holo.png b/java/res/drawable-xhdpi/sym_keyboard_voice_off_holo.png
index 0c913e1..23e75bf 100644
--- a/java/res/drawable-xhdpi/sym_keyboard_voice_off_holo.png
+++ b/java/res/drawable-xhdpi/sym_keyboard_voice_off_holo.png
Binary files differ
diff --git a/java/res/xml/rows_bulgarian_bds.xml b/java/res/xml/rows_bulgarian_bds.xml
index 9a2f0bc..b4f3f12 100644
--- a/java/res/xml/rows_bulgarian_bds.xml
+++ b/java/res/xml/rows_bulgarian_bds.xml
@@ -36,7 +36,7 @@
             latin:keyboardLayout="@xml/rowkeys_bulgarian_bds2" />
     </Row>
     <Row
-        latin:keyWidth="9.091%p"
+        latin:keyWidth="8.711%p"
     >
         <Key
             latin:keyStyle="shiftKeyStyle"
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
index be807ab..0bde2c0 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
@@ -131,6 +131,7 @@
 
     private interface SubtypeDialogProxy {
         public void onRemovePressed(SubtypePreference subtypePref);
+        public void onAddPressed(SubtypePreference subtypePref);
         public SubtypeLocaleAdapter getSubtypeLocaleAdapter();
         public KeyboardLayoutSetAdapter getKeyboardLayoutSetAdapter();
     }
@@ -241,6 +242,7 @@
             super.onClick(dialog, which);
             switch (which) {
             case DialogInterface.BUTTON_POSITIVE:
+                final boolean addPressed = isIncomplete();
                 final SubtypeLocaleItem locale =
                         (SubtypeLocaleItem) mSubtypeLocaleSpinner.getSelectedItem();
                 final KeyboardLayoutSetItem layout =
@@ -249,6 +251,9 @@
                         locale.first, layout.first, ASCII_CAPABLE);
                 setSubtype(subtype);
                 notifyChanged();
+                if (addPressed) {
+                    mProxy.onAddPressed(this);
+                }
                 break;
             case DialogInterface.BUTTON_NEUTRAL:
                 // Nothing to do
@@ -391,6 +396,11 @@
         }
 
         @Override
+        public void onAddPressed(SubtypePreference subtypePref) {
+            mIsAddingNewSubtype = false;
+        }
+
+        @Override
         public SubtypeLocaleAdapter getSubtypeLocaleAdapter() {
             return mSubtypeLocaleAdapter;
         }
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index 37deb0c..f3aa27a 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -70,6 +70,10 @@
         return false;
     }
 
+    public boolean isEmpty() {
+        return mDictionaries.isEmpty();
+    }
+
     @Override
     public void close() {
         for (final Dictionary dict : mDictionaries)
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index f5dc7b3..4cd1b38 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -36,7 +36,7 @@
             DictionaryFactory.class.getPackage().getName();
 
     /**
-     * Initializes a dictionary from a dictionary pack, with explicit flags.
+     * Initializes a main dictionary collection from a dictionary pack, with explicit flags.
      *
      * This searches for a content provider providing a dictionary pack for the specified
      * locale. If none is found, it falls back to the built-in dictionary - if any.
@@ -45,7 +45,7 @@
      * @param useFullEditDistance whether to use the full edit distance in suggestions
      * @return an initialized instance of DictionaryCollection
      */
-    public static DictionaryCollection createDictionaryFromManager(final Context context,
+    public static DictionaryCollection createMainDictionaryFromManager(final Context context,
             final Locale locale, final boolean useFullEditDistance) {
         if (null == locale) {
             Log.e(TAG, "No locale defined for dictionary");
@@ -73,7 +73,7 @@
     }
 
     /**
-     * Initializes a dictionary from a dictionary pack, with default flags.
+     * Initializes a main dictionary collection from a dictionary pack, with default flags.
      *
      * This searches for a content provider providing a dictionary pack for the specified
      * locale. If none is found, it falls back to the built-in dictionary, if any.
@@ -81,9 +81,9 @@
      * @param locale the locale for which to create the dictionary
      * @return an initialized instance of DictionaryCollection
      */
-    public static DictionaryCollection createDictionaryFromManager(final Context context,
+    public static DictionaryCollection createMainDictionaryFromManager(final Context context,
             final Locale locale) {
-        return createDictionaryFromManager(context, locale, false /* useFullEditDistance */);
+        return createMainDictionaryFromManager(context, locale, false /* useFullEditDistance */);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index a97f200..7832cb5 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -65,7 +65,7 @@
 
     private static final boolean DBG = LatinImeLogger.sDBG;
 
-    private Dictionary mMainDict;
+    private boolean mHasMainDictionary;
     private Dictionary mContactsDict;
     private WhitelistDictionary mWhiteListDictionary;
     private final HashMap<String, Dictionary> mUnigramDictionaries =
@@ -96,8 +96,12 @@
 
     /* package for test */ Suggest(final Context context, final File dictionary,
             final long startOffset, final long length, final Locale locale) {
-        initSynchronously(context, DictionaryFactory.createDictionaryForTest(context, dictionary,
-                startOffset, length /* useFullEditDistance */, false, locale), locale);
+        final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary,
+                startOffset, length /* useFullEditDistance */, false, locale);
+        mHasMainDictionary = null != mainDict;
+        addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, mainDict);
+        addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, mainDict);
+        initWhitelistAndAutocorrectAndPool(context, locale);
     }
 
     private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) {
@@ -113,14 +117,6 @@
         initWhitelistAndAutocorrectAndPool(context, locale);
     }
 
-    private void initSynchronously(final Context context, final Dictionary mainDict,
-            final Locale locale) {
-        mMainDict = mainDict;
-        addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, mainDict);
-        addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, mainDict);
-        initWhitelistAndAutocorrectAndPool(context, locale);
-    }
-
     private static void addOrReplaceDictionary(HashMap<String, Dictionary> dictionaries, String key,
             Dictionary dict) {
         final Dictionary oldDict = (dict == null)
@@ -132,13 +128,13 @@
     }
 
     public void resetMainDict(final Context context, final Locale locale) {
-        mMainDict = null;
+        mHasMainDictionary = false;
         new Thread("InitializeBinaryDictionary") {
             @Override
             public void run() {
-                final Dictionary newMainDict = DictionaryFactory.createDictionaryFromManager(
-                        context, locale);
-                mMainDict = newMainDict;
+                final DictionaryCollection newMainDict =
+                        DictionaryFactory.createMainDictionaryFromManager(context, locale);
+                mHasMainDictionary = null != newMainDict && !newMainDict.isEmpty();
                 addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, newMainDict);
                 addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, newMainDict);
             }
@@ -148,7 +144,7 @@
     // The main dictionary could have been loaded asynchronously.  Don't cache the return value
     // of this method.
     public boolean hasMainDictionary() {
-        return mMainDict != null;
+        return mHasMainDictionary;
     }
 
     public Dictionary getContactsDictionary() {
@@ -362,7 +358,13 @@
         // a boolean flag. Right now this is handled with a slight hack in
         // WhitelistDictionary#shouldForciblyAutoCorrectFrom.
         final boolean allowsToBeAutoCorrected = AutoCorrection.allowsToBeAutoCorrected(
-                getUnigramDictionaries(), consideredWord, wordComposer.isFirstCharCapitalized());
+                getUnigramDictionaries(), consideredWord, wordComposer.isFirstCharCapitalized())
+        // If we don't have a main dictionary, we never want to auto-correct. The reason for this
+        // is, the user may have a contact whose name happens to match a valid word in their
+        // language, and it will unexpectedly auto-correct. For example, if the user types in
+        // English with no dictionary and has a "Will" in their contact list, "will" would
+        // always auto-correct to "Will" which is unwanted. Hence, no main dict => no auto-correct.
+                && mHasMainDictionary;
 
         boolean autoCorrectionAvailable = hasAutoCorrection;
         if (correctionMode == CORRECTION_FULL || correctionMode == CORRECTION_FULL_BIGRAM) {
@@ -530,7 +532,7 @@
         for (final Dictionary dictionary : dictionaries) {
             dictionary.close();
         }
-        mMainDict = null;
+        mHasMainDictionary = false;
     }
 
     // TODO: Resolve the inconsistencies between the native auto correction algorithms and
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index 830fbf0..563f8a9 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -765,14 +765,39 @@
             bigramFrequency = unigramFrequency;
         }
         // We compute the difference between 255 (which means probability = 1) and the
-        // unigram score. We split this into discrete 16 steps, and this is the value
-        // we store into the 4 bits of the bigrams frequency.
-        final float bigramRatio = (float)(bigramFrequency - unigramFrequency)
-                / (MAX_TERMINAL_FREQUENCY - unigramFrequency);
-        // TODO: if the bigram freq is very close to the unigram frequency, we don't want
-        // to include the bigram in the binary dictionary at all.
-        final int discretizedFrequency = Math.round(bigramRatio * MAX_BIGRAM_FREQUENCY);
-        bigramFlags += discretizedFrequency & FLAG_ATTRIBUTE_FREQUENCY;
+        // unigram score. We split this into a number of discrete steps.
+        // Now, the steps are numbered 0~15; 0 represents an increase of 1 step while 15
+        // represents an increase of 16 steps: a value of 15 will be interpreted as the median
+        // value of the 16th step. In all justice, if the bigram frequency is low enough to be
+        // rounded below the first step (which means it is less than half a step higher than the
+        // unigram frequency) then the unigram frequency itself is the best approximation of the
+        // bigram freq that we could possibly supply, hence we should *not* include this bigram
+        // in the file at all.
+        // until this is done, we'll write 0 and slightly overestimate this case.
+        // In other words, 0 means "between 0.5 step and 1.5 step", 1 means "between 1.5 step
+        // and 2.5 steps", and 15 means "between 15.5 steps and 16.5 steps". So we want to
+        // divide our range [unigramFreq..MAX_TERMINAL_FREQUENCY] in 16.5 steps to get the
+        // step size. Then we compute the start of the first step (the one where value 0 starts)
+        // by adding half-a-step to the unigramFrequency. From there, we compute the integer
+        // number of steps to the bigramFrequency. One last thing: we want our steps to include
+        // 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.
+        final double stepSize =
+                (double)(MAX_TERMINAL_FREQUENCY - unigramFrequency) / (1.5 + MAX_BIGRAM_FREQUENCY);
+        final double firstStepStart = 1 + unigramFrequency + (stepSize / 2.0);
+        final int discretizedFrequency = (int)((bigramFrequency - firstStepStart) / stepSize);
+        // If the bigram freq is less than half-a-step higher than the unigram freq, we get -1
+        // here. The best approximation would be the unigram freq itself, so we should not
+        // include this bigram in the dictionary. For now, register as 0, and live with the
+        // small over-estimation that we get in this case. TODO: actually remove this bigram
+        // if discretizedFrequency < 0.
+        final int finalBigramFrequency = discretizedFrequency > 0 ? discretizedFrequency : 0;
+        bigramFlags += finalBigramFrequency & FLAG_ATTRIBUTE_FREQUENCY;
         return bigramFlags;
     }
 
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 6f7f0c3..0c9f9fb 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -398,7 +398,7 @@
                 SpellCheckerProximityInfo.PROXIMITY_GRID_WIDTH,
                 SpellCheckerProximityInfo.PROXIMITY_GRID_HEIGHT);
         final DictionaryCollection dictionaryCollection =
-                DictionaryFactory.createDictionaryFromManager(this, locale,
+                DictionaryFactory.createMainDictionaryFromManager(this, locale,
                         true /* useFullEditDistance */);
         final String localeStr = locale.toString();
         Dictionary userDictionary = mUserDictionaries.get(localeStr);
diff --git a/native/jni/src/correction.cpp b/native/jni/src/correction.cpp
index 376e9a1..a1f8129 100644
--- a/native/jni/src/correction.cpp
+++ b/native/jni/src/correction.cpp
@@ -344,8 +344,10 @@
                 mDistances[mOutputIndex] =
                         mProximityInfo->getNormalizedSquaredDistance(mInputIndex, proximityIndex);
             }
-            incrementInputIndex();
-            incremented = true;
+            if (!isQuote(c)) {
+                incrementInputIndex();
+                incremented = true;
+            }
         }
         return processSkipChar(c, isTerminal, incremented);
     }
@@ -710,7 +712,7 @@
         ed = max(0, ed - quoteDiffCount);
         adjustedProximityMatchedCount = min(max(0, ed - (outputLength - inputLength)),
                 proximityMatchedCount);
-        if (transposedCount < 1) {
+        if (transposedCount <= 0) {
             if (ed == 1 && (inputLength == outputLength - 1 || inputLength == outputLength + 1)) {
                 // Promote a word with just one skipped or excessive char
                 if (sameLength) {