diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 089acf379209586603311d1b0a10d3f7ec8a17d6..1e8b7db96418f73d3be118785849d8d2091e845d 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -156,12 +156,12 @@
 
     <!-- Spoken description for unknown keyboard keys. -->
     <string name="spoken_description_unknown">Key code %d</string>
-    <!-- Spoken description for the "Shift" keyboard key. -->
+    <!-- Spoken description for the "Shift" keyboard key when "Shift" is off. -->
     <string name="spoken_description_shift">Shift</string>
-    <!-- Spoken description for the "Shift" keyboard key's pressed state. -->
-    <string name="spoken_description_shift_shifted">Shift enabled</string>
-    <!-- Spoken description for the "Shift" keyboard key's pressed state. -->
-    <string name="spoken_description_caps_lock">Caps lock enabled</string>
+    <!-- Spoken description for the "Shift" keyboard key when "Shift" is on. -->
+    <string name="spoken_description_shift_shifted">Shift on (tap to disable)</string>
+    <!-- Spoken description for the "Shift" keyboard key when "Caps lock" is on. -->
+    <string name="spoken_description_caps_lock">Caps lock on (tap to disable)</string>
     <!-- Spoken description for the "Delete" keyboard key. -->
     <string name="spoken_description_delete">Delete</string>
     <!-- Spoken description for the "To Symbol" keyboard key. -->
@@ -185,6 +185,22 @@
     <!-- Spoken description for the "\u2022" (BULLET) keyboard key. -->
     <string name="spoken_description_dot">Dot</string>
 
+    <!-- Spoken feedback after turning "Shift" mode on. -->
+    <string name="spoken_description_shiftmode_on">Shift enabled</string>
+    <!-- Spoken feedback after turning "Caps lock" mode on. -->
+    <string name="spoken_description_shiftmode_locked">Caps lock enabled</string>
+    <!-- Spoken feedback after turning "Shift" mode off. -->
+    <string name="spoken_description_shiftmode_off">Shift disabled</string>
+
+    <!-- Spoken feedback after changing to the symbols keyboard. -->
+    <string name="spoken_description_mode_symbol">Symbols mode</string>
+    <!-- Spoken feedback after changing to the alphanumeric keyboard. -->
+    <string name="spoken_description_mode_alpha">Letters mode</string>
+    <!-- Spoken feedback after changing to the phone dialer keyboard. -->
+    <string name="spoken_description_mode_phone">Phone mode</string>
+    <!-- Spoken feedback after changing to the shifted phone dialer (symbols) keyboard. -->
+    <string name="spoken_description_mode_phone_shift">Phone symbols mode</string>
+
     <!-- Voice related labels -->
 
     <!-- Title of the warning dialog that shows when a user initiates voice input for
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index f98359dc11b88e10c6cdf261258a7f544bf2b2b0..2294a18a01c1eabd2261487bbd683628bd0a14c4 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -28,8 +28,11 @@ import android.view.inputmethod.EditorInfo;
 import com.android.inputmethod.compat.AccessibilityEventCompatUtils;
 import com.android.inputmethod.compat.MotionEventCompatUtils;
 import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.keyboard.LatinKeyboardView;
 import com.android.inputmethod.keyboard.PointerTracker;
+import com.android.inputmethod.latin.R;
 
 public class AccessibleKeyboardViewProxy {
     private static final String TAG = AccessibleKeyboardViewProxy.class.getSimpleName();
@@ -175,4 +178,71 @@ public class AccessibleKeyboardViewProxy {
             return true;
         }
     }
