Merge "Smaller gap for Thai 5 rows keyboard"
diff --git a/java/res/values-fr/donottranslate-config.xml b/java/res/values-fr/donottranslate-config.xml
new file mode 100644
index 0000000..1f446d5
--- /dev/null
+++ b/java/res/values-fr/donottranslate-config.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<resources>
+    <bool name="config_require_ligatures_processing">true</bool>
+</resources>
diff --git a/java/res/values-land/dimens.xml b/java/res/values-land/dimens.xml
index 6cdcf89..550d29f 100644
--- a/java/res/values-land/dimens.xml
+++ b/java/res/values-land/dimens.xml
@@ -19,8 +19,9 @@
 -->
 
 <resources>
-    <!-- keyboardHeight = key_height*4 + key_bottom_gap*3, key_height=0.260in -->
-    <dimen name="keyboardHeight">1.100in</dimen>
+    <!-- Preferable keyboard height in absolute scale: 1.100in -->
+    <!-- This keyboardHeight value should match with keyboard-heights.xml -->
+    <dimen name="keyboardHeight">176.0dp</dimen>
     <fraction name="minKeyboardHeight">45%p</fraction>
     <!-- key_height + key_bottom_gap = popup_key_height -->
 <!--    <dimen name="key_height">0.260in</dimen>-->
@@ -31,7 +32,6 @@
     <fraction name="key_bottom_gap">4.330%p</fraction>
     <fraction name="key_horizontal_gap">0.405%p</fraction>
 
-    <dimen name="keyboardHeight_stone">0.984in</dimen>
     <fraction name="key_bottom_gap_stone">5.010%p</fraction>
     <fraction name="key_horizontal_gap_stone">1.159%p</fraction>
 
diff --git a/java/res/values-land/keyboard-heights.xml b/java/res/values-land/keyboard-heights.xml
new file mode 100644
index 0000000..12c3e39
--- /dev/null
+++ b/java/res/values-land/keyboard-heights.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- Preferable keyboard height in absolute scale: 1.100in -->
+<resources>
+    <!-- Build.HARDWARE,keyboard_height_in_dp -->
+    <string-array name="keyboard_heights" translatable="false">
+        <!-- Droid -->
+        <item>sholes,194.3333</item>
+        <!-- Nexus One -->
+        <item>mahimahi,186.2667</item>
+        <!-- Nexus S -->
+        <item>herring,171.9385</item>
+        <!-- Galaxy Nexus -->
+        <item>tuna,173.4207</item>
+    </string-array>
+</resources>
diff --git a/java/res/values-sw600dp-land/dimens.xml b/java/res/values-sw600dp-land/dimens.xml
index 832e72a..c6c6f2b 100644
--- a/java/res/values-sw600dp-land/dimens.xml
+++ b/java/res/values-sw600dp-land/dimens.xml
@@ -19,8 +19,9 @@
 -->
 
 <resources>
-    <!-- keyboardHeight = key_height*4 + key_bottom_gap*3 -->
-    <dimen name="keyboardHeight">45.0mm</dimen>
+    <!-- Preferable keyboard height in absolute scale: 45.0mm -->
+    <!-- This keyboardHeight value should match with keyboard-heights.xml -->
+    <dimen name="keyboardHeight">283.5dp</dimen>
     <fraction name="minKeyboardHeight">45%p</fraction>
 
     <fraction name="keyboard_top_padding">2.444%p</fraction>
@@ -28,7 +29,6 @@
     <fraction name="key_bottom_gap">4.911%p</fraction>
     <fraction name="key_horizontal_gap">1.284%p</fraction>
 
-    <dimen name="keyboardHeight_stone">45.0mm</dimen>
     <fraction name="key_bottom_gap_stone">4.355%p</fraction>
     <fraction name="key_horizontal_gap_stone">1.505%p</fraction>
 
diff --git a/java/res/values-sw600dp-land/keyboard-heights.xml b/java/res/values-sw600dp-land/keyboard-heights.xml
new file mode 100644
index 0000000..93f9824
--- /dev/null
+++ b/java/res/values-sw600dp-land/keyboard-heights.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- Preferable keyboard height in absolute scale: 45.0mm -->
+<resources>
+    <!-- Build.HARDWARE,keyboard_height_in_dp -->
+    <string-array name="keyboard_heights" translatable="false">
+        <!-- Xoom -->
+        <item>stingray,265.4378</item>
+    </string-array>
+</resources>
diff --git a/java/res/values-sw600dp/dimens.xml b/java/res/values-sw600dp/dimens.xml
index 4c0e04e..ebe3882 100644
--- a/java/res/values-sw600dp/dimens.xml
+++ b/java/res/values-sw600dp/dimens.xml
@@ -19,8 +19,9 @@
 -->
 
 <resources>
