diff --git a/java/res/layout/sound_effect_volume_dialog.xml b/java/res/layout/sound_effect_volume_dialog.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c5b2f10e2fe60dc279590deb2895bf53df46be2c
--- /dev/null
+++ b/java/res/layout/sound_effect_volume_dialog.xml
@@ -0,0 +1,44 @@
+<?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="10dip">
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal"
+        android:layout_margin="10dip">
+        <TextView android:id="@+id/sound_effect_volume_value"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="20dip"/>
+    </LinearLayout>
+    <SeekBar
+        android:id="@+id/sound_effect_volume_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:max="100"
+        android:layout_margin="10dip"/>
+</LinearLayout>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 8bc97f66ed5f30ef3838d98709dc569651d84225..e00547a6263c14c40c7706ae59bdb665d55adff9 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -342,6 +342,8 @@
 
     <!-- Title of an option for usability study mode -->
     <string name="prefs_usability_study_mode">Usability study mode</string>
-    <!-- Title of the settings for vibration duration -->
-    <string name="prefs_vibration_duration_settings">Vibration duration settings</string>
+    <!-- Title of the settings for keypress vibration duration -->
+    <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>
 </resources>
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index b54df2686797b00d7887ddbd46d8dbd6e2e7d612..dcaa2029c933daebd37affe010f8944455c90058 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -121,7 +121,10 @@
                 android:defaultValue="true" />
             <PreferenceScreen
                 android:key="pref_vibration_duration_settings"
-                android:title="@string/prefs_vibration_duration_settings"/>
+                android:title="@string/prefs_keypress_vibration_duration_settings"/>
+            <PreferenceScreen
+                android:key="pref_keypress_sound_volume"
+                android:title="@string/prefs_keypress_sound_volume_settings" />
             <!-- TODO: evaluate results and revive this option. The code
                 already supports it. -->
             <!-- <CheckBoxPreference -->
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 958092bc702ec67a3a87adddcaf79c6d8c1d0743..28d920557a0592efe0405144c8cf7a182b883045 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -28,7 +28,6 @@ import android.content.res.Resources;
 import android.inputmethodservice.InputMethodService;
 import android.media.AudioManager;
 import android.net.ConnectivityManager;
-import android.os.Build;
 import android.os.Debug;
 import android.os.Message;
 import android.os.SystemClock;
@@ -2097,16 +2096,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
         }
     };
 
-    // update sound effect volume
+    // update keypress sound volume
     private void updateSoundEffectVolume() {
-        final String[] volumePerHardwareList = mResources.getStringArray(R.array.keypress_volumes);
-        final String hardwarePrefix = Build.HARDWARE + ",";
-        for (final String element : volumePerHardwareList) {
-            if (element.startsWith(hardwarePrefix)) {
-                mFxVolume = Float.parseFloat(element.substring(element.lastIndexOf(',') + 1));
-                break;
-            }
-        }
+        mFxVolume = Utils.getCurrentKeypressSoundVolume(mPrefs, mResources);
     }
 
     // update flags for silent mode
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index d9508f4c16da3d907064499de7f0080bcb4d36a9..eeb0299b170258d00ed0b44e80a901c13a7d2aef 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -26,6 +26,7 @@ import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.Resources;
+import android.media.AudioManager;
 import android.os.Bundle;
 import android.preference.CheckBoxPreference;
 import android.preference.ListPreference;
@@ -91,9 +92,11 @@ public class Settings extends InputMethodSettingsActivity
 
     public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
 
-    public static final String PREF_VIBRATION_DURATION_SETTINGS =
+    public static final String PREF_KEYPRESS_VIBRATION_DURATION_SETTINGS =
             "pref_vibration_duration_settings";
 
+    public static final String PREF_KEYPRESS_SOUND_VOLUME =
+            "pref_keypress_sound_volume";
     // Dialog ids
     private static final int VOICE_INPUT_CONFIRM_DIALOG = 0;
 
@@ -327,7 +330,8 @@ public class Settings extends InputMethodSettingsActivity
     }
 
     private PreferenceScreen mInputLanguageSelection;
-    private PreferenceScreen mVibrationDurationSettingsPref;
+    private PreferenceScreen mKeypressVibrationDurationSettingsPref;
+    private PreferenceScreen mKeypressSoundVolumeSettingsPref;
     private ListPreference mVoicePreference;
     private CheckBoxPreference mShowSettingsKeyPreference;
     private ListPreference mShowCorrectionSuggestionsPreference;
@@ -341,7 +345,8 @@ public class Settings extends InputMethodSettingsActivity
     private boolean mVoiceOn;
 
     private AlertDialog mDialog;
