Merge "Forward the capitalized mode to the positional info (D1)"
diff --git a/java/res/values-fr/donottranslate.xml b/java/res/values-fr/donottranslate.xml
index e11e65c..2e916a7 100644
--- a/java/res/values-fr/donottranslate.xml
+++ b/java/res/values-fr/donottranslate.xml
@@ -18,15 +18,13 @@
 */
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Symbols that should be swapped with a magic space -->
-    <string name="weak_space_swapping_symbols">.,)]}</string>
-    <!-- Symbols that should strip a magic space -->
+    <!-- Symbols that are normally preceded by a space (used to add an auto-space before these) -->
+    <string name="symbols_preceded_by_space">([{*&amp;;:!?</string>
+    <!-- Symbols that are normally followed by a space (used to add an auto-space after these) -->
+    <string name="symbols_followed_by_space">.,;:!?)]}*&amp;</string>
+    <!-- Symbols that separate words -->
     <!-- Don't remove the enclosing double quotes, they protect whitespace (not just U+0020) -->
-    <string name="weak_space_stripping_symbols">"&#x0009;&#x0020;\n"\'-/_\"</string>
-    <!-- Symbols that should promote magic spaces into real space -->
-    <string name="phantom_space_promoting_symbols">;:!?([*&amp;@{&lt;&gt;+=|</string>
-    <!-- Symbols that do NOT separate words -->
-    <!-- Note that this is identical to the default value, but since the above ones are different
-         and those variables only make sense together, this is kept here for readability. -->
-    <string name="symbols_excluded_from_word_separators">\'-</string>
+    <string name="symbols_word_separators">"&#x0009;&#x0020;\n"()[]{}*&amp;&lt;&gt;+=|.,;:!?/_\"</string>
+    <!-- Word connectors -->
+    <string name="symbols_word_connectors">\'-</string>
 </resources>
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index 193a019..70ace77 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -20,18 +20,15 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Symbols that are suggested between words -->
     <string name="suggested_punctuations">!,?,\\,,:,;,\",(,),\',-,/,@,_</string>
-    <!-- Symbols that should be swapped with a weak space -->
-    <string name="weak_space_swapping_symbols">.,;:!?)]}</string>
-    <!-- Symbols that should strip a weak space -->
+    <!-- Symbols that are normally preceded by a space (used to add an auto-space before these) -->
+    <string name="symbols_preceded_by_space">([{*&amp;</string>
+    <!-- Symbols that are normally followed by a space (used to add an auto-space after these) -->
+    <string name="symbols_followed_by_space">.,;:!?)]}*&amp;</string>
+    <!-- Symbols that separate words -->
     <!-- Don't remove the enclosing double quotes, they protect whitespace (not just U+0020) -->
