diff --git a/java/res/layout/sound_effect_volume_dialog.xml b/java/res/layout/seek_bar_dialog.xml
similarity index 88%
rename from java/res/layout/sound_effect_volume_dialog.xml
rename to java/res/layout/seek_bar_dialog.xml
index 294663006c7c7f42e2a0ab75622e8c8d20d2ad50..a47e9a0389a2ad2e17d385e87acfae5f85ae7bd0 100644
--- a/java/res/layout/sound_effect_volume_dialog.xml
+++ b/java/res/layout/seek_bar_dialog.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2011, The Android Open Source Project
+** Copyright 2012, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
@@ -30,15 +30,14 @@
         android:layout_height="wrap_content"
         android:gravity="center_horizontal"
         android:layout_margin="10dp">
-        <TextView android:id="@+id/sound_effect_volume_value"
+        <TextView android:id="@+id/seek_bar_dialog_value"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:textSize="20dp"/>
     </LinearLayout>
     <SeekBar
-        android:id="@+id/sound_effect_volume_bar"
+        android:id="@+id/seek_bar_dialog_bar"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:max="100"
         android:layout_margin="10dp"/>
 </LinearLayout>
diff --git a/java/res/layout/vibration_settings_dialog.xml b/java/res/layout/vibration_settings_dialog.xml
deleted file mode 100644
index c9fb6ec4e11b894e99cdf4e4a345fd974472c207..0000000000000000000000000000000000000000
--- a/java/res/layout/vibration_settings_dialog.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<LinearLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:orientation="vertical"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_margin="10dp">
-    <LinearLayout
-        android:orientation="horizontal"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="center_horizontal"
-        android:layout_margin="10dp">
-        <TextView android:id="@+id/vibration_value"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textSize="20dp"/>
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/settings_ms"
-            android:textSize="20dp"/>
-    </LinearLayout>
-    <SeekBar
-        android:id="@+id/vibration_settings"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:max="250"
-        android:layout_margin="10dp"/>
-</LinearLayout>
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index a6ea8a061e1835b71b149733e00ad610fbca4318..193a0191ecad0f597c41a6d31999469e8b3a83c1 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -233,6 +233,5 @@
     <!-- dictionary pack package name /settings activity (for shared prefs and settings) -->
     <string name="dictionary_pack_package_name">com.google.android.inputmethod.latin.dictionarypack</string>
     <string name="dictionary_pack_settings_activity">com.google.android.inputmethod.latin.dictionarypack.DictionarySettingsActivity</string>
-    <string name="settings_ms">ms</string>
     <string name="settings_warning_researcher_mode">Attention!  You are using the special keyboard for research purposes.</string>
 </resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 2affdebedb348f6e1b07da7bc37e546bb0804e53..5c5442708dda8845d74c3333fac02bc952b71f02 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -76,6 +76,9 @@
     <!-- Description for delay for dismissing a popup on screen: default value of the delay [CHAR LIMIT=15] -->
     <string name="key_preview_popup_dismiss_default_delay">Default</string>
 
+    <!-- Units abbreviation for the keypress vibration duration (milliseconds) [CHAR LIMIT=10] -->
+    <string name="settings_keypress_vibration_duration"><xliff:g id="milliseconds">%s</xliff:g>ms</string>
+
     <!-- Option name for enabling or disabling the use of names of people in Contacts for suggestion and correction [CHAR LIMIT=25] -->
     <string name="use_contacts_dict">Suggest Contact names</string>
     <!-- Description for option enabling or disabling the use of names of people in Contacts for suggestion and correction [CHAR LIMIT=65] -->
diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
index 0247263913dc6e18299ab676f78f8ab9c86a55af..0e7f891fffdab8087da4fa271a475b5f1ae37a37 100644
--- a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
+++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
@@ -30,6 +30,8 @@ import com.android.inputmethod.latin.VibratorUtils;
  * complexity of settings and the like.
  */
 public final class AudioAndHapticFeedbackManager {
+    public static final int MAX_KEYPRESS_VIBRATION_DURATION = 250; // millisecond
+
     private final AudioManager mAudioManager;
     private final VibratorUtils mVibratorUtils;
 
diff --git a/java/src/com/android/inputmethod/latin/SeekBarDialog.java b/java/src/com/android/inputmethod/latin/SeekBarDialog.java
new file mode 100644
index 0000000000000000000000000000000000000000..e576c09848caea54476037f4a770bcc00879c0ff
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/SeekBarDialog.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+
+public final class SeekBarDialog implements DialogInterface.OnClickListener,
+        OnSeekBarChangeListener {
+    public interface Listener {
+        public void onPositiveButtonClick(final SeekBarDialog dialog);
+        public void onNegativeButtonClick(final SeekBarDialog dialog);
+        public void onProgressChanged(final SeekBarDialog dialog);
+        public void onStartTrackingTouch(final SeekBarDialog dialog);
+        public void onStopTrackingTouch(final SeekBarDialog dialog);
+    }
+
+    public static class Adapter implements Listener {
+        @Override
+        public void onPositiveButtonClick(final SeekBarDialog dialog) {}
+        @Override
+        public void onNegativeButtonClick(final SeekBarDialog dialog) { dialog.dismiss(); }
+        @Override
+        public void onProgressChanged(final SeekBarDialog dialog) {}
+        @Override
+        public void onStartTrackingTouch(final SeekBarDialog dialog) {}
+        @Override
+        public void onStopTrackingTouch(final SeekBarDialog dialog) {}
+    }
+
+    private static final Listener EMPTY_ADAPTER = new Adapter();
+
+    private final AlertDialog mDialog;
+    private final Listener mListener;
+    private final TextView mValueView;
+    private final SeekBar mSeekBar;
+    private final String mValueFormat;
+
+    private int mValue;
+
+    private SeekBarDialog(final Builder builder) {
+        final AlertDialog.Builder dialogBuilder = builder.mDialogBuilder;
+        dialogBuilder.setView(builder.mView);
+        dialogBuilder.setPositiveButton(android.R.string.ok, this);
+        dialogBuilder.setNegativeButton(android.R.string.cancel, this);
+        mDialog = dialogBuilder.create();
+        mListener = (builder.mListener == null) ? EMPTY_ADAPTER : builder.mListener;
+        mValueView = (TextView)builder.mView.findViewById(R.id.seek_bar_dialog_value);
+        mSeekBar = (SeekBar)builder.mView.findViewById(R.id.seek_bar_dialog_bar);
+        mSeekBar.setMax(builder.mMaxValue);
+        mSeekBar.setOnSeekBarChangeListener(this);
+        if (builder.mValueFormatResId == 0) {
+            mValueFormat = "%s";
+        } else {
+            mValueFormat = mDialog.getContext().getString(builder.mValueFormatResId);
+        }
+    }
+
+    public void setValue(final int value, final boolean fromUser) {
+        mValue = value;
+        mValueView.setText(String.format(mValueFormat, value));
+        if (!fromUser) {
+            mSeekBar.setProgress(value);
+        }
+    }
+
+    public int getValue() {
+        return mValue;
+    }
+
+    public CharSequence getValueText() {
+        return mValueView.getText();
+    }
+
+    public void show() {
+        mDialog.show();
+    }
+
+    public void dismiss() {
+        mDialog.dismiss();
+    }
+
+    @Override
+    public void onClick(final DialogInterface dialog, int which) {
+        if (which == DialogInterface.BUTTON_POSITIVE) {
+            mListener.onPositiveButtonClick(this);
+            return;
+        }
+        if (which == DialogInterface.BUTTON_NEGATIVE) {
+            mListener.onNegativeButtonClick(this);
+            return;
+        }
+    }
+
+    @Override
+    public void onProgressChanged(final SeekBar seekBar, final int progress,
+            final boolean fromUser) {
+        setValue(progress, fromUser);
+        if (fromUser) {
+            mListener.onProgressChanged(this);
+        }
+    }
+
+    @Override
+    public void onStartTrackingTouch(final SeekBar seekBar) {
+        mListener.onStartTrackingTouch(this);
+    }
+
+    @Override
+    public void onStopTrackingTouch(final SeekBar seekBar) {
+        mListener.onStopTrackingTouch(this);
+    }
+
+    public static final class Builder {
+        final AlertDialog.Builder mDialogBuilder;
+        final View mView;
+
+        int mMaxValue;
+        int mValueFormatResId;
+        int mValue;
+        Listener mListener;
+
+        public Builder(final Context context) {
+            mDialogBuilder = new AlertDialog.Builder(context);
+            mView = LayoutInflater.from(context).inflate(R.layout.seek_bar_dialog, null);
+        }
+
+        public Builder setTitle(final int resId) {
+            mDialogBuilder.setTitle(resId);
+            return this;
+        }
+
+        public Builder setMaxValue(final int max) {
+            mMaxValue = max;
+            return this;
+        }
+
+        public Builder setValueFromat(final int resId) {
+            mValueFormatResId = resId;
+            return this;
+        }
+
+        public Builder setValue(final int value) {
+            mValue = value;
+            return this;
+        }
+
+        public Builder setListener(final Listener listener) {
+            mListener = listener;
+            return this;
+        }
+
+        public SeekBarDialog create() {
+            final SeekBarDialog dialog = new SeekBarDialog(this);
+            dialog.setValue(mValue, false /* fromUser */);
+            return dialog;
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 7a73cade316e4bd8091bac89f25a0b8c095d82d1..222adcb2e17a799529df6b6cdc8b873f9d680730 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -16,10 +16,8 @@
 
 package com.android.inputmethod.latin;
 
-import android.app.AlertDialog;
 import android.app.backup.BackupManager;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.Resources;
@@ -31,12 +29,7 @@ import android.preference.Preference;
 import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceGroup;
 import android.preference.PreferenceScreen;
-import android.view.LayoutInflater;
-import android.view.View;
 import android.view.inputmethod.InputMethodSubtype;
-import android.widget.SeekBar;
-import android.widget.SeekBar.OnSeekBarChangeListener;
-import android.widget.TextView;
 
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethodcommon.InputMethodSettingsFragment;
@@ -94,9 +87,6 @@ public final class Settings extends InputMethodSettingsFragment
     private CheckBoxPreference mBigramPrediction;
     private Preference mDebugSettingsPreference;
 
-    private TextView mKeypressVibrationDurationSettingsTextView;
-    private TextView mKeypressSoundVolumeSettingsTextView;
-
     private static void setPreferenceEnabled(final Preference preference, final boolean enabled) {
         if (preference != null) {
             preference.setEnabled(enabled);
@@ -127,7 +117,7 @@ public final class Settings extends InputMethodSettingsFragment
         mVoicePreference = (ListPreference) findPreference(PREF_VOICE_MODE);
         mShowCorrectionSuggestionsPreference =
                 (ListPreference) findPreference(PREF_SHOW_SUGGESTIONS_SETTING);
-        SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
+        final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
         prefs.registerOnSharedPreferenceChangeListener(this);
 
         mAutoCorrectionThresholdPreference =
@@ -227,7 +217,9 @@ public final class Settings extends InputMethodSettingsFragment
                             return true;
                         }
                     });
-            updateKeypressVibrationDurationSettingsSummary(prefs, res);
+            mKeypressVibrationDurationSettingsPref.setSummary(
+                    res.getString(R.string.settings_keypress_vibration_duration,
+                            SettingsValues.getCurrentVibrationDuration(prefs, res)));
         }
 
         mKeypressSoundVolumeSettingsPref =
@@ -241,7 +233,8 @@ public final class Settings extends InputMethodSettingsFragment
                             return true;
                         }
                     });