-    <!-- keyboardHeight = key_height*4 + key_bottom_gap*3 -->
-    <dimen name="keyboardHeight">48.0mm</dimen>
+    <!-- Preferable keyboard height in absolute scale: 48.0mm -->
+    <!-- This keyboardHeight value should match with keyboard-heights.xml -->
+    <dimen name="keyboardHeight">302.4dp</dimen>
     <fraction name="maxKeyboardHeight">50%p</fraction>
     <fraction name="minKeyboardHeight">-35.0%p</fraction>
 
@@ -31,7 +32,6 @@
     <fraction name="key_bottom_gap">3.750%p</fraction>
     <fraction name="key_horizontal_gap">1.857%p</fraction>
 
-    <dimen name="keyboardHeight_stone">48.0mm</dimen>
     <fraction name="key_bottom_gap_stone">3.75%p</fraction>
     <fraction name="key_horizontal_gap_stone">1.602%p</fraction>
 
diff --git a/java/res/values-sw600dp/keyboard-heights.xml b/java/res/values-sw600dp/keyboard-heights.xml
new file mode 100644
index 0000000..77e52be
--- /dev/null
+++ b/java/res/values-sw600dp/keyboard-heights.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- Preferable keyboard height in absolute scale: 48.0mm -->
+<resources>
+    <!-- Build.HARDWARE,keyboard_height_in_dp -->
+    <string-array name="keyboard_heights" translatable="false">
+        <!-- Xoom -->
+        <item>stingray,283.1337</item>
+    </string-array>
+</resources>
diff --git a/java/res/values-sw768dp-land/dimens.xml b/java/res/values-sw768dp-land/dimens.xml
index 535c623..597ed51 100644
--- a/java/res/values-sw768dp-land/dimens.xml
+++ b/java/res/values-sw768dp-land/dimens.xml
@@ -19,8 +19,9 @@
 -->
 
 <resources>
-    <!-- keyboardHeight = key_height*4 + key_bottom_gap*3, key_height=14.5mm -->
-    <dimen name="keyboardHeight">58.0mm</dimen>
+    <!-- Preferable keyboard height in absolute scale: 58.0mm -->
+    <!-- This keyboardHeight value should match with keyboard-heights.xml -->
+    <dimen name="keyboardHeight">365.4dp</dimen>
     <fraction name="minKeyboardHeight">45%p</fraction>
 
     <fraction name="keyboard_top_padding">1.896%p</fraction>
@@ -29,7 +30,6 @@
     <fraction name="key_bottom_gap">4.103%p</fraction>
     <fraction name="key_horizontal_gap">1.034%p</fraction>
 
-    <dimen name="keyboardHeight_stone">58.0mm</dimen>
     <fraction name="key_bottom_gap_stone">3.379%p</fraction>
     <fraction name="key_horizontal_gap_stone">1.062%p</fraction>
 
diff --git a/java/res/values-sw768dp-land/keyboard-heights.xml b/java/res/values-sw768dp-land/keyboard-heights.xml
new file mode 100644
index 0000000..692c5a0
--- /dev/null
+++ b/java/res/values-sw768dp-land/keyboard-heights.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- Preferable keyboard height in absolute scale: 58.0mm -->
+<resources>
+    <!-- Build.HARDWARE,keyboard_height_in_dp -->
+    <string-array name="keyboard_heights" translatable="false">
+        <!-- Xoom -->
+        <item>stingray,342.1198</item>
+    </string-array>
+</resources>
diff --git a/java/res/values-sw768dp/dimens.xml b/java/res/values-sw768dp/dimens.xml
index 7aefa26..a9f0c00 100644
--- a/java/res/values-sw768dp/dimens.xml
+++ b/java/res/values-sw768dp/dimens.xml
@@ -19,8 +19,9 @@
 -->
 
 <resources>
-    <!-- keyboardHeight = key_height*4 + key_bottom_gap*3, key_height=12mm -->
-    <dimen name="keyboardHeight">48.0mm</dimen>
+    <!-- Preferable keyboard height in absolute scale: 48.0mm -->
+    <!-- This keyboardHeight value should match with keyboard-heights.xml -->
+    <dimen name="keyboardHeight">302.4dp</dimen>
     <fraction name="maxKeyboardHeight">50%p</fraction>
     <fraction name="minKeyboardHeight">-35.0%p</fraction>
 
@@ -30,7 +31,6 @@
     <fraction name="key_bottom_gap">4.270%p</fraction>
     <fraction name="key_horizontal_gap">1.551%p</fraction>
 