-    <string name="weak_space_stripping_symbols">"&#x0009;&#x0020;\n"/_\'-@\"</string>
-    <!-- Symbols that should convert weak spaces into real space -->
-    <string name="phantom_space_promoting_symbols">([*&amp;{&lt;&gt;+=|</string>
-    <!-- Symbols that do NOT separate words -->
-    <string name="symbols_excluded_from_word_separators">\'-</string>
-    <!-- Word separator list is the union of all symbols except those that are not separators:
-    weak_space_swapping_symbols | weak_space_stripping_symbols
-            \ symbols_excluded_from_word_separators -->
+    <string name="symbols_word_separators">"&#x0009;&#x0020;\n"()[]{}*&amp;&lt;&gt;+=|.,;:!?/_\"</string>
+    <!-- Word connectors -->
+    <string name="symbols_word_connectors">\'-</string>
     <!-- Symbol characters list that should switch back to the main layout -->
     <!-- U+2018: "‘" LEFT SINGLE QUOTATION MARK
          U+2019: "’" RIGHT SINGLE QUOTATION MARK
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 5c54427..fd7aac4 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -376,4 +376,7 @@
     <string name="prefs_keypress_vibration_duration_settings">Keypress vibration duration settings</string>
     <!-- Title of the settings for keypress sound volume -->
     <string name="prefs_keypress_sound_volume_settings">Keypress sound volume settings</string>
+
+    <!-- Title of the button to revert to the default value of the device in the settings dialog [CHAR LIMIT=15] -->
+    <string name="button_default">Default</string>
 </resources>
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 6a01b01..ea86d98 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -209,7 +209,7 @@
     private String getDescriptionForActionKey(final Context context, final Keyboard keyboard,
             final Key key) {
         final KeyboardId keyboardId = keyboard.mId;
-        final int actionId = keyboardId.imeActionId();
+        final int actionId = keyboardId.imeAction();
         final int resId;
 
         // Always use the label, if available.
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index f9ff7b0..02116ca 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -172,12 +172,7 @@
     }
 
     public int imeAction() {
-        return InputTypeUtils.getActionIdFromEditorInfo(mEditorInfo);
-    }
-
-    public int imeActionId() {
-        final int actionId = imeAction();
-        return actionId == InputTypeUtils.IME_ACTION_CUSTOM_LABEL ? mEditorInfo.actionId : actionId;
+        return InputTypeUtils.getImeOptionsActionIdFromEditorInfo(mEditorInfo);
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/latin/InputTypeUtils.java b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
index 55414b8..e2eacb3 100644
--- a/java/src/com/android/inputmethod/latin/InputTypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
@@ -105,7 +105,7 @@
         return true;
     }
 
-    public static int getActionIdFromEditorInfo(final EditorInfo editorInfo) {
+    public static int getImeOptionsActionIdFromEditorInfo(final EditorInfo editorInfo) {
         final int actionId = editorInfo.imeOptions & EditorInfo.IME_MASK_ACTION;
         if ((editorInfo.imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
             return EditorInfo.IME_ACTION_NONE;
@@ -115,4 +115,9 @@
             return actionId;
         }
     }
+
+    public static int getConcreteActionIdFromEditorInfo(final EditorInfo editorInfo) {
+        final int actionId = getImeOptionsActionIdFromEditorInfo(editorInfo);
+        return actionId == InputTypeUtils.IME_ACTION_CUSTOM_LABEL ? editorInfo.actionId : actionId;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index f867a4e..d02c4df 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -166,6 +166,7 @@
     private boolean mExpectingUpdateSelection;
     private int mDeleteCount;
     private long mLastKeyTime;
+    private int mActionId;
 
     // Member variables for remembering the current device orientation.
     private int mDisplayOrientation;
@@ -754,6 +755,7 @@
 
         mLastSelectionStart = editorInfo.initialSelStart;
         mLastSelectionEnd = editorInfo.initialSelEnd;
+        mActionId = InputTypeUtils.getConcreteActionIdFromEditorInfo(editorInfo);
 
         mHandler.cancelUpdateSuggestionStrip();
         mHandler.cancelDoubleSpacePeriodTimer();
@@ -1273,10 +1275,6 @@
         return mOptionsDialog != null && mOptionsDialog.isShowing();
     }
 
-    private static int getActionId(final Keyboard keyboard) {
-        return keyboard != null ? keyboard.mId.imeActionId() : EditorInfo.IME_ACTION_NONE;
-    }
-
     private void performEditorAction(final int actionId) {
         mConnection.performEditorAction(actionId);
     }
@@ -1385,10 +1383,9 @@
             }
             break;
         case Constants.CODE_ACTION_ENTER:
-            final int actionId = getActionId(switcher.getKeyboard());
-            if (EditorInfo.IME_ACTION_NONE != actionId
-                && EditorInfo.IME_ACTION_UNSPECIFIED != actionId) {
-                performEditorAction(actionId);
+            if (EditorInfo.IME_ACTION_NONE != mActionId
+                && EditorInfo.IME_ACTION_UNSPECIFIED != mActionId) {
+                performEditorAction(mActionId);
                 break;
             }
             didAutoCorrect = handleNonSpecialCharacter(Constants.CODE_ENTER, x, y, spaceState);
@@ -1497,12 +1494,7 @@
             mSpaceState = SPACE_STATE_PHANTOM;
         } else {
             final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
-            // TODO: reverse this logic. We should have the means to determine whether a character
-            // should usually be followed by a space, and it should be more readable.
-            if (Constants.NOT_A_CODE != codePointBeforeCursor
-                    && !Character.isWhitespace(codePointBeforeCursor)
-                    && !mSettings.getCurrent().isPhantomSpacePromotingSymbol(codePointBeforeCursor)
-                    && !mSettings.getCurrent().isWeakSpaceStripper(codePointBeforeCursor)) {
+            if (mSettings.getCurrent().isUsuallyFollowedBySpace(codePointBeforeCursor)) {
                 mSpaceState = SPACE_STATE_PHANTOM;
             }
         }
@@ -1779,25 +1771,22 @@
         }
     }
 
+    /*
+     * Strip a trailing space if necessary and returns whether it's a swap weak space situation.
+     */
     private boolean maybeStripSpace(final int code,
             final int spaceState, final boolean isFromSuggestionStrip) {
         if (Constants.CODE_ENTER == code && SPACE_STATE_SWAP_PUNCTUATION == spaceState) {
             mConnection.removeTrailingSpace();
             return false;
-        } else if ((SPACE_STATE_WEAK == spaceState
-                || SPACE_STATE_SWAP_PUNCTUATION == spaceState)
-                && isFromSuggestionStrip) {
-            if (mSettings.getCurrent().isWeakSpaceSwapper(code)) {
-                return true;
-            } else {
-                if (mSettings.getCurrent().isWeakSpaceStripper(code)) {
-                    mConnection.removeTrailingSpace();
-                }
-                return false;
-            }
-        } else {
-            return false;
         }
+        if ((SPACE_STATE_WEAK == spaceState || SPACE_STATE_SWAP_PUNCTUATION == spaceState)
+                && isFromSuggestionStrip) {
+            if (mSettings.getCurrent().isUsuallyPrecededBySpace(code)) return false;
+            if (mSettings.getCurrent().isUsuallyFollowedBySpace(code)) return true;
+            mConnection.removeTrailingSpace();
+        }
+        return false;
     }
 
     private void handleCharacter(final int primaryCode, final int x,
@@ -1805,7 +1794,7 @@
         boolean isComposingWord = mWordComposer.isComposingWord();
 
         if (SPACE_STATE_PHANTOM == spaceState &&
-                !mSettings.getCurrent().isSymbolExcludedFromWordSeparators(primaryCode)) {
+                !mSettings.getCurrent().isWordConnector(primaryCode)) {
             if (isComposingWord) {
                 // Sanity check
                 throw new RuntimeException("Should not be composing here");
@@ -1817,7 +1806,7 @@
         // dozen milliseconds. Avoid calling it as much as possible, since we are on the UI
         // thread here.
         if (!isComposingWord && (isAlphabet(primaryCode)
-                || mSettings.getCurrent().isSymbolExcludedFromWordSeparators(primaryCode))
+                || mSettings.getCurrent().isWordConnector(primaryCode))
                 && mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation) &&
                 !mConnection.isCursorTouchingWord(mSettings.getCurrent())) {
             // Reset entirely the composing state anyway, then start composing a new word unless
@@ -1889,7 +1878,7 @@
                 Constants.SUGGESTION_STRIP_COORDINATE == x);
 
         if (SPACE_STATE_PHANTOM == spaceState &&
-                mSettings.getCurrent().isPhantomSpacePromotingSymbol(primaryCode)) {
+                mSettings.getCurrent().isUsuallyPrecededBySpace(primaryCode)) {
             promotePhantomSpace();
         }
         sendKeyCodePoint(primaryCode);
@@ -1904,16 +1893,13 @@
             }
 
             mHandler.startDoubleSpacePeriodTimer();
-            if (!mConnection.isCursorTouchingWord(mSettings.getCurrent())) {
-                mHandler.postUpdateSuggestionStrip();
-            }
+            mHandler.postUpdateSuggestionStrip();
         } else {
             if (swapWeakSpace) {
                 swapSwapperAndSpace();
                 mSpaceState = SPACE_STATE_SWAP_PUNCTUATION;
             } else if (SPACE_STATE_PHANTOM == spaceState
-                    && !mSettings.getCurrent().isWeakSpaceStripper(primaryCode)
-                    && !mSettings.getCurrent().isPhantomSpacePromotingSymbol(primaryCode)) {
+                    && mSettings.getCurrent().isUsuallyFollowedBySpace(primaryCode)) {
                 // If we are in phantom space state, and the user presses a separator, we want to
                 // stay in phantom space state so that the next keypress has a chance to add the
                 // space. For example, if I type "Good dat", pick "day" from the suggestion strip
@@ -2162,9 +2148,9 @@
                 // In the batch input mode, a manually picked suggested word should just replace
                 // the current batch input text and there is no need for a phantom space.
                 && !mWordComposer.isBatchMode()) {
-            int firstChar = Character.codePointAt(suggestion, 0);
-            if ((!mSettings.getCurrent().isWeakSpaceStripper(firstChar))
-                    && (!mSettings.getCurrent().isWeakSpaceSwapper(firstChar))) {
+            final int firstChar = Character.codePointAt(suggestion, 0);
+            if (!mSettings.getCurrent().isWordSeparator(firstChar)
+                    || mSettings.getCurrent().isUsuallyPrecededBySpace(firstChar)) {
                 promotePhantomSpace();
             }
         }
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index f7268fc..0e75533 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -577,11 +577,11 @@
         final CharSequence before = getTextBeforeCursor(1, 0);
         final CharSequence after = getTextAfterCursor(1, 0);
         if (!TextUtils.isEmpty(before) && !settingsValues.isWordSeparator(before.charAt(0))
-                && !settingsValues.isSymbolExcludedFromWordSeparators(before.charAt(0))) {
+                && !settingsValues.isWordConnector(before.charAt(0))) {
             return true;
         }
         if (!TextUtils.isEmpty(after) && !settingsValues.isWordSeparator(after.charAt(0))
-                && !settingsValues.isSymbolExcludedFromWordSeparators(after.charAt(0))) {
+                && !settingsValues.isWordConnector(after.charAt(0))) {
             return true;
         }
         return false;
@@ -633,12 +633,9 @@
         final char firstChar = word.charAt(0); // we just tested that word is not empty
         if (word.length() == 1 && !Character.isLetter(firstChar)) return null;
 
-        // We only suggest on words that start with a letter or a symbol that is excluded from
-        // word separators (see #handleCharacterWhileInBatchEdit).
-        if (!(Character.isLetter(firstChar)
-                || settings.isSymbolExcludedFromWordSeparators(firstChar))) {
-            return null;
-        }
+        // We don't restart suggestion if the first character is not a letter, because we don't
+        // start composing when the first character is not a letter.
+        if (!Character.isLetter(firstChar)) return null;
 
         return word;
     }
diff --git a/java/src/com/android/inputmethod/latin/SeekBarDialog.java b/java/src/com/android/inputmethod/latin/SeekBarDialog.java
index e576c09..c736d1b 100644
--- a/java/src/com/android/inputmethod/latin/SeekBarDialog.java
+++ b/java/src/com/android/inputmethod/latin/SeekBarDialog.java
@@ -30,6 +30,8 @@
     public interface Listener {
         public void onPositiveButtonClick(final SeekBarDialog dialog);
         public void onNegativeButtonClick(final SeekBarDialog dialog);
+        public void onNeutralButtonClick(final SeekBarDialog dialog);
+        public void onDismiss(final SeekBarDialog dialog);
         public void onProgressChanged(final SeekBarDialog dialog);
         public void onStartTrackingTouch(final SeekBarDialog dialog);
         public void onStopTrackingTouch(final SeekBarDialog dialog);
@@ -39,7 +41,11 @@
         @Override
         public void onPositiveButtonClick(final SeekBarDialog dialog) {}
         @Override
-        public void onNegativeButtonClick(final SeekBarDialog dialog) { dialog.dismiss(); }
+        public void onNegativeButtonClick(final SeekBarDialog dialog) {}
+        @Override
+        public void onNeutralButtonClick(final SeekBarDialog dialog) {}
+        @Override
+        public void onDismiss(final SeekBarDialog dialog) {}
         @Override
         public void onProgressChanged(final SeekBarDialog dialog) {}
         @Override
@@ -63,6 +69,9 @@
         dialogBuilder.setView(builder.mView);
         dialogBuilder.setPositiveButton(android.R.string.ok, this);
         dialogBuilder.setNegativeButton(android.R.string.cancel, this);
+        if (builder.mNeutralButtonTextResId != 0) {
+            dialogBuilder.setNeutralButton(builder.mNeutralButtonTextResId, this);
+        }
         mDialog = dialogBuilder.create();
         mListener = (builder.mListener == null) ? EMPTY_ADAPTER : builder.mListener;
         mValueView = (TextView)builder.mView.findViewById(R.id.seek_bar_dialog_value);
@@ -101,15 +110,21 @@
     }
 
     @Override
-    public void onClick(final DialogInterface dialog, int which) {
-        if (which == DialogInterface.BUTTON_POSITIVE) {
+    public void onClick(final DialogInterface dialog, final int which) {
+        switch (which) {
+        case DialogInterface.BUTTON_POSITIVE:
             mListener.onPositiveButtonClick(this);
-            return;
-        }
-        if (which == DialogInterface.BUTTON_NEGATIVE) {
+            break;
+        case DialogInterface.BUTTON_NEGATIVE:
             mListener.onNegativeButtonClick(this);
+            break;
+        case DialogInterface.BUTTON_NEUTRAL:
+            mListener.onNeutralButtonClick(this);
+            break;
+        default:
             return;
         }
+        mListener.onDismiss(this);
     }
 
     @Override
@@ -135,6 +150,7 @@
         final AlertDialog.Builder mDialogBuilder;
         final View mView;
 
+        int mNeutralButtonTextResId;
         int mMaxValue;
         int mValueFormatResId;
         int mValue;
@@ -150,8 +166,14 @@
             return this;
         }
 
+        public Builder setNeutralButtonText(final int resId) {
+            mNeutralButtonTextResId = resId;
+            return this;
+        }
+
         public Builder setMaxValue(final int max) {
             mMaxValue = max;
+            mValue = Math.min(mValue, max);
             return this;
         }
 
@@ -161,7 +183,7 @@
         }
 
         public Builder setValue(final int value) {
-            mValue = value;
+            mValue = Math.min(value, mMaxValue);
             return this;
         }
 
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index c5930a9..866bef0 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -167,19 +167,21 @@
     public static float readKeypressSoundVolume(final SharedPreferences prefs,
             final Resources res) {
         final float volume = prefs.getFloat(PREF_KEYPRESS_SOUND_VOLUME, -1.0f);
-        if (volume >= 0) {
-            return volume;
-        }
+        return (volume >= 0) ? volume : readDefaultKeypressSoundVolume(res);
+    }
+
+    public static float readDefaultKeypressSoundVolume(final Resources res) {
         return Float.parseFloat(
                 ResourceUtils.getDeviceOverrideValue(res, R.array.keypress_volumes));
     }
 
-    public static int readVibrationDuration(final SharedPreferences prefs,
+    public static int readKeypressVibrationDuration(final SharedPreferences prefs,
             final Resources res) {
         final int ms = prefs.getInt(PREF_VIBRATION_DURATION_SETTINGS, -1);
-        if (ms >= 0) {
-            return ms;
-        }
+        return (ms >= 0) ? ms : readDefaultKeypressVibrationDuration(res);
+    }
+
+    public static int readDefaultKeypressVibrationDuration(final Resources res) {
         return Integer.parseInt(
                 ResourceUtils.getDeviceOverrideValue(res, R.array.keypress_vibration_durations));
     }
diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java
index 507a37b..6a43718 100644
--- a/java/src/com/android/inputmethod/latin/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java
@@ -180,7 +180,7 @@
                     });
             mKeypressVibrationDurationSettingsPref.setSummary(
                     res.getString(R.string.settings_keypress_vibration_duration,
-                            Settings.readVibrationDuration(prefs, res)));
+                            Settings.readKeypressVibrationDuration(prefs, res)));
         }
 
         mKeypressSoundVolumeSettingsPref =
