Merge "Straighten out magic space vs real spaces behavior."
diff --git a/java/res/values-xlarge/config.xml b/java/res/values-xlarge/config.xml
index 2c125dc..80ef3cd 100644
--- a/java/res/values-xlarge/config.xml
+++ b/java/res/values-xlarge/config.xml
@@ -37,8 +37,7 @@
     <bool name="config_use_spacebar_language_switcher">false</bool>
     <!-- Showing mini keyboard, just above the touched point if true, aligned to the key if false -->
     <bool name="config_show_mini_keyboard_at_touched_point">true</bool>
-    <!-- The language is never displayed if == 0, always displayed if < 0 -->
-    <integer name="config_delay_before_fadeout_language_on_spacebar">1200</integer>
+    <integer name="config_delay_update_suggestions">180</integer>
     <!-- This configuration is the index of the array {@link KeyboardSwitcher.KEYBOARD_THEMES}. -->
     <string name="config_default_keyboard_theme_id" translatable="false">5</string>
     <string name="config_text_size_of_language_on_spacebar" translatable="false">medium</string>
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index 4651de7..ee86fbd 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -50,6 +50,9 @@
     <bool name="config_show_mini_keyboard_at_touched_point">false</bool>
     <!-- The language is never displayed if == 0, always displayed if < 0 -->
     <integer name="config_delay_before_fadeout_language_on_spacebar">1200</integer>
+    <integer name="config_delay_update_suggestions">100</integer>
+    <integer name="config_delay_update_old_suggestions">300</integer>
+    <integer name="config_delay_update_shift_state">100</integer>
     <integer name="config_duration_of_fadeout_language_on_spacebar">50</integer>
     <integer name="config_final_fadeout_percentage_of_language_on_spacebar">50</integer>
     <integer name="config_delay_before_preview">0</integer>
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index fb70b35..5c59d44 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -200,8 +200,8 @@
         }
 
         // Horizontal gap is divided equally to both sides of the key.
-        this.mX = x + mGap / 2;
-        this.mY = y;
+        mX = x + mGap / 2;
+        mY = y;
 
         final TypedArray keyAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
                 R.styleable.Keyboard_Key);
@@ -351,12 +351,13 @@
         final boolean rightEdge = (flags & Keyboard.EDGE_RIGHT) != 0;
         final boolean topEdge = (flags & Keyboard.EDGE_TOP) != 0;
         final boolean bottomEdge = (flags & Keyboard.EDGE_BOTTOM) != 0;