-            updateKeypressSoundVolumeSummary(prefs, res);
+            mKeypressSoundVolumeSettingsPref.setSummary(String.valueOf(
+                    getCurrentKeyPressSoundVolumePercent(prefs, res)));
         }
         refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, res);
     }
@@ -349,122 +342,73 @@ public final class Settings extends InputMethodSettingsFragment
         }
     }
 
-    private void updateKeypressVibrationDurationSettingsSummary(
-            final SharedPreferences sp, final Resources res) {
-        if (mKeypressVibrationDurationSettingsPref != null) {
-            mKeypressVibrationDurationSettingsPref.setSummary(
-                    SettingsValues.getCurrentVibrationDuration(sp, res)
-                            + res.getString(R.string.settings_ms));
-        }
-    }
-
     private void showKeypressVibrationDurationSettingsDialog() {
         final SharedPreferences sp = getPreferenceManager().getSharedPreferences();
         final Context context = getActivity();
-        final Resources res = context.getResources();
-        final AlertDialog.Builder builder = new AlertDialog.Builder(context);
-        builder.setTitle(R.string.prefs_keypress_vibration_duration_settings);
-        builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+        final PreferenceScreen settingsPref = mKeypressVibrationDurationSettingsPref;
+        final SeekBarDialog.Listener listener = new SeekBarDialog.Adapter() {
             @Override
-            public void onClick(DialogInterface dialog, int whichButton) {
-                final int ms = Integer.valueOf(
-                        mKeypressVibrationDurationSettingsTextView.getText().toString());
+            public void onPositiveButtonClick(final SeekBarDialog dialog) {
+                final int ms = dialog.getValue();
                 sp.edit().putInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, ms).apply();
-                updateKeypressVibrationDurationSettingsSummary(sp, res);
-            }
-        });
-        builder.setNegativeButton(android.R.string.cancel,  new DialogInterface.OnClickListener() {
-            @Override
-            public void onClick(DialogInterface dialog, int whichButton) {
-                dialog.dismiss();
-            }
-        });
-        final View v = LayoutInflater.from(context).inflate(
-                R.layout.vibration_settings_dialog, null);
-        final int currentMs = SettingsValues.getCurrentVibrationDuration(
-                getPreferenceManager().getSharedPreferences(), getResources());
-        mKeypressVibrationDurationSettingsTextView = (TextView)v.findViewById(R.id.vibration_value);
-        final SeekBar sb = (SeekBar)v.findViewById(R.id.vibration_settings);
-        sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
-            @Override
-            public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2) {
-                final int tempMs = arg1;
-                mKeypressVibrationDurationSettingsTextView.setText(String.valueOf(tempMs));
-            }
-
-            @Override
-            public void onStartTrackingTouch(SeekBar arg0) {
+                if (settingsPref != null) {
+                    settingsPref.setSummary(dialog.getValueText());
+                }
             }
 
             @Override