@@ -308,10 +308,29 @@
         final Context context = getActivity();
         final PreferenceScreen settingsPref = mKeypressVibrationDurationSettingsPref;
         final SeekBarDialog.Listener listener = new SeekBarDialog.Adapter() {
+            private void writePreference(final SharedPreferences sp, final int value) {
+                sp.edit().putInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, value).apply();
+            }
+
+            private void feedbackSettingsValue(final int value) {
+                AudioAndHapticFeedbackManager.getInstance().vibrate(value);
+            }
+
             @Override
             public void onPositiveButtonClick(final SeekBarDialog dialog) {
-                final int ms = dialog.getValue();
-                sp.edit().putInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, ms).apply();
+                writePreference(sp, dialog.getValue());
+            }
+
+            @Override
+            public void onNeutralButtonClick(final SeekBarDialog dialog) {
+                final int defaultValue =
+                        Settings.readDefaultKeypressVibrationDuration(context.getResources());
+                dialog.setValue(defaultValue, false /* fromUser */);
+                writePreference(sp, defaultValue);
+            }
+
+            @Override
+            public void onDismiss(final SeekBarDialog dialog) {
                 if (settingsPref != null) {
                     settingsPref.setSummary(dialog.getValueText());
                 }
@@ -319,13 +338,13 @@
 
             @Override
             public void onStopTrackingTouch(final SeekBarDialog dialog) {
-                final int ms = dialog.getValue();
-                AudioAndHapticFeedbackManager.getInstance().vibrate(ms);
+                feedbackSettingsValue(dialog.getValue());
             }
         };