-    <dimen name="keyboardHeight_stone">48.0mm</dimen>
     <fraction name="key_bottom_gap_stone">3.75%p</fraction>
     <fraction name="key_horizontal_gap_stone">1.059%p</fraction>
 
diff --git a/java/res/values-sw768dp/keyboard-heights.xml b/java/res/values-sw768dp/keyboard-heights.xml
new file mode 100644
index 0000000..77e52be
--- /dev/null
+++ b/java/res/values-sw768dp/keyboard-heights.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- Preferable keyboard height in absolute scale: 48.0mm -->
+<resources>
+    <!-- Build.HARDWARE,keyboard_height_in_dp -->
+    <string-array name="keyboard_heights" translatable="false">
+        <!-- Xoom -->
+        <item>stingray,283.1337</item>
+    </string-array>
+</resources>
diff --git a/java/res/values/dimens.xml b/java/res/values/dimens.xml
index 5f74fa7..9d64a61 100644
--- a/java/res/values/dimens.xml
+++ b/java/res/values/dimens.xml
@@ -19,8 +19,9 @@
 -->
 
 <resources>
-    <!-- keyboardHeight = row_height*4 + key_bottom_gap*3 -->
-    <dimen name="keyboardHeight">1.285in</dimen>
+    <!-- Preferable keyboard height in absolute scale: 1.285in -->
+    <!-- This keyboardHeight value should match with keyboard-heights.xml -->
+    <dimen name="keyboardHeight">205.6dp</dimen>
     <fraction name="maxKeyboardHeight">50%p</fraction>
     <fraction name="minKeyboardHeight">-61.8%p</fraction>
 
@@ -35,7 +36,6 @@
     <fraction name="key_bottom_gap">6.250%p</fraction>
     <fraction name="key_horizontal_gap">1.352%p</fraction>
 
-    <dimen name="keyboardHeight_stone">1.317in</dimen>
     <fraction name="keyboard_top_padding_stone">1.556%p</fraction>
     <fraction name="keyboard_bottom_padding_stone">0.778%p</fraction>
     <fraction name="key_bottom_gap_stone">7.506%p</fraction>
diff --git a/java/res/values/donottranslate-config.xml b/java/res/values/donottranslate-config.xml
index 9b94272..ba1cecb 100644
--- a/java/res/values/donottranslate-config.xml
+++ b/java/res/values/donottranslate-config.xml
@@ -20,4 +20,5 @@
 
 <resources>
     <bool name="config_require_umlaut_processing">false</bool>
+    <bool name="config_require_ligatures_processing">false</bool>
 </resources>
diff --git a/java/res/values/keyboard-heights.xml b/java/res/values/keyboard-heights.xml
new file mode 100644
index 0000000..7d5b583
--- /dev/null
+++ b/java/res/values/keyboard-heights.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2012, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- Preferable keyboard height in absolute scale: 1.285in -->
+<resources>
+    <!-- Build.HARDWARE,keyboard_height_in_dp -->
+    <string-array name="keyboard_heights" translatable="false">
+        <!-- Droid -->
+        <item>sholes,227.0167</item>
+        <!-- Nexus One -->
+        <item>mahimahi,217.5932</item>
+        <!-- Nexus S -->
+        <item>herring,200.8554</item>
+        <!-- Galaxy Nexus -->
+        <item>tuna,202.5869</item>
+    </string-array>
+</resources>
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index 691f98a..b08ff3b 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -173,7 +173,6 @@
     >
         <!-- This should be aligned with KeyboardSwitcher.KEYBOARD_THEMES[] -->
         <item name="themeId">6</item>