-    private TextView mVibrationSettingsTextView;
+    private TextView mKeypressVibrationDurationSettingsTextView;
+    private TextView mKeypressSoundVolumeSettingsTextView;
 
     private boolean mOkClicked = false;
     private String mVoiceModeOff;
@@ -477,19 +482,34 @@ public class Settings extends InputMethodSettingsActivity
             }
         }
 
-        mVibrationDurationSettingsPref =
-                (PreferenceScreen) findPreference(PREF_VIBRATION_DURATION_SETTINGS);
-        if (mVibrationDurationSettingsPref != null) {
-            mVibrationDurationSettingsPref.setOnPreferenceClickListener(
+        mKeypressVibrationDurationSettingsPref =
+                (PreferenceScreen) findPreference(PREF_KEYPRESS_VIBRATION_DURATION_SETTINGS);
+        if (mKeypressVibrationDurationSettingsPref != null) {
+            mKeypressVibrationDurationSettingsPref.setOnPreferenceClickListener(
                     new OnPreferenceClickListener() {
                         @Override
                         public boolean onPreferenceClick(Preference arg0) {
-                            showVibrationSettingsDialog();
+                            showKeypressVibrationDurationSettingsDialog();
                             return true;
                         }
                     });
-            updateVibrationDurationSettingsSummary(prefs, res);
+            updateKeypressVibrationDurationSettingsSummary(prefs, res);
         }
+
+        mKeypressSoundVolumeSettingsPref =
+                (PreferenceScreen) findPreference(PREF_KEYPRESS_SOUND_VOLUME);
+        if (mKeypressSoundVolumeSettingsPref != null) {
+            mKeypressSoundVolumeSettingsPref.setOnPreferenceClickListener(
+                    new OnPreferenceClickListener() {
+                        @Override
+                        public boolean onPreferenceClick(Preference arg0) {
+                            showKeypressSoundVolumeSettingDialog();
+                            return true;
+                        }
+                    });
+            updateKeypressSoundVolumeSummary(prefs, res);
+        }
+        refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, res);
     }
 
     @SuppressWarnings("unused")
@@ -537,6 +557,7 @@ public class Settings extends InputMethodSettingsActivity
         updateVoiceModeSummary();
         updateShowCorrectionSuggestionsSummary();
         updateKeyPreviewPopupDelaySummary();
+        refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, getResources());
     }
 
     @Override
@@ -637,26 +658,44 @@ public class Settings extends InputMethodSettingsActivity
         }
     }
 
-    private void updateVibrationDurationSettingsSummary(SharedPreferences sp, Resources res) {
-        if (mVibrationDurationSettingsPref != null) {
-            mVibrationDurationSettingsPref.setSummary(
+    private void refreshEnablingsOfKeypressSoundAndVibrationSettings(
+            SharedPreferences sp, Resources res) {
+        if (mKeypressVibrationDurationSettingsPref != null) {
+            final boolean hasVibrator = VibratorCompatWrapper.getInstance(this).hasVibrator();
+            final boolean vibrateOn = hasVibrator && sp.getBoolean(Settings.PREF_VIBRATE_ON,
+                    res.getBoolean(R.bool.config_default_vibration_enabled));
+            mKeypressVibrationDurationSettingsPref.setEnabled(vibrateOn);
+        }
+
+        if (mKeypressSoundVolumeSettingsPref != null) {
+            final boolean soundOn = sp.getBoolean(Settings.PREF_SOUND_ON,
+                    res.getBoolean(R.bool.config_default_sound_enabled));
+            mKeypressSoundVolumeSettingsPref.setEnabled(soundOn);
+        }
+    }
+
+    private void updateKeypressVibrationDurationSettingsSummary(
+            SharedPreferences sp, Resources res) {
+        if (mKeypressVibrationDurationSettingsPref != null) {
+            mKeypressVibrationDurationSettingsPref.setSummary(
                     Utils.getCurrentVibrationDuration(sp, res)
                             + res.getString(R.string.settings_ms));
         }
     }
 
-    private void showVibrationSettingsDialog() {
+    private void showKeypressVibrationDurationSettingsDialog() {
         final SharedPreferences sp = getPreferenceManager().getSharedPreferences();
         final Activity context = getActivityInternal();
         final Resources res = context.getResources();
         final AlertDialog.Builder builder = new AlertDialog.Builder(context);
-        builder.setTitle(R.string.prefs_vibration_duration_settings);
+        builder.setTitle(R.string.prefs_keypress_vibration_duration_settings);
         builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
             @Override
             public void onClick(DialogInterface dialog, int whichButton) {
-                final int ms = Integer.valueOf(mVibrationSettingsTextView.getText().toString());
-                sp.edit().putInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, ms).apply();
-                updateVibrationDurationSettingsSummary(sp, res);
+                final int ms = Integer.valueOf(
+                        mKeypressVibrationDurationSettingsTextView.getText().toString());
+                sp.edit().putInt(Settings.PREF_KEYPRESS_VIBRATION_DURATION_SETTINGS, ms).apply();
+                updateKeypressVibrationDurationSettingsSummary(sp, res);
             }
         });
         builder.setNegativeButton(android.R.string.cancel,  new DialogInterface.OnClickListener() {
@@ -669,13 +708,13 @@ public class Settings extends InputMethodSettingsActivity
                 R.layout.vibration_settings_dialog, null);
         final int currentMs = Utils.getCurrentVibrationDuration(
                 getPreferenceManager().getSharedPreferences(), getResources());
-        mVibrationSettingsTextView = (TextView)v.findViewById(R.id.vibration_value);
+        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;
-                mVibrationSettingsTextView.setText(String.valueOf(tempMs));
+                mKeypressVibrationDurationSettingsTextView.setText(String.valueOf(tempMs));
             }
 
             @Override
@@ -689,7 +728,67 @@ public class Settings extends InputMethodSettingsActivity
             }
         });
         sb.setProgress(currentMs);