+
+    /**
+     * Notifies the user of changes in the keyboard shift state.
+     */
+    public void notifyShiftState() {
+        final Keyboard keyboard = mView.getKeyboard();
+        final KeyboardId keyboardId = keyboard.mId;
+        final int elementId = keyboardId.mElementId;
+        final Context context = mView.getContext();
+        final CharSequence text;
+
+        switch (elementId) {
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
+            text = context.getText(R.string.spoken_description_shiftmode_locked);
+            break;
+        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
+        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
+            text = context.getText(R.string.spoken_description_shiftmode_on);
+            break;
+        default:
+            text = context.getText(R.string.spoken_description_shiftmode_off);
+        }
+
+        AccessibilityUtils.getInstance().speak(text);
+    }
+
+    /**
+     * Notifies the user of changes in the keyboard symbols state.
+     */
+    public void notifySymbolsState() {
+        final Keyboard keyboard = mView.getKeyboard();
+        final Context context = mView.getContext();
+        final KeyboardId keyboardId = keyboard.mId;
+        final int elementId = keyboardId.mElementId;
+        final int resId;
+
+        switch (elementId) {
+        case KeyboardId.ELEMENT_ALPHABET:
+        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
+            resId = R.string.spoken_description_mode_alpha;
+            break;
+        case KeyboardId.ELEMENT_SYMBOLS:
+        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
+            resId = R.string.spoken_description_mode_symbol;
+            break;
+        case KeyboardId.ELEMENT_PHONE:
+            resId = R.string.spoken_description_mode_phone;
+            break;
+        case KeyboardId.ELEMENT_PHONE_SYMBOLS:
+            resId = R.string.spoken_description_mode_phone_shift;
+            break;
+        default:
+            resId = -1;
+        }
+
+        if (resId < 0) {
+            return;
+        }
+
+        final String text = context.getString(resId);
+        AccessibilityUtils.getInstance().speak(text);
+    }
 }
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 18a4bfbfc9c82ddf08155117dd1886afae38aed9..f0dba4a02e56117821f1f42166a29a02679a507d 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -129,19 +129,37 @@ public class KeyCodeDescriptionMapper {
      *         the key
      */
     private CharSequence getDescriptionForSwitchAlphaSymbol(Context context, Keyboard keyboard) {
-        final KeyboardId id = keyboard.mId;
-
-        if (id.isAlphabetKeyboard()) {
-            return context.getString(R.string.spoken_description_to_symbol);
-        } else if (id.isSymbolsKeyboard()) {
-            return context.getString(R.string.spoken_description_to_alpha);
-        } else if (id.isPhoneShiftKeyboard()) {
-            return context.getString(R.string.spoken_description_to_numeric);
-        } else if (id.isPhoneKeyboard()) {
-            return context.getString(R.string.spoken_description_to_symbol);
-        } else {
+        final KeyboardId keyboardId = keyboard.mId;
+        final int elementId = keyboardId.mElementId;
+        final int resId;
+
+        switch (elementId) {
+        case KeyboardId.ELEMENT_ALPHABET:
+        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
+            resId = R.string.spoken_description_to_symbol;
+            break;
+        case KeyboardId.ELEMENT_SYMBOLS:
+        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
+            resId = R.string.spoken_description_to_alpha;
+            break;
+        case KeyboardId.ELEMENT_PHONE:
+            resId = R.string.spoken_description_to_symbol;
+            break;
+        case KeyboardId.ELEMENT_PHONE_SYMBOLS:
+            resId = R.string.spoken_description_to_numeric;
+            break;
+        default:
+            resId = -1;
+        }
+
+        if (resId < 0) {
             return null;
         }
+
+        return context.getString(resId);
     }
 
     /**
@@ -152,13 +170,21 @@ public class KeyCodeDescriptionMapper {
      * @return A context-sensitive description of the "Shift" key.
      */
     private CharSequence getDescriptionForShiftKey(Context context, Keyboard keyboard) {
+        final KeyboardId keyboardId = keyboard.mId;
+        final int elementId = keyboardId.mElementId;
         final int resId;
 
-        if (keyboard.isShiftLocked()) {
+        switch (elementId) {
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
             resId = R.string.spoken_description_caps_lock;
-        } else if (keyboard.isShiftedOrShiftLocked()) {
+            break;
+        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
+        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
+        case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
             resId = R.string.spoken_description_shift_shifted;
-        } else {
+            break;
+        default:
             resId = R.string.spoken_description_shift;
         }
 
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 6cb68d6a0ff609f9daa7977fcd8029ee36215f6c..1cb79e7070c694f18aa9e743581094b5a23dfd69 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -49,6 +49,7 @@ import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.InputConnection;
 
 import com.android.inputmethod.accessibility.AccessibilityUtils;
+import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
 import com.android.inputmethod.compat.CompatUtils;
 import com.android.inputmethod.compat.EditorInfoCompatUtils;
 import com.android.inputmethod.compat.InputConnectionCompatUtils;
@@ -2323,6 +2324,18 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
     @Override
     public void onReleaseKey(int primaryCode, boolean withSliding) {
         mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding);
+
+        // If accessibility is on, ensure the user receives keyboard state updates.
+        if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
+            switch (primaryCode) {
+            case Keyboard.CODE_SHIFT:
+                AccessibleKeyboardViewProxy.getInstance().notifyShiftState();
+                break;
+            case Keyboard.CODE_SWITCH_ALPHA_SYMBOL:
+                AccessibleKeyboardViewProxy.getInstance().notifySymbolsState();
+                break;
+            }
+        }
     }
 
     // receive ringer mode change and network state change.