Merge "Make required binary dictionary version use FormatSpec.*"
diff --git a/java/res/values-sw540dp/config-per-form-factor.xml b/java/res/values-sw430dp/config-per-form-factor.xml
similarity index 76%
rename from java/res/values-sw540dp/config-per-form-factor.xml
rename to java/res/values-sw430dp/config-per-form-factor.xml
index 7f898e0..8868081 100644
--- a/java/res/values-sw540dp/config-per-form-factor.xml
+++ b/java/res/values-sw430dp/config-per-form-factor.xml
@@ -20,13 +20,14 @@
 
 <!-- Configuration values for Large Phone. -->
 <resources>
-    <bool name="config_enable_show_key_preview_popup_option">false</bool>
+    <bool name="config_enable_show_key_preview_popup_option">true</bool>
     <!-- Whether or not Popup on key press is enabled by default -->
-    <bool name="config_default_key_preview_popup">false</bool>
-    <bool name="config_default_sound_enabled">true</bool>
+    <bool name="config_default_key_preview_popup">true</bool>
+    <bool name="config_default_sound_enabled">false</bool>
     <bool name="config_enable_show_voice_key_option">true</bool>
-    <bool name="config_key_selection_by_dragging_finger">false</bool>
+    <bool name="config_key_selection_by_dragging_finger">true</bool>
     <!-- Showing more keys keyboard, just above the touched point if true, aligned to the key if
          false -->
     <bool name="config_show_more_keys_keyboard_at_touched_point">false</bool>
+    <bool name="config_use_fullscreen_mode">false</bool>
 </resources>
diff --git a/java/res/values-sw540dp/config-screen-metrics.xml b/java/res/values-sw430dp/config-screen-metrics.xml
similarity index 100%
rename from java/res/values-sw540dp/config-screen-metrics.xml
rename to java/res/values-sw430dp/config-screen-metrics.xml
diff --git a/java/res/values-sw540dp-land/config.xml b/java/res/values-sw600dp-land/config.xml
similarity index 96%
rename from java/res/values-sw540dp-land/config.xml
rename to java/res/values-sw600dp-land/config.xml
index f4785e7..bd123c4 100644
--- a/java/res/values-sw540dp-land/config.xml
+++ b/java/res/values-sw600dp-land/config.xml
@@ -18,10 +18,8 @@
 */
 -->
 
-<!-- Configuration values for Large Phone and Small Tablet Landscape. -->
+<!-- Configuration values for Small Tablet Landscape. -->
 <resources>
-    <bool name="config_use_fullscreen_mode">false</bool>
-
     <!-- Preferable keyboard height in absolute scale: 45.0mm -->
     <!-- This config_default_keyboard_height value should match with keyboard-heights.xml -->
     <dimen name="config_default_keyboard_height">283.5dp</dimen>
diff --git a/java/res/values-sw600dp/config-per-form-factor.xml b/java/res/values-sw600dp/config-per-form-factor.xml
index 1c7e677..aa9a054 100644
--- a/java/res/values-sw600dp/config-per-form-factor.xml
+++ b/java/res/values-sw600dp/config-per-form-factor.xml
@@ -29,4 +29,5 @@
     <!-- Showing more keys keyboard, just above the touched point if true, aligned to the key if
          false -->
     <bool name="config_show_more_keys_keyboard_at_touched_point">false</bool>
+    <bool name="config_use_fullscreen_mode">false</bool>
 </resources>
diff --git a/java/res/values-sw540dp/config.xml b/java/res/values-sw600dp/config.xml
similarity index 98%
rename from java/res/values-sw540dp/config.xml
rename to java/res/values-sw600dp/config.xml
index 8637d6b..700350c 100644
--- a/java/res/values-sw540dp/config.xml
+++ b/java/res/values-sw600dp/config.xml
@@ -18,7 +18,7 @@
 */
 -->
 
-<!-- Configuration values for Large Phone and Small Tablet Portrait. -->
+<!-- Configuration values for Small Tablet Portrait. -->
 <resources>
     <dimen name="config_key_hysteresis_distance">40.0dp</dimen>
 
diff --git a/java/res/values-sw540dp/touch-position-correction.xml b/java/res/values-sw600dp/touch-position-correction.xml
similarity index 100%
rename from java/res/values-sw540dp/touch-position-correction.xml
rename to java/res/values-sw600dp/touch-position-correction.xml
diff --git a/java/res/values-sw768dp-land/config.xml b/java/res/values-sw768dp-land/config.xml
index b97872f..4040e29 100644
--- a/java/res/values-sw768dp-land/config.xml
+++ b/java/res/values-sw768dp-land/config.xml
@@ -20,8 +20,6 @@
 
 <!-- Configuration values for Large Tablet Landscape. -->
 <resources>