-        final int left = this.mX;
-        final int right = left + this.mWidth;
-        final int top = this.mY;
-        final int bottom = top + this.mHeight;
-        return (x >= left || leftEdge) && (x < right || rightEdge)
-                && (y >= top || topEdge) && (y < bottom || bottomEdge);
+        final int left = mX - mGap / 2;
+        final int right = left + mWidth + mGap;
+        final int top = mY;
+        final int bottom = top + mHeight + mKeyboard.getVerticalGap();
+        // In order to mitigate rounding errors, we use (left <= x <= right) here.
+        return (x >= left || leftEdge) && (x <= right || rightEdge)
+                && (y >= top || topEdge) && (y <= bottom || bottomEdge);
     }
 
     /**
@@ -366,10 +367,10 @@
      * @return the square of the distance of the point from the nearest edge of the key
      */
     public int squaredDistanceToEdge(int x, int y) {
-        final int left = this.mX;
-        final int right = left + this.mWidth;
-        final int top = this.mY;
-        final int bottom = top + this.mHeight;
+        final int left = mX;
+        final int right = left + mWidth;
+        final int top = mY;
+        final int bottom = top + mHeight;
         final int edgeX = x < left ? left : (x > right ? right : x);
         final int edgeY = y < top ? top : (y > bottom ? bottom : y);
         final int dx = x - edgeX;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index 2eeae96..0b13afe 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -91,11 +91,11 @@
      *
      * @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 #NOT_A_KEY} value.
+     *         initialized by {@link #NOT_A_CODE} value.
      */
     public int[] newCodeArray() {
         int[] codes = new int[getMaxNearbyKeys()];
-        Arrays.fill(codes, NOT_A_KEY);
+        Arrays.fill(codes, NOT_A_CODE);
         return codes;
     }
 
@@ -106,16 +106,20 @@
 
     /**
      * Insert the key into nearby keys buffer and sort nearby keys by ascending order of distance.
+     * If the distance of two keys are the same, the key which the point is on should be considered
+     * as a closer one.
      *
      * @param keyIndex index of the key.
      * @param distance distance between the key's edge and user touched point.
+     * @param isOnKey true if the point is on the key.
      * @return order of the key in the nearby buffer, 0 if it is the nearest key.
      */
-    private int sortNearbyKeys(int keyIndex, int distance) {
+    private int sortNearbyKeys(int keyIndex, int distance, boolean isOnKey) {
         final int[] distances = mDistances;
         final int[] indices = mIndices;
         for (int insertPos = 0; insertPos < distances.length; insertPos++) {
-            if (distance < distances[insertPos]) {
+            final int comparingDistance = distances[insertPos];
+            if (distance < comparingDistance || (distance == comparingDistance && isOnKey)) {
                 final int nextPos = insertPos + 1;
                 if (nextPos < distances.length) {
                     System.arraycopy(distances, insertPos, distances, nextPos,
@@ -174,11 +178,11 @@
         int primaryIndex = NOT_A_KEY;
         for (final int index : mKeyboard.getNearestKeys(touchX, touchY)) {
             final Key key = keys.get(index);
-            final boolean isInside = mKeyboard.isInside(key, touchX, touchY);
+            final boolean isOnKey = key.isOnKey(touchX, touchY);
             final int distance = key.squaredDistanceToEdge(touchX, touchY);
-            if (isInside || (mProximityCorrectOn && distance < mProximityThresholdSquare)) {
-                final int insertedPosition = sortNearbyKeys(index, distance);
-                if (insertedPosition == 0 && isInside)
+            if (isOnKey || (mProximityCorrectOn && distance < mProximityThresholdSquare)) {
+                final int insertedPosition = sortNearbyKeys(index, distance, isOnKey);
+                if (insertedPosition == 0 && isOnKey)
                     primaryIndex = index;
             }
         }
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index fa2e085..492883c 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -383,10 +383,6 @@
         mProximityInfo.setProximityInfo(mGridNeighbors, getMinWidth(), getHeight(), mKeys);
     }
 
-    public boolean isInside(Key key, int x, int y) {
-        return key.isOnKey(x, y);
-    }
-
     /**
      * Returns the indices of the keys that are closest to the given point.
      * @param x the x-coordinate of the point
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index a78ff7e..76a230f 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -28,6 +28,8 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
 import java.util.Locale;
 
 /**
@@ -79,27 +81,32 @@
     }
 
     /**
-     * Queries a content provider for dictionary data for some locale and returns it as a file name.
+     * Queries a content provider for dictionary data for some locale and returns the file addresses
      *
      * This will query a content provider for dictionary data for a given locale, and return
-     * the name of a file suitable to be mmap'ed. It will copy it to local storage if needed.
-     * It should also check the dictionary version to avoid unnecessary copies but this is
+     * the addresses of a file set the members of which are suitable to be mmap'ed. It will copy
+     * them to local storage if needed.
+     * It should also check the dictionary versions to avoid unnecessary copies but this is
      * still in TODO state.
      * This will make the data from the content provider the cached dictionary for this locale,
      * overwriting any previous cached data.
-     * @returns the name of the file, or null if no data could be obtained.
+     * @returns the addresses of the files, or null if no data could be obtained.
      * @throw FileNotFoundException if the provider returns non-existent data.
      * @throw IOException if the provider-returned data could not be read.
      */
-    public static String getDictionaryFileFromContentProvider(Locale locale, Context context)
-            throws FileNotFoundException, IOException {
+    public static List<AssetFileAddress> getDictSetFromContentProvider(Locale locale,
+            Context context) throws FileNotFoundException, IOException {
         // TODO: check whether the dictionary is the same or not and if it is, return the cached
         // file.
+        // TODO: This should be able to read a number of files from the dictionary pack, copy
+        // them all and return them.
         final ContentResolver resolver = context.getContentResolver();
         final Uri dictionaryPackUri = getProviderUri(locale);
         final AssetFileDescriptor afd = resolver.openAssetFileDescriptor(dictionaryPackUri, "r");
         if (null == afd) return null;
-        return copyFileTo(afd.createInputStream(), getCacheFileNameForLocale(locale, context));
+        final String fileName =
+                copyFileTo(afd.createInputStream(), getCacheFileNameForLocale(locale, context));
+        return Arrays.asList(AssetFileAddress.makeFromFileName(fileName));
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index c4e098a..562580d 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -22,6 +22,8 @@
 
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
 import java.util.Locale;
 
 /**
@@ -52,13 +54,13 @@
     }
 
     /**
-     * Returns a file address for a given locale, trying relevant methods in order.
+     * Returns a list of file addresses for a given locale, trying relevant methods in order.
      *
-     * Tries to get a binary dictionary from various sources, in order:
-     * - Uses a private method of getting a private dictionary, as implemented by the
+     * Tries to get binary dictionaries from various sources, in order:
+     * - Uses a private method of getting a private dictionaries, as implemented by the
      *   PrivateBinaryDictionaryGetter class.
      * If that fails:
-     * - Uses a content provider to get a public dictionary, as per the protocol described
+     * - Uses a content provider to get a public dictionary set, as per the protocol described
      *   in BinaryDictionaryFileDumper.
      * If that fails:
      * - Gets a file name from the fallback resource passed as an argument.
@@ -66,27 +68,25 @@
      * - Returns null.
      * @return The address of a valid file, or null.
      */
-    public static AssetFileAddress getDictionaryFile(Locale locale, Context context,
+    public static List<AssetFileAddress> getDictionaryFiles(Locale locale, Context context,
             int fallbackResId) {
-        // Try first to query a private file signed the same way.
-        final AssetFileAddress privateFile =
-                PrivateBinaryDictionaryGetter.getDictionaryFile(locale, context);
-        if (null != privateFile) {
-            return privateFile;
+        // Try first to query a private package signed the same way for private files.
+        final List<AssetFileAddress> privateFiles =
+                PrivateBinaryDictionaryGetter.getDictionaryFiles(locale, context);
+        if (null != privateFiles) {
+            return privateFiles;
         } else {
             try {
                 // If that was no-go, try to find a publicly exported dictionary.
-                final String fileName = BinaryDictionaryFileDumper.
-                        getDictionaryFileFromContentProvider(locale, context);
-                return AssetFileAddress.makeFromFileName(fileName);
+                return BinaryDictionaryFileDumper.getDictSetFromContentProvider(locale, context);
             } catch (FileNotFoundException e) {
                 Log.e(TAG, "Unable to create dictionary file from provider for locale "
                         + locale.toString() + ": falling back to internal dictionary");
-                return loadFallbackResource(context, fallbackResId);
+                return Arrays.asList(loadFallbackResource(context, fallbackResId));
             } catch (IOException e) {
                 Log.e(TAG, "Unable to read source data for locale "
                         + locale.toString() + ": falling back to internal dictionary");
-                return loadFallbackResource(context, fallbackResId);
+                return Arrays.asList(loadFallbackResource(context, fallbackResId));
             }
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index 4b64e53..3fcb6ed 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -16,6 +16,7 @@
 
 package com.android.inputmethod.latin;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
 
@@ -34,6 +35,10 @@
         mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
     }
 
+    public DictionaryCollection(Collection<Dictionary> dictionaries) {
+        mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
+    }
+
     @Override
     public void getWords(final WordComposer composer, final WordCallback callback) {
         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 fcb6343..605676d 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -22,6 +22,8 @@
 import android.util.Log;
 
 import java.io.File;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Locale;
 
 /**
@@ -49,11 +51,14 @@
             return new DictionaryCollection(createBinaryDictionary(context, fallbackResId));
         }
 
-        final AssetFileAddress dictFile = BinaryDictionaryGetter.getDictionaryFile(locale,
-                context, fallbackResId);
-        if (null == dictFile) return null;
-        return new DictionaryCollection(new BinaryDictionary(context,
-                dictFile.mFilename, dictFile.mOffset, dictFile.mLength, null));
+        final List<Dictionary> dictList = new LinkedList<Dictionary>();
+        for (final AssetFileAddress f : BinaryDictionaryGetter.getDictionaryFiles(locale,
+                context, fallbackResId)) {
+            dictList.add(new BinaryDictionary(context, f.mFilename, f.mOffset, f.mLength, null));
+        }
+
+        if (null == dictList) return null;
+        return new DictionaryCollection(dictList);
     }
 
     /**
@@ -65,7 +70,6 @@
     protected static BinaryDictionary createBinaryDictionary(Context context, int resId) {
         AssetFileDescriptor afd = null;
         try {
-            // TODO: IMPORTANT: Do not create a dictionary from a placeholder.
             afd = context.getResources().openRawResourceFd(resId);
             if (afd == null) {
                 Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 17a78d5..2afb1c9 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -110,9 +110,6 @@
      */
     public static final String IME_OPTION_NO_SETTINGS_KEY = "noSettingsKey";
 
-    private static final int DELAY_UPDATE_SUGGESTIONS = 180;
-    private static final int DELAY_UPDATE_OLD_SUGGESTIONS = 300;
-    private static final int DELAY_UPDATE_SHIFT_STATE = 100;
     private static final int EXTENDED_TOUCHABLE_REGION_HEIGHT = 100;
 
     // How many continuous deletes at which to start deleting at a higher speed.
@@ -193,6 +190,9 @@
     private boolean mConfigEnableShowSubtypeSettings;
     private boolean mConfigSwipeDownDismissKeyboardEnabled;
     private int mConfigDelayBeforeFadeoutLanguageOnSpacebar;
+    private int mConfigDelayUpdateSuggestions;
+    private int mConfigDelayUpdateOldSuggestions;
+    private int mConfigDelayUpdateShiftState;
     private int mConfigDurationOfFadeoutLanguageOnSpacebar;
     private float mConfigFinalFadeoutFactorOfLanguageOnSpacebar;
     private long mConfigDoubleSpacesTurnIntoPeriodTimeout;
@@ -313,7 +313,8 @@
 
         public void postUpdateSuggestions() {
             removeMessages(MSG_UPDATE_SUGGESTIONS);
-            sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTIONS), DELAY_UPDATE_SUGGESTIONS);
+            sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTIONS),
+                    mConfigDelayUpdateSuggestions);
         }
 
         public void cancelUpdateSuggestions() {
@@ -327,7 +328,7 @@
         public void postUpdateOldSuggestions() {
             removeMessages(MSG_UPDATE_OLD_SUGGESTIONS);
             sendMessageDelayed(obtainMessage(MSG_UPDATE_OLD_SUGGESTIONS),
-                    DELAY_UPDATE_OLD_SUGGESTIONS);
+                    mConfigDelayUpdateOldSuggestions);
         }
 
         public void cancelUpdateOldSuggestions() {
@@ -336,7 +337,7 @@
 
         public void postUpdateShiftKeyState() {
             removeMessages(MSG_UPDATE_SHIFT_STATE);
-            sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), DELAY_UPDATE_SHIFT_STATE);
+            sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), mConfigDelayUpdateShiftState);
         }
 
         public void cancelUpdateShiftState() {
@@ -345,7 +346,8 @@
 
         public void postUpdateBigramPredictions() {
             removeMessages(MSG_SET_BIGRAM_PREDICTIONS);
-            sendMessageDelayed(obtainMessage(MSG_SET_BIGRAM_PREDICTIONS), DELAY_UPDATE_SUGGESTIONS);
+            sendMessageDelayed(obtainMessage(MSG_SET_BIGRAM_PREDICTIONS),
+                    mConfigDelayUpdateSuggestions);
         }
 
         public void cancelUpdateBigramPredictions() {
@@ -428,6 +430,10 @@
                 R.bool.config_swipe_down_dismiss_keyboard_enabled);
         mConfigDelayBeforeFadeoutLanguageOnSpacebar = res.getInteger(
                 R.integer.config_delay_before_fadeout_language_on_spacebar);
+        mConfigDelayUpdateSuggestions = res.getInteger(R.integer.config_delay_update_suggestions);
+        mConfigDelayUpdateOldSuggestions = res.getInteger(
+                R.integer.config_delay_update_old_suggestions);
+        mConfigDelayUpdateShiftState = res.getInteger(R.integer.config_delay_update_shift_state);
         mConfigDurationOfFadeoutLanguageOnSpacebar = res.getInteger(
                 R.integer.config_duration_of_fadeout_language_on_spacebar);
         mConfigFinalFadeoutFactorOfLanguageOnSpacebar = res.getInteger(
diff --git a/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java
index 90726b0..eb740e1 100644
--- a/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java
@@ -18,11 +18,12 @@
 
 import android.content.Context;
 
+import java.util.List;
 import java.util.Locale;
 
 class PrivateBinaryDictionaryGetter {
     private PrivateBinaryDictionaryGetter() {}
-    public static AssetFileAddress getDictionaryFile(Locale locale, Context context) {
+    public static List<AssetFileAddress> getDictionaryFiles(Locale locale, Context context) {
         return null;
     }
 }