-            public void onStopTrackingTouch(SeekBar arg0) {
-                final int tempMs = arg0.getProgress();
-                VibratorUtils.getInstance(context).vibrate(tempMs);
+            public void onStopTrackingTouch(final SeekBarDialog dialog) {
+                final int ms = dialog.getValue();
+                VibratorUtils.getInstance(context).vibrate(ms);
             }
-        });
-        sb.setProgress(currentMs);
-        mKeypressVibrationDurationSettingsTextView.setText(String.valueOf(currentMs));
-        builder.setView(v);
-        builder.create().show();
+        };
+        final int currentMs = SettingsValues.getCurrentVibrationDuration(sp, getResources());
+        final SeekBarDialog.Builder builder = new SeekBarDialog.Builder(context);
+        builder.setTitle(R.string.prefs_keypress_vibration_duration_settings)
+            .setListener(listener)
+            .setMaxValue(AudioAndHapticFeedbackManager.MAX_KEYPRESS_VIBRATION_DURATION)
+            .setValueFromat(R.string.settings_keypress_vibration_duration)
+            .setValue(currentMs)
+            .create()
+            .show();
     }
 
-    private void updateKeypressSoundVolumeSummary(final SharedPreferences sp, final Resources res) {
-        if (mKeypressSoundVolumeSettingsPref != null) {
-            mKeypressSoundVolumeSettingsPref.setSummary(String.valueOf(
-                    (int)(SettingsValues.getCurrentKeypressSoundVolume(sp, res) * 100)));
-        }
+    private static final int PERCENT_INT = 100;
+    private static final float PERCENT_FLOAT = 100.0f;
+
+    private static int getCurrentKeyPressSoundVolumePercent(final SharedPreferences sp,
+            final Resources res) {
+        return (int)(SettingsValues.getCurrentKeypressSoundVolume(sp, res) * PERCENT_FLOAT);
     }
 
     private void showKeypressSoundVolumeSettingDialog() {
+        final SharedPreferences sp = getPreferenceManager().getSharedPreferences();
         final Context context = getActivity();
         final AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-        final SharedPreferences sp = getPreferenceManager().getSharedPreferences();
-        final Resources res = context.getResources();
-        final AlertDialog.Builder builder = new AlertDialog.Builder(context);
-        builder.setTitle(R.string.prefs_keypress_sound_volume_settings);
-        builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+        final PreferenceScreen settingsPref = mKeypressSoundVolumeSettingsPref;
+        final SeekBarDialog.Listener listener = new SeekBarDialog.Adapter() {
             @Override
-            public void onClick(DialogInterface dialog, int whichButton) {
-                final float volume =
-                        ((float)Integer.valueOf(
-                                mKeypressSoundVolumeSettingsTextView.getText().toString())) / 100;
+            public void onPositiveButtonClick(final SeekBarDialog dialog) {
+                final float volume = dialog.getValue() / PERCENT_FLOAT;
                 sp.edit().putFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, volume).apply();
-                updateKeypressSoundVolumeSummary(sp, res);
-            }
-        });
-        builder.setNegativeButton(android.R.string.cancel,  new DialogInterface.OnClickListener() {
-            @Override
-            public void onClick(DialogInterface dialog, int whichButton) {
-                dialog.dismiss();
-            }
-        });
-        final View v = LayoutInflater.from(context).inflate(
-                R.layout.sound_effect_volume_dialog, null);
-        final int currentVolumeInt =
-                (int)(SettingsValues.getCurrentKeypressSoundVolume(sp, res) * 100);
-        mKeypressSoundVolumeSettingsTextView =
-                (TextView)v.findViewById(R.id.sound_effect_volume_value);
-        final SeekBar sb = (SeekBar)v.findViewById(R.id.sound_effect_volume_bar);
-        sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
-            @Override
-            public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2) {
-                final int tempVolume = arg1;
-                mKeypressSoundVolumeSettingsTextView.setText(String.valueOf(tempVolume));
-            }
-
-            @Override
-            public void onStartTrackingTouch(SeekBar arg0) {
+                if (settingsPref != null) {
+                    settingsPref.setSummary(dialog.getValueText());
+                }
             }
 
             @Override
-            public void onStopTrackingTouch(SeekBar arg0) {
-                final float tempVolume = ((float)arg0.getProgress()) / 100;
-                am.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, tempVolume);
+            public void onStopTrackingTouch(final SeekBarDialog dialog) {
+                final float volume = dialog.getValue() / PERCENT_FLOAT;
+                am.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, volume);
             }
-        });
-        sb.setProgress(currentVolumeInt);
-        mKeypressSoundVolumeSettingsTextView.setText(String.valueOf(currentVolumeInt));
-        builder.setView(v);
-        builder.create().show();
+        };
+        final SeekBarDialog.Builder builder = new SeekBarDialog.Builder(context);
+        final int currentVolumeInt = getCurrentKeyPressSoundVolumePercent(sp, getResources());
+        builder.setTitle(R.string.prefs_keypress_sound_volume_settings)
+            .setListener(listener)
+            .setMaxValue(PERCENT_INT)
+            .setValue(currentVolumeInt)
+            .create()
+            .show();
     }
 }