-        final int currentMs = Settings.readVibrationDuration(sp, getResources());
+        final int currentMs = Settings.readKeypressVibrationDuration(sp, getResources());
         final SeekBarDialog.Builder builder = new SeekBarDialog.Builder(context);
         builder.setTitle(R.string.prefs_keypress_vibration_duration_settings)
+                .setNeutralButtonText(R.string.button_default)
                 .setListener(listener)
                 .setMaxValue(AudioAndHapticFeedbackManager.MAX_KEYPRESS_VIBRATION_DURATION)
                 .setValueFromat(R.string.settings_keypress_vibration_duration)
@@ -348,10 +367,29 @@
         final AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
         final PreferenceScreen settingsPref = mKeypressSoundVolumeSettingsPref;
         final SeekBarDialog.Listener listener = new SeekBarDialog.Adapter() {
+            private void writePreference(final SharedPreferences sp, final float value) {
+                sp.edit().putFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, value).apply();
+            }
+
+            private void feedbackSettingsValue(final float value) {
+                am.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, value);
+            }
+
             @Override
             public void onPositiveButtonClick(final SeekBarDialog dialog) {
-                final float volume = dialog.getValue() / PERCENT_FLOAT;
-                sp.edit().putFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, volume).apply();
+                writePreference(sp, dialog.getValue() / PERCENT_FLOAT);
+            }
+
+            @Override
+            public void onNeutralButtonClick(final SeekBarDialog dialog) {
+                final float defaultValue =
+                        Settings.readDefaultKeypressSoundVolume(context.getResources());
+                dialog.setValue((int)(defaultValue * PERCENT_INT), false /* fromUser */);
+                writePreference(sp, defaultValue);
+            }
+
+            @Override
+            public void onDismiss(final SeekBarDialog dialog) {
                 if (settingsPref != null) {
                     settingsPref.setSummary(dialog.getValueText());
                 }
@@ -359,13 +397,13 @@
 
             @Override
             public void onStopTrackingTouch(final SeekBarDialog dialog) {
-                final float volume = dialog.getValue() / PERCENT_FLOAT;
-                am.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, volume);
+                feedbackSettingsValue(dialog.getValue() / PERCENT_FLOAT);
             }
         };
         final SeekBarDialog.Builder builder = new SeekBarDialog.Builder(context);
         final int currentVolumeInt = getCurrentKeyPressSoundVolumePercent(sp, getResources());
         builder.setTitle(R.string.prefs_keypress_sound_volume_settings)