-        <item name="keyboardHeight">@dimen/keyboardHeight_stone</item>
         <item name="keyboardTopPadding">@fraction/keyboard_top_padding_stone</item>
         <item name="keyboardBottomPadding">@fraction/keyboard_bottom_padding_stone</item>
         <item name="horizontalGap">@fraction/key_horizontal_gap_stone</item>
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 2b1cc43..07b9c1e 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -31,6 +31,7 @@
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.Utils;
 import com.android.inputmethod.latin.XmlParseUtils;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -715,22 +716,30 @@
                     R.styleable.Keyboard_Key);
             try {
                 final int displayHeight = mDisplayMetrics.heightPixels;
-                final int keyboardHeight = (int)keyboardAttr.getDimension(
-                        R.styleable.Keyboard_keyboardHeight, displayHeight / 2);
-                final int maxKeyboardHeight = (int)getDimensionOrFraction(keyboardAttr,
+                final String keyboardHeightString = Utils.getDeviceOverrideValue(
+                        mResources, R.array.keyboard_heights, null);
+                final float keyboardHeight;
+                if (keyboardHeightString != null) {
+                    keyboardHeight = Float.parseFloat(keyboardHeightString)
+                            * mDisplayMetrics.density;
+                } else {
+                    keyboardHeight = keyboardAttr.getDimension(
+                            R.styleable.Keyboard_keyboardHeight, displayHeight / 2);
+                }
+                final float maxKeyboardHeight = getDimensionOrFraction(keyboardAttr,
                         R.styleable.Keyboard_maxKeyboardHeight, displayHeight, displayHeight / 2);
-                int minKeyboardHeight = (int)getDimensionOrFraction(keyboardAttr,
+                float minKeyboardHeight = getDimensionOrFraction(keyboardAttr,
                         R.styleable.Keyboard_minKeyboardHeight, displayHeight, displayHeight / 2);
                 if (minKeyboardHeight < 0) {
                     // Specified fraction was negative, so it should be calculated against display
                     // width.
-                    minKeyboardHeight = -(int)getDimensionOrFraction(keyboardAttr,
+                    minKeyboardHeight = -getDimensionOrFraction(keyboardAttr,
                             R.styleable.Keyboard_minKeyboardHeight, displayWidth, displayWidth / 2);
                 }
                 final Params params = mParams;
                 // Keyboard height will not exceed maxKeyboardHeight and will not be less than
                 // minKeyboardHeight.
-                params.mOccupiedHeight = Math.max(
+                params.mOccupiedHeight = (int)Math.max(
                         Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight);
                 params.mOccupiedWidth = params.mId.mWidth;
                 params.mTopPadding = (int)getDimensionOrFraction(keyboardAttr,
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index dfc8c8e..a9df1ce 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -55,6 +55,8 @@
 
     public static final Flag FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING =
             new Flag(R.bool.config_require_umlaut_processing, 0x1);
+    public static final Flag FLAG_REQUIRES_FRENCH_LIGATURES_PROCESSING =
+            new Flag(R.bool.config_require_ligatures_processing, 0x4);
 
     // FULL_EDIT_DISTANCE is a flag that forces the dictionary to use full words
     // when computing edit distance, instead of the default behavior of stopping
@@ -77,6 +79,7 @@
         // actual value will be read from the configuration/extra value at run time for
         // the configuration at dictionary creation time.
         FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING,
+        FLAG_REQUIRES_FRENCH_LIGATURES_PROCESSING,
     };
 
     private int mFlags = 0;
@@ -202,9 +205,11 @@
 
         Arrays.fill(mInputCodes, WordComposer.NOT_A_CODE);
         for (int i = 0; i < codesSize; i++) {
-            int[] alternatives = codes.getCodesAt(i);
-            System.arraycopy(alternatives, 0, mInputCodes, i * MAX_PROXIMITY_CHARS_SIZE,
-                    Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE));
+            final int[] alternatives = codes.getCodesAt(i);
+            if (alternatives == null || alternatives.length < 1) {
+                continue;
+            }
+            mInputCodes[i] = alternatives[0];
         }
         Arrays.fill(outputChars, (char) 0);
         Arrays.fill(scores, 0);
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 231211d..cabf680 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -20,8 +20,6 @@
 import com.android.inputmethod.keyboard.KeyDetector;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
-import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService;
-import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -172,8 +170,7 @@
     /**
      * Internal method to retrieve reasonable proximity info for a character.
      */
-    private void addKeyInfo(final int codePoint, final Keyboard keyboard,
-            final KeyDetector keyDetector) {
+    private void addKeyInfo(final int codePoint, final Keyboard keyboard) {
         for (final Key key : keyboard.mKeys) {
             if (key.mCode == codePoint) {
                 final int x = key.mX + key.mWidth / 2;
@@ -191,27 +188,16 @@
      * Set the currently composing word to the one passed as an argument.
      * This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity.
      */
-    private void setComposingWord(final CharSequence word, final Keyboard keyboard,
-            final KeyDetector keyDetector) {
+    public void setComposingWord(final CharSequence word, final Keyboard keyboard) {
         reset();
         final int length = word.length();
         for (int i = 0; i < length; i = Character.offsetByCodePoints(word, i, 1)) {
             int codePoint = Character.codePointAt(word, i);
-            addKeyInfo(codePoint, keyboard, keyDetector);
+            addKeyInfo(codePoint, keyboard);
         }
     }
 
     /**
-     * Shortcut for the above method, this will create a new KeyDetector for the passed keyboard.
-     */
-    public void setComposingWord(final CharSequence word, final Keyboard keyboard) {
-        final KeyDetector keyDetector = new KeyDetector(0);
-        keyDetector.setKeyboard(keyboard, 0, 0);
-        keyDetector.setProximityCorrectionEnabled(true);
-        setComposingWord(word, keyboard, keyDetector);
-    }
-
-    /**
      * Delete the last keystroke as a result of hitting backspace.
      */
     public void deleteLast() {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
index 90c0aac..7627700 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -39,7 +39,7 @@
     final protected static void buildProximityIndices(final int[] proximity,
             final TreeMap<Integer, Integer> indices) {
         for (int i = 0; i < proximity.length; i += ROW_SIZE) {
-            if (NUL != proximity[i]) indices.put(proximity[i], i);
+            if (NUL != proximity[i]) indices.put(proximity[i], i / ROW_SIZE);
         }
     }
     final protected static int computeIndex(final int characterCode,
@@ -198,9 +198,8 @@
     // inferior to 1 << 16
     public static int getXYForCodePointAndScript(final int codePoint, final int script) {
         final int index = getIndexOfCodeForScript(codePoint, script);
-        // TODO: precompute index / ROW_SIZE
-        final int y = index / (PROXIMITY_GRID_WIDTH * ROW_SIZE);
-        final int x = (index / ROW_SIZE) % PROXIMITY_GRID_WIDTH;
+        final int y = index / PROXIMITY_GRID_WIDTH;
+        final int x = index % PROXIMITY_GRID_WIDTH;
         if (y > PROXIMITY_GRID_HEIGHT) {
             // Safety check, should be entirely useless
             throw new RuntimeException("Wrong y coordinate in spell checker proximity");
diff --git a/native/src/defines.h b/native/src/defines.h
index 2121534..e882c37 100644
--- a/native/src/defines.h
+++ b/native/src/defines.h
@@ -171,7 +171,7 @@
 #define EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO -2
 #define PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO -3
 #define ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO -4
-#define NOT_A_INDEX -1
+#define NOT_AN_INDEX -1
 #define NOT_A_FREQUENCY -1
 
 #define KEYCODE_SPACE ' '
diff --git a/native/src/proximity_info.cpp b/native/src/proximity_info.cpp
index 47f1376..c00c4c2 100644
--- a/native/src/proximity_info.cpp
+++ b/native/src/proximity_info.cpp
@@ -129,7 +129,7 @@
     return false;
 }
 
-bool ProximityInfo::isOnKey(const int keyId, const int x, const int y) {
+bool ProximityInfo::isOnKey(const int keyId, const int x, const int y) const {
     if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
     const int left = mKeyXCoordinates[keyId];
     const int top = mKeyYCoordinates[keyId];
@@ -138,7 +138,7 @@
     return left < right && top < bottom && x >= left && x < right && y >= top && y < bottom;
 }
 
-int ProximityInfo::squaredDistanceToEdge(const int keyId, const int x, const int y) {
+int ProximityInfo::squaredDistanceToEdge(const int keyId, const int x, const int y) const {
     if (keyId < 0) return true; // NOT_A_ID is -1, but return whenever < 0 just in case
     const int left = mKeyXCoordinates[keyId];
     const int top = mKeyYCoordinates[keyId];
@@ -152,59 +152,61 @@
 }
 
 void ProximityInfo::calculateNearbyKeyCodes(
-        const int x, const int y, const int32_t primaryKey, int *inputCodes) {
+        const int x, const int y, const int32_t primaryKey, int *inputCodes) const {
     int insertPos = 0;
     inputCodes[insertPos++] = primaryKey;
     const int startIndex = getStartIndexFromCoordinates(x, y);
-    for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
-        const int32_t c = mProximityCharsArray[startIndex + i];
-        if (c < KEYCODE_SPACE || c == primaryKey) {
-            continue;
-        }
-        const int keyIndex = getKeyIndex(c);
-        const bool onKey = isOnKey(keyIndex, x, y);
-        const int distance = squaredDistanceToEdge(keyIndex, x, y);
-        if (onKey || distance < MOST_COMMON_KEY_WIDTH_SQUARE) {
-            inputCodes[insertPos++] = c;
-            if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
-                if (DEBUG_DICT) {
-                    assert(false);
-                }
-                return;
-            }
-        }
-    }
-    const int additionalProximitySize =
-            AdditionalProximityChars::getAdditionalCharsSize(&mLocaleStr, primaryKey);
-    if (additionalProximitySize > 0) {
-        inputCodes[insertPos++] = ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE;
-        if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
-            if (DEBUG_DICT) {
-                assert(false);
-            }
-            return;
-        }
-
-        const int32_t* additionalProximityChars =
-                AdditionalProximityChars::getAdditionalChars(&mLocaleStr, primaryKey);
-        for (int j = 0; j < additionalProximitySize; ++j) {
-            const int32_t ac = additionalProximityChars[j];
-            int k = 0;
-            for (; k < insertPos; ++k) {
-                if ((int)ac == inputCodes[k]) {
-                    break;
-                }
-            }
-            if (k < insertPos) {
+    if (startIndex >= 0) {
+        for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
+            const int32_t c = mProximityCharsArray[startIndex + i];
+            if (c < KEYCODE_SPACE || c == primaryKey) {
                 continue;
             }
-            inputCodes[insertPos++] = ac;
+            const int keyIndex = getKeyIndex(c);
+            const bool onKey = isOnKey(keyIndex, x, y);
+            const int distance = squaredDistanceToEdge(keyIndex, x, y);
+            if (onKey || distance < MOST_COMMON_KEY_WIDTH_SQUARE) {
+                inputCodes[insertPos++] = c;
+                if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
+                    if (DEBUG_DICT) {
+                        assert(false);
+                    }
+                    return;
+                }
+            }
+        }
+        const int additionalProximitySize =
+                AdditionalProximityChars::getAdditionalCharsSize(&mLocaleStr, primaryKey);
+        if (additionalProximitySize > 0) {
+            inputCodes[insertPos++] = ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE;
             if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
                 if (DEBUG_DICT) {
                     assert(false);
                 }
                 return;
             }
+
+            const int32_t* additionalProximityChars =
+                    AdditionalProximityChars::getAdditionalChars(&mLocaleStr, primaryKey);
+            for (int j = 0; j < additionalProximitySize; ++j) {
+                const int32_t ac = additionalProximityChars[j];
+                int k = 0;
+                for (; k < insertPos; ++k) {
+                    if ((int)ac == inputCodes[k]) {
+                        break;
+                    }
+                }
+                if (k < insertPos) {
+                    continue;
+                }
+                inputCodes[insertPos++] = ac;
+                if (insertPos >= MAX_PROXIMITY_CHARS_SIZE) {
+                    if (DEBUG_DICT) {
+                        assert(false);
+                    }
+                    return;
+                }
+            }
         }
     }
     // Add a delimiter for the proximity characters
@@ -219,7 +221,7 @@
             MAX_WORD_LENGTH_INTERNAL * MAX_PROXIMITY_CHARS_SIZE * sizeof(mInputCodes[0]));
 
     for (int i = 0; i < inputLength; ++i) {
-        const int32_t primaryKey = inputCodes[i * MAX_PROXIMITY_CHARS_SIZE];
+        const int32_t primaryKey = inputCodes[i];
         const int x = xCoordinates[i];
         const int y = yCoordinates[i];
         int *proximities = &mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE];
@@ -304,7 +306,7 @@
 
 float ProximityInfo::calculateNormalizedSquaredDistance(
         const int keyIndex, const int inputIndex) const {
-    if (keyIndex == NOT_A_INDEX) {
+    if (keyIndex == NOT_AN_INDEX) {
         return NOT_A_DISTANCE_FLOAT;
     }
     if (!hasSweetSpotData(keyIndex)) {
@@ -325,11 +327,11 @@
 int ProximityInfo::getKeyIndex(const int c) const {
     if (KEY_COUNT == 0) {
         // We do not have the coordinate data
-        return NOT_A_INDEX;
+        return NOT_AN_INDEX;
     }
     const unsigned short baseLowerC = toBaseLowerCase(c);
     if (baseLowerC > MAX_CHAR_CODE) {
-        return NOT_A_INDEX;
+        return NOT_AN_INDEX;
     }
     return mCodeToKeyIndex[baseLowerC];
 }
diff --git a/native/src/proximity_info.h b/native/src/proximity_info.h
index cdc5a3d..c1eefea 100644
--- a/native/src/proximity_info.h
+++ b/native/src/proximity_info.h
@@ -92,10 +92,10 @@
         // the radius of the key is assigned to zero.
         return mSweetSpotRadii[keyIndex] > 0.0;
     }
-    bool isOnKey(const int keyId, const int x, const int y);
-    int squaredDistanceToEdge(const int keyId, const int x, const int y);
+    bool isOnKey(const int keyId, const int x, const int y) const;
+    int squaredDistanceToEdge(const int keyId, const int x, const int y) const;
     void calculateNearbyKeyCodes(
-            const int x, const int y, const int32_t primaryKey, int *inputCodes);
+            const int x, const int y, const int32_t primaryKey, int *inputCodes) const;
 
     const int MAX_PROXIMITY_CHARS_SIZE;
     const int KEYBOARD_WIDTH;
diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp
index 563541d..d21413d 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -34,16 +34,20 @@
         { 'o', 'e', 0x00F6 }, // U+00F6 : LATIN SMALL LETTER O WITH DIAERESIS
         { 'u', 'e', 0x00FC } }; // U+00FC : LATIN SMALL LETTER U WITH DIAERESIS
 
+const UnigramDictionary::digraph_t UnigramDictionary::FRENCH_LIGATURES_DIGRAPHS[] =
+        { { 'a', 'e', 0x00E6 }, // U+00E6 : LATIN SMALL LETTER AE
+        { 'o', 'e', 0x0153 } }; // U+0153 : LATIN SMALL LIGATURE OE
+
 // TODO: check the header
 UnigramDictionary::UnigramDictionary(const uint8_t* const streamStart, int typedLetterMultiplier,
         int fullWordMultiplier, int maxWordLength, int maxWords, int maxProximityChars,
         const bool isLatestDictVersion)
     : DICT_ROOT(streamStart), MAX_WORD_LENGTH(maxWordLength), MAX_WORDS(maxWords),
-    MAX_PROXIMITY_CHARS(maxProximityChars), IS_LATEST_DICT_VERSION(isLatestDictVersion),
+    IS_LATEST_DICT_VERSION(isLatestDictVersion),
     TYPED_LETTER_MULTIPLIER(typedLetterMultiplier), FULL_WORD_MULTIPLIER(fullWordMultiplier),
       // TODO : remove this variable.
     ROOT_POS(0),
-    BYTES_IN_ONE_CHAR(MAX_PROXIMITY_CHARS * sizeof(int)),
+    BYTES_IN_ONE_CHAR(sizeof(int)),
     MAX_DIGRAPH_SEARCH_DEPTH(DEFAULT_MAX_DIGRAPH_SEARCH_DEPTH) {
     if (DEBUG_DICT) {
         AKLOGI("UnigramDictionary - constructor");
@@ -53,9 +57,8 @@
 UnigramDictionary::~UnigramDictionary() {
 }
 
-static inline unsigned int getCodesBufferSize(const int *codes, const int codesSize,
-        const int MAX_PROXIMITY_CHARS) {
-    return sizeof(*codes) * MAX_PROXIMITY_CHARS * codesSize;
+static inline unsigned int getCodesBufferSize(const int *codes, const int codesSize) {
+    return sizeof(*codes) * codesSize;
 }
 
 // TODO: This needs to take a const unsigned short* and not tinker with its contents
@@ -73,7 +76,7 @@
 
     // Search for the first char of some digraph
     int lastDigraphIndex = -1;
-    const int thisChar = codes[i * MAX_PROXIMITY_CHARS];
+    const int thisChar = codes[i];
     for (lastDigraphIndex = digraphsSize - 1; lastDigraphIndex >= 0; --lastDigraphIndex) {
         if (thisChar == digraphs[lastDigraphIndex].first) break;
     }
@@ -81,7 +84,7 @@
     if (lastDigraphIndex < 0) return 0;
 
     // It's an interesting digraph if the second char matches too.
-    if (digraphs[lastDigraphIndex].second == codes[(i + 1) * MAX_PROXIMITY_CHARS]) {
+    if (digraphs[lastDigraphIndex].second == codes[i + 1]) {
         return digraphs[lastDigraphIndex].replacement;
     } else {
         return 0;
@@ -102,7 +105,7 @@
         WordsPriorityQueuePool *queuePool,
         const digraph_t* const digraphs, const unsigned int digraphsSize) {
 
-    const int startIndex = (codesDest - codesBuffer) / MAX_PROXIMITY_CHARS;
+    const int startIndex = codesDest - codesBuffer;
     if (currentDepth < MAX_DIGRAPH_SEARCH_DEPTH) {
         for (int i = 0; i < codesRemain; ++i) {
             xCoordinatesBuffer[startIndex + i] = xcoordinates[codesBufferSize - codesRemain + i];
@@ -126,19 +129,18 @@
                         replacementCodePoint;
                 getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates,
                         codesBuffer, xCoordinatesBuffer, yCoordinatesBuffer, codesBufferSize, flags,
-                        codesSrc + (i + 1) * MAX_PROXIMITY_CHARS, codesRemain - i - 1,
-                        currentDepth + 1, codesDest + i * MAX_PROXIMITY_CHARS, correction,
+                        codesSrc + i + 1, codesRemain - i - 1,
+                        currentDepth + 1, codesDest + i, correction,
                         queuePool, digraphs, digraphsSize);
 
                 // Copy the second char of the digraph in place, then continue processing on
                 // the remaining part of the word.
                 // In our example, after "pru" in the buffer copy the "e", and continue on "fen"
-                memcpy(codesDest + i * MAX_PROXIMITY_CHARS, codesSrc + i * MAX_PROXIMITY_CHARS,
-                        BYTES_IN_ONE_CHAR);
+                memcpy(codesDest + i, codesSrc + i, BYTES_IN_ONE_CHAR);
                 getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates,
                         codesBuffer, xCoordinatesBuffer, yCoordinatesBuffer, codesBufferSize, flags,
-                        codesSrc + i * MAX_PROXIMITY_CHARS, codesRemain - i, currentDepth + 1,
-                        codesDest + i * MAX_PROXIMITY_CHARS, correction, queuePool,
+                        codesSrc + i, codesRemain - i, currentDepth + 1,
+                        codesDest + i, correction, queuePool,
                         digraphs, digraphsSize);
                 return;
             }
@@ -173,7 +175,7 @@
     Correction* masterCorrection = correction;
     if (REQUIRES_GERMAN_UMLAUT_PROCESSING & flags)
     { // Incrementally tune the word and try all possibilities
-        int codesBuffer[getCodesBufferSize(codes, codesSize, MAX_PROXIMITY_CHARS)];
+        int codesBuffer[getCodesBufferSize(codes, codesSize)];
         int xCoordinatesBuffer[codesSize];
         int yCoordinatesBuffer[codesSize];
         getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
@@ -181,6 +183,15 @@
                 codesSize, flags, codes, codesSize, 0, codesBuffer, masterCorrection, queuePool,
                 GERMAN_UMLAUT_DIGRAPHS,
                 sizeof(GERMAN_UMLAUT_DIGRAPHS) / sizeof(GERMAN_UMLAUT_DIGRAPHS[0]));
+    } else if (REQUIRES_FRENCH_LIGATURES_PROCESSING & flags) {
+        int codesBuffer[getCodesBufferSize(codes, codesSize)];
+        int xCoordinatesBuffer[codesSize];
+        int yCoordinatesBuffer[codesSize];
+        getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
+                xCoordinatesBuffer, yCoordinatesBuffer,
+                codesSize, flags, codes, codesSize, 0, codesBuffer, masterCorrection, queuePool,
+                FRENCH_LIGATURES_DIGRAPHS,
+                sizeof(FRENCH_LIGATURES_DIGRAPHS) / sizeof(FRENCH_LIGATURES_DIGRAPHS[0]));
     } else { // Normal processing
         getWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, codesSize, flags,
                 masterCorrection, queuePool);
@@ -414,7 +425,7 @@
         if (inputWordStartPos > 0) {
             const int offset = inputWordStartPos;
             initSuggestions(proximityInfo, &xcoordinates[offset], &ycoordinates[offset],
-                    codes + offset * MAX_PROXIMITY_CHARS, inputWordLength, correction);
+                    codes + offset, inputWordLength, correction);
             queuePool->clearSubQueue(currentWordIndex);
             getSuggestionCandidates(useFullEditDistance, inputWordLength, correction,
                     queuePool, false, MAX_ERRORS_FOR_TWO_WORDS, currentWordIndex);
@@ -573,10 +584,6 @@
         const bool hasAutoCorrectionCandidate) {
     if (inputLength >= MAX_WORD_LENGTH) return;
     if (DEBUG_DICT) {
-        // MAX_PROXIMITY_CHARS_SIZE in ProximityInfo.java should be 16
-        assert(MAX_PROXIMITY_CHARS == 16);
-    }
-    if (DEBUG_DICT) {
         AKLOGI("--- Suggest multiple words");
     }
 
diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h
index b85913f..86bda77 100644
--- a/native/src/unigram_dictionary.h
+++ b/native/src/unigram_dictionary.h
@@ -143,7 +143,6 @@
     const uint8_t* const DICT_ROOT;
     const int MAX_WORD_LENGTH;
     const int MAX_WORDS;
-    const int MAX_PROXIMITY_CHARS;
     const bool IS_LATEST_DICT_VERSION;
     const int TYPED_LETTER_MULTIPLIER;
     const int FULL_WORD_MULTIPLIER;
@@ -157,9 +156,11 @@
     // Please update both at the same time.
     enum {
         REQUIRES_GERMAN_UMLAUT_PROCESSING = 0x1,
-        USE_FULL_EDIT_DISTANCE = 0x2
+        USE_FULL_EDIT_DISTANCE = 0x2,
+        REQUIRES_FRENCH_LIGATURES_PROCESSING = 0x4
     };
     static const digraph_t GERMAN_UMLAUT_DIGRAPHS[];
+    static const digraph_t FRENCH_LIGATURES_DIGRAPHS[];
 
     // Still bundled members
     unsigned short mWord[MAX_WORD_LENGTH_INTERNAL];// TODO: remove