-        mVibrationSettingsTextView.setText(String.valueOf(currentMs));
+        mKeypressVibrationDurationSettingsTextView.setText(String.valueOf(currentMs));
+        builder.setView(v);
+        builder.create().show();
+    }
+
+    private void updateKeypressSoundVolumeSummary(SharedPreferences sp, Resources res) {
+        if (mKeypressSoundVolumeSettingsPref != null) {
+            mKeypressSoundVolumeSettingsPref.setSummary(
+                    String.valueOf((int)(Utils.getCurrentKeypressSoundVolume(sp, res) * 100)));
+        }
+    }
+
+    private void showKeypressSoundVolumeSettingDialog() {
+        final AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+        final SharedPreferences sp = getPreferenceManager().getSharedPreferences();
+        final Activity context = getActivityInternal();
+        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() {
+            @Override
+            public void onClick(DialogInterface dialog, int whichButton) {
+                final float volume =
+                        ((float)Integer.valueOf(
+                                mKeypressSoundVolumeSettingsTextView.getText().toString())) / 100;
+                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 = context.getLayoutInflater().inflate(
+                R.layout.sound_effect_volume_dialog, null);
+        final int currentVolumeInt = (int)(Utils.getCurrentKeypressSoundVolume(
+                getPreferenceManager().getSharedPreferences(), getResources()) * 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) {
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar arg0) {
+                final float tempVolume = ((float)arg0.getProgress()) / 100;
+                am.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD, tempVolume);
+            }
+        });
+        sb.setProgress(currentVolumeInt);
+        mKeypressSoundVolumeSettingsTextView.setText(String.valueOf(currentVolumeInt));
         builder.setView(v);
         builder.create().show();
     }
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index de29304609978a84970154915b5aa7ab334c5b5d..34c8c894b8fec6b271c1fcfbcf812b3969d197be 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -776,7 +776,7 @@ public class Utils {
     }
 
     public static int getCurrentVibrationDuration(SharedPreferences sp, Resources res) {
-        final int ms = sp.getInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, -1);
+        final int ms = sp.getInt(Settings.PREF_KEYPRESS_VIBRATION_DURATION_SETTINGS, -1);
         if (ms >= 0) {
             return ms;
         }
@@ -791,6 +791,22 @@ public class Utils {
         return -1;
     }
 
+    public static float getCurrentKeypressSoundVolume(SharedPreferences sp, Resources res) {
+        final float volume = sp.getFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, -1.0f);
+        if (volume >= 0) {
+            return volume;
+        }
+
+        final String[] volumePerHardwareList = res.getStringArray(R.array.keypress_volumes);
+        final String hardwarePrefix = Build.HARDWARE + ",";
+        for (final String element : volumePerHardwareList) {
+            if (element.startsWith(hardwarePrefix)) {
+                return Float.parseFloat(element.substring(element.lastIndexOf(',') + 1));
+            }
+        }
+        return -1.0f;
+    }
+
     public static boolean willAutoCorrect(SuggestedWords suggestions) {
         return !suggestions.mTypedWordValid && suggestions.mHasMinimalSuggestion;
     }