+                .setNeutralButtonText(R.string.button_default)
                 .setListener(listener)
                 .setMaxValue(PERCENT_INT)
                 .setValue(currentVolumeInt)
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index 9a20246..1e3bdf0 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -37,11 +37,10 @@
 
     // From resources:
     public final int mDelayUpdateOldSuggestions;
-    public final String mWeakSpaceStrippers;
-    public final String mWeakSpaceSwappers;
-    private final String mPhantomSpacePromotingSymbols;
+    public final int[] mSymbolsPrecededBySpace;
+    public final int[] mSymbolsFollowedBySpace;
+    public final int[] mWordConnectors;
     public final SuggestedWords mSuggestPuncList;
-    private final String mSymbolsExcludedFromWordSeparators;
     public final String mWordSeparators;
     public final CharSequence mHintToSaveText;
 
@@ -79,25 +78,19 @@
             final InputAttributes inputAttributes) {
         // Get the resources
         mDelayUpdateOldSuggestions = res.getInteger(R.integer.config_delay_update_old_suggestions);
-        mWeakSpaceStrippers = res.getString(R.string.weak_space_stripping_symbols);
-        mWeakSpaceSwappers = res.getString(R.string.weak_space_swapping_symbols);
-        mPhantomSpacePromotingSymbols = res.getString(R.string.phantom_space_promoting_symbols);
-        if (LatinImeLogger.sDBG) {
-            final int length = mWeakSpaceStrippers.length();
-            for (int i = 0; i < length; i = mWeakSpaceStrippers.offsetByCodePoints(i, 1)) {
-                if (isWeakSpaceSwapper(mWeakSpaceStrippers.codePointAt(i))) {
-                    throw new RuntimeException("Char code " + mWeakSpaceStrippers.codePointAt(i)
-                            + " is both a weak space swapper and stripper.");
-                }
-            }
-        }
+        mSymbolsPrecededBySpace =
+                StringUtils.toCodePointArray(res.getString(R.string.symbols_preceded_by_space));
+        Arrays.sort(mSymbolsPrecededBySpace);
+        mSymbolsFollowedBySpace =
+                StringUtils.toCodePointArray(res.getString(R.string.symbols_followed_by_space));
+        Arrays.sort(mSymbolsFollowedBySpace);
+        mWordConnectors =
+                StringUtils.toCodePointArray(res.getString(R.string.symbols_word_connectors));
+        Arrays.sort(mWordConnectors);
         final String[] suggestPuncsSpec = KeySpecParser.parseCsvString(
                 res.getString(R.string.suggested_punctuations), null);
         mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec);
