merge in jb-release history after reset to jb-dev
diff --git a/java/proguard.flags b/java/proguard.flags
index fd73e12..34e23aa 100644
--- a/java/proguard.flags
+++ b/java/proguard.flags
@@ -7,6 +7,10 @@
   *;
 }
 
+-keep class com.android.inputmethod.keyboard.ProximityInfo {
+  <init>(com.android.inputmethod.keyboard.ProximityInfo);
+}
+
 -keep class com.android.inputmethod.latin.Suggest {
   <init>(...);
   com.android.inputmethod.latin.SuggestedWords getSuggestions(...);
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
index 3cfef97..8bc7893 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
@@ -29,7 +29,6 @@
 import android.util.SparseArray;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewParent;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.inputmethod.EditorInfo;
@@ -276,18 +275,7 @@
      */
     void sendAccessibilityEventForKey(Key key, int eventType) {
         final AccessibilityEvent event = createAccessibilityEvent(key, eventType);
-        final ViewParent parent = mKeyboardView.getParent();
-
-        if (parent == null) {
-            return;
-        }
-
-        if (!parent.requestSendAccessibilityEvent(mKeyboardView, event)) {
-            // TODO: Remove this line after the top-level view for the IME
-            // window is fixed to be non-null and requestSendAccessibilityEvent
-            // can return true.
-            mAccessibilityUtils.requestSendAccessibilityEvent(event);
-        }
+        mAccessibilityUtils.requestSendAccessibilityEvent(event);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 5ea28ab..9d8bace 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -42,6 +42,8 @@
     private final int mKeyboardMinWidth;
     private final int mKeyboardHeight;
     private final int mMostCommonKeyWidth;
+    private final Key[] mKeys;
+    private final TouchPositionCorrection mTouchPositionCorrection;
     private final Key[][] mGridNeighbors;
     private final String mLocaleStr;
 
@@ -62,13 +64,36 @@
         mKeyboardHeight = height;
         mKeyHeight = mostCommonKeyHeight;
         mMostCommonKeyWidth = mostCommonKeyWidth;
+        mKeys = keys;
+        mTouchPositionCorrection = touchPositionCorrection;
         mGridNeighbors = new Key[mGridSize][];
         if (minWidth == 0 || height == 0) {
             // No proximity required. Keyboard might be more keys keyboard.
             return;
         }
-        computeNearestNeighbors(
-                mostCommonKeyWidth, keys, touchPositionCorrection);
+        computeNearestNeighbors();
+        mNativeProximityInfo = createNativeProximityInfo();
+    }
+
+    // TODO: Remove this public constructor when the native part of the ProximityInfo becomes
+    // immutable.
+    // This public constructor aims only for test purpose.
+    public ProximityInfo(ProximityInfo o) {
+        mLocaleStr = o.mLocaleStr;
+        mGridWidth = o.mGridWidth;
+        mGridHeight = o.mGridHeight;
+        mGridSize = o.mGridSize;
+        mCellWidth = o.mCellWidth;
+        mCellHeight = o.mCellHeight;
+        mKeyboardMinWidth = o.mKeyboardMinWidth;
+        mKeyboardHeight = o.mKeyboardHeight;
+        mKeyHeight = o.mKeyHeight;
+        mMostCommonKeyWidth = o.mMostCommonKeyWidth;
+        mKeys = o.mKeys;
+        mTouchPositionCorrection = o.mTouchPositionCorrection;
+        mGridNeighbors = new Key[mGridSize][];
+        computeNearestNeighbors();
+        mNativeProximityInfo = createNativeProximityInfo();
     }
 
     public static ProximityInfo createDummyProximityInfo() {
@@ -100,8 +125,12 @@
 
     private native void releaseProximityInfoNative(long nativeProximityInfo);
 
-    private final void setProximityInfo(Key[][] gridNeighborKeys, int keyboardWidth,
-            int keyboardHeight, final Key[] keys, TouchPositionCorrection touchPositionCorrection) {
+    private final long createNativeProximityInfo() {
+        final Key[][] gridNeighborKeys = mGridNeighbors;
+        final int keyboardWidth = mKeyboardMinWidth;
+        final int keyboardHeight = mKeyboardHeight;
+        final Key[] keys = mKeys;
+        final TouchPositionCorrection touchPositionCorrection = mTouchPositionCorrection;
         final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE];
         Arrays.fill(proximityCharsArray, KeyDetector.NOT_A_CODE);
         for (int i = 0; i < mGridSize; ++i) {
@@ -156,7 +185,7 @@
             sweetSpotCenterXs = sweetSpotCenterYs = sweetSpotRadii = null;
         }
 
-        mNativeProximityInfo = setProximityInfoNative(mLocaleStr, MAX_PROXIMITY_CHARS_SIZE,
+        return setProximityInfoNative(mLocaleStr, MAX_PROXIMITY_CHARS_SIZE,
                 keyboardWidth, keyboardHeight, mGridWidth, mGridHeight, mMostCommonKeyWidth,
                 proximityCharsArray,
                 keyCount, keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes,
@@ -179,8 +208,9 @@
         }
     }
 
-    private void computeNearestNeighbors(int defaultWidth, final Key[] keys,
-            TouchPositionCorrection touchPositionCorrection) {
+    private void computeNearestNeighbors() {
+        final int defaultWidth = mMostCommonKeyWidth;
+        final Key[] keys = mKeys;
         final HashMap<Integer, Key> keyCodeMap = new HashMap<Integer, Key>();
         for (final Key key : keys) {
             keyCodeMap.put(key.mCode, key);
@@ -206,8 +236,6 @@
                         Arrays.copyOfRange(neighborKeys, 0, count);
             }
         }
-        setProximityInfo(mGridNeighbors, mKeyboardMinWidth, mKeyboardHeight, keys,
-                touchPositionCorrection);
     }
 
     public void fillArrayWithNearestKeyCodes(int x, int y, int primaryKeyCode, int[] dest) {
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
index f0076a5..ffdbfbb 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
+++ b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
@@ -22,6 +22,7 @@
 import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME;
 
 import android.os.Build;
+import android.text.TextUtils;
 import android.view.inputmethod.InputMethodSubtype;
 
 import java.util.ArrayList;
@@ -84,11 +85,14 @@
     }
 
     public static InputMethodSubtype[] createAdditionalSubtypesArray(String prefSubtypes) {
+        if (TextUtils.isEmpty(prefSubtypes)) {
+            return null;
+        }
         final String[] prefSubtypeArray = prefSubtypes.split(PREF_SUBTYPE_SEPARATOR);
         final ArrayList<InputMethodSubtype> subtypesList =
                 new ArrayList<InputMethodSubtype>(prefSubtypeArray.length);
-        for (int i = 0; i < prefSubtypeArray.length; i++) {
-            final InputMethodSubtype subtype = createAdditionalSubtype(prefSubtypeArray[i]);
+        for (final String prefSubtype : prefSubtypeArray) {
+            final InputMethodSubtype subtype = createAdditionalSubtype(prefSubtype);
             if (subtype.getNameResId() == SubtypeLocale.UNKNOWN_KEYBOARD_LAYOUT) {
                 // Skip unknown keyboard layout subtype. This may happen when predefined keyboard
                 // layout has been removed.
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index a4670da..37eced5 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -58,6 +58,9 @@
 
     public static final String QUERY_PARAMETER_MAY_PROMPT_USER = "mayPrompt";
     public static final String QUERY_PARAMETER_TRUE = "true";
+    public static final String QUERY_PARAMETER_DELETE_RESULT = "result";
+    public static final String QUERY_PARAMETER_SUCCESS = "success";
+    public static final String QUERY_PARAMETER_FAILURE = "failure";
 
     // Prevents this class to be accidentally instantiated.
     private BinaryDictionaryFileDumper() {
@@ -145,7 +148,7 @@
         final int MODE_MIN = COMPRESSED_CRYPTED_COMPRESSED;
         final int MODE_MAX = NONE;
 
-        final Uri wordListUri = getProviderUriBuilder(id).build();
+        final Uri.Builder wordListUriBuilder = getProviderUriBuilder(id);
         final String outputFileName = BinaryDictionaryGetter.getCacheFileName(id, locale, context);
 
         for (int mode = MODE_MIN; mode <= MODE_MAX; ++mode) {
@@ -154,6 +157,7 @@
             File outputFile = null;
             FileOutputStream outputStream = null;
             AssetFileDescriptor afd = null;
+            final Uri wordListUri = wordListUriBuilder.build();
             try {
                 // Open input.
                 afd = openAssetFileDescriptor(resolver, wordListUri);
@@ -190,9 +194,12 @@
                         break;
                     }
                 checkMagicAndCopyFileTo(new BufferedInputStream(inputStream), outputStream);
-                if (0 >= resolver.delete(wordListUri, null, null)) {
+                wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT,
+                        QUERY_PARAMETER_SUCCESS);
+                if (0 >= resolver.delete(wordListUriBuilder.build(), null, null)) {
                     Log.e(TAG, "Could not have the dictionary pack delete a word list");
                 }
+                BinaryDictionaryGetter.removeFilesWithIdExcept(context, id, outputFile);
                 // Success! Close files (through the finally{} clause) and return.
                 return AssetFileAddress.makeFromFileName(outputFileName);
             } catch (Exception e) {
@@ -225,9 +232,11 @@
         // We could not copy the file at all. This is very unexpected.
         // I'd rather not print the word list ID to the log out of security concerns
         Log.e(TAG, "Could not copy a word list. Will not be able to use it.");
-        // If we can't copy it we should probably delete it to avoid trying to copy it over
-        // and over each time we open LatinIME.
-        if (0 >= resolver.delete(wordListUri, null, null)) {
+        // If we can't copy it we should warn the dictionary provider so that it can mark it
+        // as invalid.
+        wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT,
+                QUERY_PARAMETER_FAILURE);
+        if (0 >= resolver.delete(wordListUriBuilder.build(), null, null)) {
             Log.e(TAG, "In addition, we were unable to delete it.");
         }
         return null;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 5acd629..063243e 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -283,6 +283,39 @@
     }
 
     /**
+     * Remove all files with the passed id, except the passed file.
+     *
+     * If a dictionary with a given ID has a metadata change that causes it to change
+     * path, we need to remove the old version. The only way to do this is to check all
+     * installed files for a matching ID in a different directory.
+     */
+    public static void removeFilesWithIdExcept(final Context context, final String id,
+            final File fileToKeep) {
+        try {
+            final File canonicalFileToKeep = fileToKeep.getCanonicalFile();
+            final File[] directoryList = getCachedDirectoryList(context);
+            if (null == directoryList) return;
+            for (File directory : directoryList) {
+                // There is one directory per locale. See #getCachedDirectoryList
+                if (!directory.isDirectory()) continue;
+                final File[] wordLists = directory.listFiles();
+                if (null == wordLists) continue;
+                for (File wordList : wordLists) {
+                    final String fileId = getWordListIdFromFileName(wordList.getName());
+                    if (fileId.equals(id)) {
+                        if (!canonicalFileToKeep.equals(wordList.getCanonicalFile())) {
+                            wordList.delete();
+                        }
+                    }
+                }
+            }
+        } catch (java.io.IOException e) {
+            Log.e(TAG, "IOException trying to cleanup files : " + e);
+        }
+    }
+
+
+    /**
      * Returns the id associated with the main word list for a specified locale.
      *
      * Word lists stored in Android Keyboard's resources are referred to as the "main"
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 139eb46..83658f7 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -2299,7 +2299,7 @@
             final InputConnection ic = getCurrentInputConnection();
             if (null != ic) {
                 final CharSequence lastChar = ic.getTextBeforeCursor(1, 0);
-                if (lastChar.length() > 0 && Character.isHighSurrogate(lastChar.charAt(0))) {
+                if (!TextUtils.isEmpty(lastChar) && Character.isHighSurrogate(lastChar.charAt(0))) {
                     ic.deleteSurroundingText(1, 0);
                 }
             }
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 9e478fa..c98a27b 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -503,27 +503,6 @@
         return true;
     }
 
-    // TODO: Use codepoint instead of char
-    private int searchBigramSuggestion(final char[] word, final int offset, final int length) {
-        // TODO This is almost O(n^2). Might need fix.
-        // search whether the word appeared in bigram data
-        int bigramSuggestSize = mBigramSuggestions.size();
-        for (int i = 0; i < bigramSuggestSize; i++) {
-            if (mBigramSuggestions.get(i).codePointCount() == length) {
-                boolean chk = true;
-                for (int j = 0; j < length; j++) {
-                    if (mBigramSuggestions.get(i).codePointAt(j) != word[offset+j]) {
-                        chk = false;
-                        break;
-                    }
-                }
-                if (chk) return i;
-            }
-        }
-
-        return -1;
-    }
-
     public void close() {
         final HashSet<Dictionary> dictionaries = new HashSet<Dictionary>();
         dictionaries.addAll(mUnigramDictionaries.values());
diff --git a/native/jni/src/correction.cpp b/native/jni/src/correction.cpp
index 5ae34cd..fe3f292 100644
--- a/native/jni/src/correction.cpp
+++ b/native/jni/src/correction.cpp
@@ -977,7 +977,7 @@
             }
             const int freq = freqArray[i];
             // Demote too short weak words
-            if (wordLength <= 4 && freq <= MAX_FREQ * 2 / 3 /* heuristic... */) {
+            if (wordLength <= 4 && freq <= SUPPRESS_SHORT_MULTIPLE_WORDS_THRESHOLD_FREQ) {
                 multiplyRate(100 * freq / MAX_FREQ, &totalFreq);
             }
             if (wordLength == 1) {
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index dfc5238..19f8434 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -228,6 +228,8 @@
 
 #define TWO_WORDS_CORRECTION_WITH_OTHER_ERROR_THRESHOLD 0.35
 #define START_TWO_WORDS_CORRECTION_THRESHOLD 0.185
+/* heuristic... This should be changed if we change the unit of the frequency. */
+#define SUPPRESS_SHORT_MULTIPLE_WORDS_THRESHOLD_FREQ (MAX_FREQ * 58 / 100)
 
 #define MAX_DEPTH_MULTIPLIER 3
 
diff --git a/native/jni/src/unigram_dictionary.cpp b/native/jni/src/unigram_dictionary.cpp
index 3c826e9..8285828 100644
--- a/native/jni/src/unigram_dictionary.cpp
+++ b/native/jni/src/unigram_dictionary.cpp
@@ -503,8 +503,12 @@
                 freqArray, wordLengthArray, currentWordIndex + 1, isSpaceProximity, outputWord);
         if (DEBUG_DICT) {
             DUMP_WORD(outputWord, tempOutputWordLength);
-            AKLOGI("Split two words: %d, %d, %d, %d, (%d) %d", freqArray[0], freqArray[1], pairFreq,
-                    inputLength, wordLengthArray[0], tempOutputWordLength);
+            for (int i = 0; i < currentWordIndex + 1; ++i) {
+                AKLOGI("Split %d,%d words: freq = %d, length = %d", i, currentWordIndex + 1,
+                        freqArray[i], wordLengthArray[i]);
+            }
+            AKLOGI("Split two words: freq = %d, length = %d, %d, isSpace ? %d", pairFreq,
+                    inputLength, tempOutputWordLength, isSpaceProximity);
         }
         addWord(outputWord, tempOutputWordLength, pairFreq, queuePool->getMasterQueue());
     }