-    <bool name="config_use_fullscreen_mode">false</bool>
-
     <!-- Preferable keyboard height in absolute scale: 58.0mm -->
     <!-- This config_default_keyboard_height value should match with keyboard-heights.xml -->
     <dimen name="config_default_keyboard_height">365.4dp</dimen>
diff --git a/java/res/values-sw768dp/config-per-form-factor.xml b/java/res/values-sw768dp/config-per-form-factor.xml
index 136f415..b90fbae 100644
--- a/java/res/values-sw768dp/config-per-form-factor.xml
+++ b/java/res/values-sw768dp/config-per-form-factor.xml
@@ -29,4 +29,5 @@
     <!-- Showing more keys keyboard, just above the touched point if true, aligned to the key if
          false -->
     <bool name="config_show_more_keys_keyboard_at_touched_point">true</bool>
+    <bool name="config_use_fullscreen_mode">false</bool>
 </resources>
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index 78e771f..c8e3c19 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -24,54 +24,54 @@
     keyboard_locale: script_name/keyboard_layout_set
     af: Afrikaans/qwerty
     ar: Arabic/arabic
-    (az: Azerbaijani/qwerty)  # disabled temporarily. waiting for string resources.
-    be_BY: Belarusian/east_slavic
+    (az_AZ: Azerbaijani (Azerbaijan)/qwerty)  # disabled temporarily. waiting for string resources.
+    be_BY: Belarusian (Belarus)/east_slavic
     bg: Bulgarian/bulgarian
     bg: Bulgarian/bulgarian_bds
     ca: Catalan/spanish
     cs: Czech/qwertz
     da: Danish/nordic
     de: German/qwertz
-    de_CH: German Switzerland/swiss
+    de_CH: German (Switzerland)/swiss
     el: Greek/greek
-    en_US: English United States/qwerty
-    en_GB: English Great Britain/qwerty
+    en_US: English (United States)/qwerty
+    en_GB: English (Great Britain)/qwerty
     eo: Esperanto/spanish
     es: Spanish/spanish
-    es_US: Spanish United States/spanish
-    (es_419: Spanish Latin America/qwerty)
-    et_EE: Estonian/nordic
+    es_US: Spanish (United States)/spanish
+    (es_419: Spanish (Latin America)/qwerty)
+    et_EE: Estonian (Estonia)/nordic
     fa: Persian/arabic
     fi: Finnish/nordic
     fr: French/azerty
-    fr_CA: French Canada/qwerty
-    fr_CH: French Switzerland/swiss
+    fr_CA: French (Canada)/qwerty
+    fr_CH: French (Switzerland)/swiss
     hi: Hindi/hindi
     hr: Croatian/qwertz
     hu: Hungarian/qwertz
-    hy_AM: Armenian Phonetic/armenian_phonetic
+    hy_AM: Armenian (Armenia) Phonetic/armenian_phonetic
     in: Indonesian/qwerty    # "id" is official language code of Indonesian.
     is: Icelandic/qwerty
     it: Italian/qwerty
     iw: Hebrew/hebrew        # "he" is official language code of Hebrew.
-    ka_GE: Georgian/georgian
+    ka_GE: Georgian (Georgia)/georgian
     (kk: Kazakh/east_slavic) # disabled temporarily. waiting for string resources.
-    km_KH: Khmer/khmer
+    km_KH: Khmer (Cambodia)/khmer
     ky: Kyrgyz/east_slavic
-    lo_LA: Lao/lao
+    lo_LA: Lao (Laos)/lao
     lt: Lithuanian/qwerty
     lv: Latvian/qwerty
     mk: Macedonian/south_slavic
-    mn_MN: Mongolian/mongolian
-    ms_MY: Malay/qwerty
+    mn_MN: Mongolian (Mongolia)/mongolian
+    ms_MY: Malay (Malaysia)/qwerty
     nb: Norwegian Bokmål/nordic
-    (ne: Nepali Romanized/nepali_romanized)  # disabled temporarily
-    (ne: Nepali Traditional/nepali_traditional)  # disabled temporarily
+    (ne_NP: Nepali (Nepal) Romanized/nepali_romanized)  # disabled temporarily
+    (ne_NP: Nepali (Nepal) Traditional/nepali_traditional)  # disabled temporarily
     nl: Dutch/qwerty
-    nl_BE: Dutch Belgium/azerty
+    nl_BE: Dutch (Belgium)/azerty
     pl: Polish/qwerty
-    pt_BR: Portuguese Brazil/qwerty
-    pt_PT: Portuguese Portugal/qwerty
+    pt_BR: Portuguese (Brazil)/qwerty
+    pt_PT: Portuguese (Portugal)/qwerty
     ro: Romanian/qwerty
     ru: Russian/east_slavic
     sk: Slovak/qwerty
@@ -135,7 +135,7 @@
     <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0x70b0f974"
-            android:imeSubtypeLocale="az"
+            android:imeSubtypeLocale="az_AZ"
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EmojiCapable"
             android:isAsciiCapable="true"
@@ -455,7 +455,7 @@
     <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_generic"
             android:subtypeId="0xd80a4cee"
-            android:imeSubtypeLocale="ne"
+            android:imeSubtypeLocale="ne_NP"
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="KeyboardLayoutSet=nepali_romanized,EmojiCapable"
             android:isAsciiCapable="false"
@@ -463,7 +463,7 @@
     <subtype android:icon="@drawable/ic_ime_switcher_dark"
             android:label="@string/subtype_nepali_traditional"
             android:subtypeId="0x5fafea88"
-            android:imeSubtypeLocale="ne"
+            android:imeSubtypeLocale="ne_NP"
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="KeyboardLayoutSet=nepali_traditional,EmojiCapable"
             android:isAsciiCapable="false"
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index f9e78bf..a2528e0 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -70,6 +70,7 @@
         public void startKeyRepeatTimerOf(PointerTracker tracker, int repeatCount, int delay);
         public void startLongPressTimerOf(PointerTracker tracker, int delay);
         public void cancelLongPressTimerOf(PointerTracker tracker);
+        public void cancelLongPressShiftKeyTimers();
         public void cancelKeyTimersOf(PointerTracker tracker);
         public void startDoubleTapShiftKeyTimer();
         public void cancelDoubleTapShiftKeyTimer();
@@ -90,6 +91,8 @@
             @Override
             public void cancelLongPressTimerOf(PointerTracker tracker) {}
             @Override
+            public void cancelLongPressShiftKeyTimers() {}
+            @Override
             public void cancelKeyTimersOf(PointerTracker tracker) {}
             @Override
             public void startDoubleTapShiftKeyTimer() {}
@@ -1338,6 +1341,9 @@
     }
 
     private void startLongPressTimer(final Key key) {
+        // Note that we need to cancel all active long press shift key timers if any whenever we
+        // start a new long press timer for both non-shift and shift keys.
+        sTimerProxy.cancelLongPressShiftKeyTimers();
         if (sInGesture) return;
         if (key == null) return;
         if (!key.isLongPressEnabled()) return;
@@ -1349,6 +1355,7 @@
         if (mIsInDraggingFinger && key.getMoreKeys() == null) return;
 
         final int delay = getLongPressTimeout(key.getCode());
+        if (delay <= 0) return;
         sTimerProxy.startLongPressTimerOf(this, delay);
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index dd98c17..0c80ce2 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -304,6 +304,7 @@
         mSwitchActions.setSymbolsKeyboard();
         mIsAlphabetMode = false;
         mIsSymbolShifted = false;
+        mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
         // Reset alphabet shift state.
         mAlphabetShiftState.setShiftLocked(false);
         mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
@@ -316,6 +317,7 @@
         mSwitchActions.setSymbolsShiftedKeyboard();
         mIsAlphabetMode = false;
         mIsSymbolShifted = true;
+        mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
         // Reset alphabet shift state.
         mAlphabetShiftState.setShiftLocked(false);
         mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
@@ -327,6 +329,7 @@
         }
         mIsAlphabetMode = false;
         mIsEmojiMode = true;
+        mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
         // Remember caps lock mode and reset alphabet shift state.
         mPrevMainKeyboardWasShiftLocked = mAlphabetShiftState.isShiftLocked();
         mAlphabetShiftState.setShiftLocked(false);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index 1b722c5..f708c2c 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -635,8 +635,8 @@
         /* 118 */ "\\%,\u2030",
     };
 
-    /* Language az: Azerbaijani */
-    private static final String[] LANGUAGE_az = {
+    /* Language az_AZ: Azerbaijani (Azerbaijan) */
+    private static final String[] LANGUAGE_az_AZ = {
         // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
         /* 0 */ "\u00E2",
         // U+0259: "ə" LATIN SMALL LETTER SCHWA
@@ -2454,8 +2454,8 @@
         /* 53 */ "!text/double_9qm_rqm",
     };
 
-    /* Language ne: Nepali */
-    private static final String[] LANGUAGE_ne = {
+    /* Language ne_NP: Nepali (Nepal) */
+    private static final String[] LANGUAGE_ne_NP = {
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
@@ -3519,7 +3519,7 @@
         "DEFAULT", LANGUAGE_DEFAULT, /* default */
         "af", LANGUAGE_af, /* Afrikaans */
         "ar", LANGUAGE_ar, /* Arabic */
-        "az", LANGUAGE_az, /* Azerbaijani */
+        "az" /* "az_AZ" */, LANGUAGE_az_AZ, /* Azerbaijani (Azerbaijan) */
         "be" /* "be_BY" */, LANGUAGE_be_BY, /* Belarusian (Belarus) */
         "bg", LANGUAGE_bg, /* Bulgarian */
         "ca", LANGUAGE_ca, /* Catalan */
@@ -3551,7 +3551,7 @@
         "mk", LANGUAGE_mk, /* Macedonian */
         "mn" /* "mn_MN" */, LANGUAGE_mn_MN, /* Mongolian (Mongolia) */
         "nb", LANGUAGE_nb, /* Norwegian Bokmål */
-        "ne", LANGUAGE_ne, /* Nepali */
+        "ne" /* "ne_NP" */, LANGUAGE_ne_NP, /* Nepali (Nepal) */
         "nl", LANGUAGE_nl, /* Dutch */
         "pl", LANGUAGE_pl, /* Polish */
         "pt", LANGUAGE_pt, /* Portuguese */
diff --git a/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java b/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java
index 88d0878..3298a3f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/TimerHandler.java
@@ -38,8 +38,9 @@
     private static final int MSG_TYPING_STATE_EXPIRED = 0;
     private static final int MSG_REPEAT_KEY = 1;
     private static final int MSG_LONGPRESS_KEY = 2;
-    private static final int MSG_DOUBLE_TAP_SHIFT_KEY = 3;
-    private static final int MSG_UPDATE_BATCH_INPUT = 4;
+    private static final int MSG_LONGPRESS_SHIFT_KEY = 3;
+    private static final int MSG_DOUBLE_TAP_SHIFT_KEY = 4;
+    private static final int MSG_UPDATE_BATCH_INPUT = 5;
 
     private final int mIgnoreAltCodeKeyTimeout;
     private final int mGestureRecognitionUpdateTime;
@@ -66,6 +67,7 @@
             tracker.onKeyRepeat(msg.arg1 /* code */, msg.arg2 /* repeatCount */);
             break;
         case MSG_LONGPRESS_KEY:
+        case MSG_LONGPRESS_SHIFT_KEY:
             cancelLongPressTimers();
             callbacks.onLongPress(tracker);
             break;
@@ -102,19 +104,31 @@
 
     @Override
     public void startLongPressTimerOf(final PointerTracker tracker, final int delay) {
-        if (delay <= 0) {
+        final Key key = tracker.getKey();
+        if (key == null) {
             return;
         }
-        sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay);
+        // Use a separate message id for long pressing shift key, because long press shift key
+        // timers should be canceled when other key is pressed.
+        final int messageId = (key.getCode() == Constants.CODE_SHIFT)
+                ? MSG_LONGPRESS_SHIFT_KEY : MSG_LONGPRESS_KEY;
+        sendMessageDelayed(obtainMessage(messageId, tracker), delay);
     }
 
     @Override
     public void cancelLongPressTimerOf(final PointerTracker tracker) {
         removeMessages(MSG_LONGPRESS_KEY, tracker);
+        removeMessages(MSG_LONGPRESS_SHIFT_KEY, tracker);
+    }
+
+    @Override
+    public void cancelLongPressShiftKeyTimers() {
+        removeMessages(MSG_LONGPRESS_SHIFT_KEY);
     }
 
     private void cancelLongPressTimers() {
         removeMessages(MSG_LONGPRESS_KEY);
+        removeMessages(MSG_LONGPRESS_SHIFT_KEY);
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 0f7263d..8b46655 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1469,6 +1469,7 @@
                 ResearchLogger.latinIME_maybeDoubleSpacePeriod(textToInsert,
                         false /* isBatchMode */);
             }
+            mWordComposer.doubleSpacePeriod();
             mKeyboardSwitcher.updateShiftState();
             return true;
         }
@@ -1793,10 +1794,19 @@
         final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
         if (Character.isLetterOrDigit(codePointBeforeCursor)
                 || currentSettingsValues.isUsuallyFollowedBySpace(codePointBeforeCursor)) {
+            final boolean autoShiftHasBeenOverriden = mKeyboardSwitcher.getKeyboardShiftMode() !=
+                    getCurrentAutoCapsState();
             mSpaceState = SPACE_STATE_PHANTOM;
+            if (!autoShiftHasBeenOverriden) {
+                // When we change the space state, we need to update the shift state of the
+                // keyboard unless it has been overridden manually. This is happening for example
+                // after typing some letters and a period, then gesturing; the keyboard is not in
+                // caps mode yet, but since a gesture is starting, it should go in caps mode,
+                // unless the user explictly said it should not.
+                mKeyboardSwitcher.updateShiftState();
+            }
         }
         mConnection.endBatchEdit();
-        mKeyboardSwitcher.updateShiftState();
         mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(getActualCapsMode(),
                 // Prev word is 1st word before cursor
                 getNthPreviousWordForSuggestion(currentSettingsValues, 1 /* nthPreviousWord */));
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 2f81d15..5ecfc67 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -471,7 +471,12 @@
         mCapsCount = 0;
         mDigitsCount = 0;
         mIsBatchMode = false;
-        mPreviousWord = mTypedWord.toString();
+        final boolean isWhitespace = 1 == StringUtils.codePointCount(separatorString)
+                && Character.isWhitespace(separatorString.codePointAt(0));
+        // If not whitespace, we don't use the previous word for suggestion. This is consistent
+        // with how we get the previous word for suggestion: see RichInputConnection#spaceRegex and
+        // LatinIME#getNthPreviousWordForSuggestion.
+        mPreviousWord = isWhitespace ? mTypedWord.toString() : null;
         mTypedWord.setLength(0);
         mCodePointSize = 0;
         mTrailingSingleQuotesCount = 0;
@@ -485,6 +490,13 @@
         return lastComposedWord;
     }
 
+    public void doubleSpacePeriod() {
+        // When a period was entered with a double space, the separator we got has been
+        // changed by a period (see #commitWord). We should not use the previous word for
+        // suggestion.
+        mPreviousWord = null;
+    }
+
     public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord,
             final String previousWord) {
         mPrimaryKeyCodes = lastComposedWord.mPrimaryKeyCodes;
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index f23fe46..af54805 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -188,7 +188,6 @@
     // us to change the format during development while having testing devices remove
     // older files with each upgrade, while still having a readable versioning scheme.
     public static final int VERSION2 = 2;
-    public static final int VERSION3 = 3;
     public static final int VERSION4 = 400;
     static final int MINIMUM_SUPPORTED_VERSION = VERSION2;
     static final int MAXIMUM_SUPPORTED_VERSION = VERSION4;
@@ -352,18 +351,19 @@
         public static final String DICTIONARY_ID_ATTRIBUTE = "dictionary";
         private static final String DICTIONARY_DESCRIPTION_ATTRIBUTE = "description";
         public FileHeader(final int headerSize, final DictionaryOptions dictionaryOptions,
-                final FormatOptions formatOptions) {
+                final FormatOptions formatOptions) throws UnsupportedFormatException {
             mDictionaryOptions = dictionaryOptions;
             mFormatOptions = formatOptions;
             mBodyOffset = formatOptions.mVersion < VERSION4 ? headerSize : 0;
             if (null == getLocaleString()) {
-                throw new RuntimeException("Cannot create a FileHeader without a locale");
+                throw new UnsupportedFormatException("Cannot create a FileHeader without a locale");
             }
             if (null == getVersion()) {
-                throw new RuntimeException("Cannot create a FileHeader without a version");
+                throw new UnsupportedFormatException(
+                        "Cannot create a FileHeader without a version");
             }
             if (null == getId()) {
-                throw new RuntimeException("Cannot create a FileHeader without an ID");
+                throw new UnsupportedFormatException("Cannot create a FileHeader without an ID");
             }
         }
 
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver2DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver2DictEncoder.java
index 6655442..e543042 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver2DictEncoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver2DictEncoder.java
@@ -68,7 +68,7 @@
     @Override
     public void writeDictionary(final FusionDictionary dict, final FormatOptions formatOptions)
             throws IOException, UnsupportedFormatException {
-        if (formatOptions.mVersion > FormatSpec.VERSION3) {
+        if (formatOptions.mVersion > FormatSpec.VERSION2) {
             throw new UnsupportedFormatException(
                     "The given format options has wrong version number : "
                     + formatOptions.mVersion);
diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
index a746f99..8eaee4d 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java
@@ -56,7 +56,8 @@
         }
         if (!BinaryDictionary.createEmptyDictFile(mDictPlacedDir.getAbsolutePath(),
                 FormatSpec.VERSION4, dict.mOptions.mAttributes)) {
-            throw new IOException("Cannot create dictionary file");
+            throw new IOException("Cannot create dictionary file : "
+                + mDictPlacedDir.getAbsolutePath());
         }
         final BinaryDictionary binaryDict = new BinaryDictionary(mDictPlacedDir.getAbsolutePath(),
                 0l, mDictPlacedDir.length(), true /* useFullEditDistance */,
diff --git a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
index b51fd93..be09554 100644
--- a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
@@ -40,12 +40,17 @@
      * are out of range in <code>dest</code>.
      */
     public static void copyNonParagraphSuggestionSpansFrom(Spanned source, int start, int end,
-                                     Spannable dest, int destoff) {
+            Spannable dest, int destoff) {
         Object[] spans = source.getSpans(start, end, SuggestionSpan.class);
 
         for (int i = 0; i < spans.length; i++) {
             int fl = source.getSpanFlags(spans[i]);
-            if (0 != (fl & Spannable.SPAN_PARAGRAPH)) continue;
+            // We don't care about the PARAGRAPH flag in LatinIME code. However, if this flag
+            // is set, Spannable#setSpan will throw an exception unless the span is on the edge
+            // of a word. But the spans have been split into two by the getText{Before,After}Cursor
+            // methods, so after concatenation they may end in the middle of a word.
+            // Since we don't use them, we can just remove them and avoid crashing.
+            fl &= ~Spannable.SPAN_PARAGRAPH;
 
             int st = source.getSpanStart(spans[i]);
             int en = source.getSpanEnd(spans[i]);
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
index aff045c..f52f5e7 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictDecoderEncoderTests.java
@@ -169,8 +169,7 @@
         long now = -1, diff = -1;
 
         try {
-            final DictEncoder dictEncoder = BinaryDictUtils.getDictEncoder(file, formatOptions,
-                    getContext().getCacheDir());
+            final DictEncoder dictEncoder = BinaryDictUtils.getDictEncoder(file, formatOptions);
 
             now = System.currentTimeMillis();
             // If you need to dump the dict to a textual file, uncomment the line below and the
@@ -316,7 +315,7 @@
         runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
                 BinaryDictUtils.VERSION2_OPTIONS);
         runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
-                BinaryDictUtils.VERSION3_OPTIONS);
+                BinaryDictUtils.VERSION4_OPTIONS_WITHOUT_TIMESTAMP);
         runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
                 BinaryDictUtils.VERSION4_OPTIONS_WITH_TIMESTAMP);
 
@@ -331,7 +330,7 @@
         runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
                 BinaryDictUtils.VERSION2_OPTIONS);
         runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
-                BinaryDictUtils.VERSION3_OPTIONS);
+                BinaryDictUtils.VERSION4_OPTIONS_WITHOUT_TIMESTAMP);
         runReadAndWriteTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
                 BinaryDictUtils.VERSION4_OPTIONS_WITH_TIMESTAMP);
 
@@ -390,8 +389,6 @@
 
     private long timeAndCheckReadUnigramsAndBigramsBinary(final File file, final List<String> words,
             final SparseArray<List<Integer>> bigrams, final int bufferType) {
-        FileInputStream inStream = null;
-
         final TreeMap<Integer, String> resultWords = CollectionUtils.newTreeMap();
         final TreeMap<Integer, ArrayList<PendingAttribute>> resultBigrams =
                 CollectionUtils.newTreeMap();
@@ -407,14 +404,6 @@
             Log.e(TAG, "IOException", e);
         } catch (UnsupportedFormatException e) {
             Log.e(TAG, "UnsupportedFormatException", e);
-        } finally {
-            if (inStream != null) {
-                try {
-                    inStream.close();
-                } catch (IOException e) {
-                    // do nothing
-                }
-            }
         }
 
         checkWordMap(words, bigrams, resultWords, resultFreqs, resultBigrams);
@@ -461,7 +450,7 @@
         runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
                 BinaryDictUtils.VERSION2_OPTIONS);
         runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
-                BinaryDictUtils.VERSION3_OPTIONS);
+                BinaryDictUtils.VERSION4_OPTIONS_WITHOUT_TIMESTAMP);
         runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_BUFFER,
                 BinaryDictUtils.VERSION4_OPTIONS_WITH_TIMESTAMP);
 
@@ -476,7 +465,7 @@
         runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
                 BinaryDictUtils.VERSION2_OPTIONS);
         runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
-                BinaryDictUtils.VERSION3_OPTIONS);
+                BinaryDictUtils.VERSION4_OPTIONS_WITHOUT_TIMESTAMP);
         runReadUnigramsAndBigramsTests(results, BinaryDictUtils.USE_BYTE_ARRAY,
                 BinaryDictUtils.VERSION4_OPTIONS_WITH_TIMESTAMP);
 
@@ -590,14 +579,14 @@
         runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_ARRAY,
                 BinaryDictUtils.VERSION2_OPTIONS);
         runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_ARRAY,
-                BinaryDictUtils.VERSION3_OPTIONS);
+                BinaryDictUtils.VERSION4_OPTIONS_WITHOUT_TIMESTAMP);
         runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_ARRAY,
                 BinaryDictUtils.VERSION4_OPTIONS_WITH_TIMESTAMP);
 
         runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_BUFFER,
                 BinaryDictUtils.VERSION2_OPTIONS);
         runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_BUFFER,
-                BinaryDictUtils.VERSION3_OPTIONS);
+                BinaryDictUtils.VERSION4_OPTIONS_WITHOUT_TIMESTAMP);
         runGetTerminalPositionTests(BinaryDictUtils.USE_BYTE_BUFFER,
                 BinaryDictUtils.VERSION4_OPTIONS_WITH_TIMESTAMP);
 
@@ -633,6 +622,7 @@
     }
 
     public void testDeleteWord() throws IOException, UnsupportedFormatException {
-        runTestDeleteWord(BinaryDictUtils.VERSION3_OPTIONS);
+        runTestDeleteWord(BinaryDictUtils.VERSION4_OPTIONS_WITHOUT_TIMESTAMP);
+        runTestDeleteWord(BinaryDictUtils.VERSION4_OPTIONS_WITH_TIMESTAMP);
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
index ba1b6b3..9ed50c4 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
@@ -237,8 +237,7 @@
         dict.add("abcd", 10, null, false);
 
         try {
-            final DictEncoder dictEncoder = BinaryDictUtils.getDictEncoder(file, formatOptions,
-                    getContext().getCacheDir());
+            final DictEncoder dictEncoder = BinaryDictUtils.getDictEncoder(file, formatOptions);
             dictEncoder.writeDictionary(dict, formatOptions);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
@@ -289,7 +288,6 @@
     }
 
     public void testInsertWord() {
-        runTestInsertWord(BinaryDictUtils.VERSION3_OPTIONS);
         runTestInsertWord(BinaryDictUtils.VERSION4_OPTIONS_WITHOUT_TIMESTAMP);
         runTestInsertWord(BinaryDictUtils.VERSION4_OPTIONS_WITH_TIMESTAMP);
     }
@@ -307,8 +305,7 @@
         dict.add("efgh", 15, null, false);
 
         try {
-            final DictEncoder dictEncoder = BinaryDictUtils.getDictEncoder(file, formatOptions,
-                    getContext().getCacheDir());
+            final DictEncoder dictEncoder = BinaryDictUtils.getDictEncoder(file, formatOptions);
             dictEncoder.writeDictionary(dict, formatOptions);
         } catch (IOException e) {
             fail("IOException while writing an initial dictionary : " + e);
@@ -330,7 +327,6 @@
     }
 
     public void testInsertWordWithBigrams() {
-        runTestInsertWordWithBigrams(BinaryDictUtils.VERSION3_OPTIONS);
         runTestInsertWordWithBigrams(BinaryDictUtils.VERSION4_OPTIONS_WITHOUT_TIMESTAMP);
         runTestInsertWordWithBigrams(BinaryDictUtils.VERSION4_OPTIONS_WITH_TIMESTAMP);
     }
@@ -347,8 +343,7 @@
         dict.add("initial", 10, null, false);
 
         try {
-            final DictEncoder dictEncoder = BinaryDictUtils.getDictEncoder(file, formatOptions,
-                    getContext().getCacheDir());
+            final DictEncoder dictEncoder = BinaryDictUtils.getDictEncoder(file, formatOptions);
             dictEncoder.writeDictionary(dict, formatOptions);
         } catch (IOException e) {
             assertTrue(false);
@@ -379,7 +374,6 @@
     }
 
     public void testRandomWords() {
-        runTestRandomWords(BinaryDictUtils.VERSION3_OPTIONS);
         runTestRandomWords(BinaryDictUtils.VERSION4_OPTIONS_WITHOUT_TIMESTAMP);
         runTestRandomWords(BinaryDictUtils.VERSION4_OPTIONS_WITH_TIMESTAMP);
     }
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictUtils.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictUtils.java
index a568704..67d77e0 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictUtils.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictUtils.java
@@ -31,8 +31,6 @@
 
     public static final FormatSpec.FormatOptions VERSION2_OPTIONS =
             new FormatSpec.FormatOptions(FormatSpec.VERSION2);
-    public static final FormatSpec.FormatOptions VERSION3_OPTIONS =
-            new FormatSpec.FormatOptions(FormatSpec.VERSION3);
     public static final FormatSpec.FormatOptions VERSION4_OPTIONS_WITHOUT_TIMESTAMP =
             new FormatSpec.FormatOptions(FormatSpec.VERSION4, false /* hasTimestamp */);
     public static final FormatSpec.FormatOptions VERSION4_OPTIONS_WITH_TIMESTAMP =
@@ -48,8 +46,7 @@
 
     public static File getDictFile(final String name, final String version,
             final FormatOptions formatOptions, final File directory) {
-        if (formatOptions.mVersion == FormatSpec.VERSION2
-                || formatOptions.mVersion == FormatSpec.VERSION3) {
+        if (formatOptions.mVersion == FormatSpec.VERSION2) {
             return new File(directory, name + "." + version + TEST_DICT_FILE_EXTENSION);
         } else if (formatOptions.mVersion == FormatSpec.VERSION4) {
             return new File(directory, name + "." + version);
@@ -59,12 +56,13 @@
         }
     }
 
-    public static DictEncoder getDictEncoder(final File file, final FormatOptions formatOptions,
-            final File cacheDir) {
+    public static DictEncoder getDictEncoder(final File file, final FormatOptions formatOptions) {
         if (formatOptions.mVersion == FormatSpec.VERSION4) {
-            return new Ver4DictEncoder(cacheDir);
-        } else if (formatOptions.mVersion == FormatSpec.VERSION3
-                || formatOptions.mVersion == FormatSpec.VERSION2) {
+            if (!file.isDirectory()) {
+                file.mkdir();
+            }
+            return new Ver4DictEncoder(file);
+        } else if (formatOptions.mVersion == FormatSpec.VERSION2) {
             return new Ver2DictEncoder(file);
         } else {
             throw new RuntimeException("The format option has a wrong version : "
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java
index eb419fc..143bce5 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java
@@ -46,7 +46,6 @@
 
     static class Arguments {
         private static final String OPTION_VERSION_2 = "-2";
-        private static final String OPTION_VERSION_3 = "-3";
         private static final String OPTION_VERSION_4 = "-4";
         private static final String OPTION_INPUT_SOURCE = "-s";
         private static final String OPTION_INPUT_BIGRAM_XML = "-b";
@@ -158,8 +157,6 @@
                 if (arg.charAt(0) == '-') {
                     if (OPTION_VERSION_2.equals(arg)) {
                         // Do nothing, this is the default
-                    } else if (OPTION_VERSION_3.equals(arg)) {
-                        outputBinaryFormatVersion = FormatSpec.VERSION3;
                     } else if (OPTION_VERSION_4.equals(arg)) {
                         outputBinaryFormatVersion = FormatSpec.VERSION4;
                     } else if (OPTION_HELP.equals(arg)) {
diff --git a/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java b/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
index 9e397f1..0c11f86 100644
--- a/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
+++ b/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
@@ -42,9 +42,16 @@
     private static final int TEST_FREQ = 37; // Some arbitrary value unlikely to happen by chance
 
     public void testGetRawDictWorks() throws IOException, UnsupportedFormatException {
+        final String VERSION = "1";
+        final String LOCALE = "test";
+        final String ID = "main:test";
+
         // Create a thrice-compressed dictionary file.
-        final FusionDictionary dict = new FusionDictionary(new PtNodeArray(),
-                new DictionaryOptions(new HashMap<String, String>()));
+        final DictionaryOptions testOptions = new DictionaryOptions(new HashMap<String, String>());
+        testOptions.mAttributes.put(FormatSpec.FileHeader.DICTIONARY_VERSION_ATTRIBUTE, VERSION);
+        testOptions.mAttributes.put(FormatSpec.FileHeader.DICTIONARY_LOCALE_ATTRIBUTE, LOCALE);
+        testOptions.mAttributes.put(FormatSpec.FileHeader.DICTIONARY_ID_ATTRIBUTE, ID);
+        final FusionDictionary dict = new FusionDictionary(new PtNodeArray(), testOptions);
         dict.add("foo", TEST_FREQ, null, false /* isNotAWord */);
         dict.add("fta", 1, null, false /* isNotAWord */);
         dict.add("ftb", 1, null, false /* isNotAWord */);
@@ -72,6 +79,12 @@
         final FusionDictionary resultDict = dictDecoder.readDictionaryBinary(
                 null /* dict : an optional dictionary to add words to, or null */,
                 false /* deleteDictIfBroken */);
+        assertEquals("Wrong version attribute", VERSION, resultDict.mOptions.mAttributes.get(
+                FormatSpec.FileHeader.DICTIONARY_VERSION_ATTRIBUTE));
+        assertEquals("Wrong locale attribute", LOCALE, resultDict.mOptions.mAttributes.get(
+                FormatSpec.FileHeader.DICTIONARY_LOCALE_ATTRIBUTE));
+        assertEquals("Wrong id attribute", ID, resultDict.mOptions.mAttributes.get(
+                FormatSpec.FileHeader.DICTIONARY_ID_ATTRIBUTE));
         assertEquals("Dictionary can't be read back correctly",
                 FusionDictionary.findWordInTree(resultDict.mRootNodeArray, "foo").getFrequency(),
                 TEST_FREQ);
diff --git a/tools/make-keyboard-text/res/values-az/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-az-rAZ/donottranslate-more-keys.xml
similarity index 100%
rename from tools/make-keyboard-text/res/values-az/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-az-rAZ/donottranslate-more-keys.xml
diff --git a/tools/make-keyboard-text/res/values-ne/donottranslate-more-keys.xml b/tools/make-keyboard-text/res/values-ne-rNP/donottranslate-more-keys.xml
similarity index 100%
rename from tools/make-keyboard-text/res/values-ne/donottranslate-more-keys.xml
rename to tools/make-keyboard-text/res/values-ne-rNP/donottranslate-more-keys.xml