-        mSymbolsExcludedFromWordSeparators =
-                res.getString(R.string.symbols_excluded_from_word_separators);
-        mWordSeparators = createWordSeparators(mWeakSpaceStrippers, mWeakSpaceSwappers,
-                mSymbolsExcludedFromWordSeparators, res);
+        mWordSeparators = res.getString(R.string.symbols_word_separators);
         mHintToSaveText = res.getText(R.string.hint_add_to_dictionary);
 
         // Store the input attributes
@@ -128,7 +121,7 @@
         mBigramPredictionEnabled = readBigramPredictionEnabled(prefs, res);
 
         // Compute other readable settings
-        mKeypressVibrationDuration = Settings.readVibrationDuration(prefs, res);
+        mKeypressVibrationDuration = Settings.readKeypressVibrationDuration(prefs, res);
         mKeypressSoundVolume = Settings.readKeypressSoundVolume(prefs, res);
         mKeyPreviewPopupDismissDelay = Settings.readKeyPreviewPopupDismissDelay(prefs, res);
         mAutoCorrectionThreshold = readAutoCorrectionThreshold(res,
@@ -169,25 +162,16 @@
         return mWordSeparators.contains(String.valueOf((char)code));
     }
 
-    public boolean isSymbolExcludedFromWordSeparators(final int code) {
-        return mSymbolsExcludedFromWordSeparators.contains(String.valueOf((char)code));
+    public boolean isWordConnector(final int code) {
+        return Arrays.binarySearch(mWordConnectors, code) >= 0;
     }
 
-    // TODO: use "Phantom" instead of "Weak" in this method name
-    public boolean isWeakSpaceStripper(final int code) {
-        // TODO: this does not work if the code does not fit in a char
-        return mWeakSpaceStrippers.contains(String.valueOf((char)code));
+    public boolean isUsuallyPrecededBySpace(final int code) {
+        return Arrays.binarySearch(mSymbolsPrecededBySpace, code) >= 0;
     }
 
-    // TODO: use "Phantom" instead of "Weak" in this method name
-    public boolean isWeakSpaceSwapper(final int code) {
-        // TODO: this does not work if the code does not fit in a char
-        return mWeakSpaceSwappers.contains(String.valueOf((char)code));
-    }
-
-    public boolean isPhantomSpacePromotingSymbol(final int code) {
-        // TODO: this does not work if the code does not fit in a char
-        return mPhantomSpacePromotingSymbols.contains(String.valueOf((char)code));
+    public boolean isUsuallyFollowedBySpace(final int code) {
+        return Arrays.binarySearch(mSymbolsFollowedBySpace, code) >= 0;
     }
 
     public boolean shouldInsertSpacesAutomatically() {
@@ -239,18 +223,6 @@
                 false /* isPrediction */);
     }
 
-    private static String createWordSeparators(final String weakSpaceStrippers,
-            final String weakSpaceSwappers, final String symbolsExcludedFromWordSeparators,
-            final Resources res) {
-        String wordSeparators = weakSpaceStrippers + weakSpaceSwappers
-                + res.getString(R.string.phantom_space_promoting_symbols);
-        for (int i = symbolsExcludedFromWordSeparators.length() - 1; i >= 0; --i) {
-            wordSeparators = wordSeparators.replace(
-                    symbolsExcludedFromWordSeparators.substring(i, i + 1), "");
-        }
-        return wordSeparators;
-    }
-
     private static final int SUGGESTION_VISIBILITY_SHOW_VALUE =
             R.string.prefs_suggestion_visibility_show_value;
     private static final int SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE =