diff --git a/java/res/values/keycodes.xml b/java/res/values/keycodes.xml
index 6c18cb42a12a5782aef5606cc2a0778b80bcc259..d6f9bfc28b95e2a7d775946177c851e296dd0e73 100644
--- a/java/res/values/keycodes.xml
+++ b/java/res/values/keycodes.xml
@@ -28,4 +28,24 @@
     <integer name="key_delete">-5</integer>
     <integer name="key_settings">-100</integer>
     <integer name="key_voice">-102</integer>
+
+    <!-- Array used for mapping key codes to description strings. -->
+    <array name="key_descriptions">
+        <item>@integer/key_tab</item>
+        <item>@string/description_tab_key</item>
+        <item>@integer/key_return</item>
+        <item>@string/description_return_key</item>
+        <item>@integer/key_space</item>
+        <item>@string/description_space_key</item>
+        <item>@integer/key_shift</item>
+        <item>@string/description_shift_key</item>
+        <item>@integer/key_switch_alpha_symbol</item>
+        <item>@string/description_switch_alpha_symbol_key</item>
+        <item>@integer/key_delete</item>
+        <item>@string/description_delete_key</item>
+        <item>@integer/key_settings</item>
+        <item>@string/description_settings_key</item>
+        <item>@integer/key_voice</item>
+        <item>@string/description_voice_key</item>
+    </array>
 </resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index f63d6816ced395ddb7d6cd6b79c759d5e3d6e9b7..3c0a9c1a2366962a1e167727107b87d5cda8ca63 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -102,6 +102,31 @@
     <!-- Label for "Wait" key of phone number keyboard.  Must be short to fit on key! [CHAR LIMIT=5]-->
     <string name="label_wait_key">Wait</string>
 
+    <!-- Spoken text description for delete key. -->
+    <string name="description_delete_key">Delete</string>
+    <!-- Spoken text description for return key. -->
+    <string name="description_return_key">Return</string>
+    <!-- Spoken text description for settings key. -->
+    <string name="description_settings_key">Settings</string>
+    <!-- Spoken text description for shift key. -->
+    <string name="description_shift_key">Shift</string>
+    <!-- Spoken text description for space key. -->
+    <string name="description_space_key">Space</string>
+    <!-- Spoken text description for symbols key. -->
+    <string name="description_switch_alpha_symbol_key">Symbols</string>
+    <!-- Spoken text description for tab key. -->
+    <string name="description_tab_key">Tab</string>
+    <!-- Spoken text description for voice input key. -->
+    <string name="description_voice_key">Voice Input</string>
+    <!-- Spoken text description for symbols mode on. -->
+    <string name="description_symbols_on">Symbols on</string>
+    <!-- Spoken text description for symbols mode off. -->
+    <string name="description_symbols_off">Symbols off</string>
+    <!-- Spoken text description for shift mode on. -->
+    <string name="description_shift_on">Shift on</string>
+    <!-- Spoken text description for shift mode off. -->
+    <string name="description_shift_off">Shift off</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/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 86b4405bdef20b15369ab88f590b87db618d79ce..6651f9ffaae4925ceb19a66686a0c14749c36664 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -344,7 +344,8 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
             // state when shift key is pressed to go to normal mode.
             // On the other hand, on distinct multi touch panel device, turning off the shift locked
             // state with shift key pressing is handled by onReleaseShift().
-            if (!hasDistinctMultitouch() && !shifted && latinKeyboard.isShiftLocked()) {
+            if ((!hasDistinctMultitouch() || isAccessibilityEnabled())
+                    && !shifted && latinKeyboard.isShiftLocked()) {
                 latinKeyboard.setShiftLocked(false);
             }
             if (latinKeyboard.setShifted(shifted)) {
@@ -442,6 +443,9 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
     public void onPressShift() {
         if (!isKeyboardAvailable())
             return;
+        // If accessibility is enabled, disable momentary shift lock.
+        if (isAccessibilityEnabled())
+            return;
         ShiftKeyState shiftKeyState = mShiftKeyState;
         if (DEBUG_STATE)
             Log.d(TAG, "onPressShift:"
@@ -477,6 +481,9 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
     public void onReleaseShift() {
         if (!isKeyboardAvailable())
             return;
+        // If accessibility is enabled, disable momentary shift lock.
+        if (isAccessibilityEnabled())
+            return;
         ShiftKeyState shiftKeyState = mShiftKeyState;
         if (DEBUG_STATE)
             Log.d(TAG, "onReleaseShift:"
@@ -502,6 +509,9 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
     }
 
     public void onPressSymbol() {
+        // If accessibility is enabled, disable momentary symbol lock.
+        if (isAccessibilityEnabled())
+            return;
         if (DEBUG_STATE)
             Log.d(TAG, "onPressSymbol:"
                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
@@ -512,6 +522,9 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
     }
 
     public void onReleaseSymbol() {
+        // If accessibility is enabled, disable momentary symbol lock.
+        if (isAccessibilityEnabled())
+            return;
         if (DEBUG_STATE)
             Log.d(TAG, "onReleaseSymbol:"
                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
@@ -524,6 +537,9 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
     }
 
     public void onOtherKeyPressed() {
+        // If accessibility is enabled, disable momentary mode locking.
+        if (isAccessibilityEnabled())
+            return;
         if (DEBUG_STATE)
             Log.d(TAG, "onOtherKeyPressed:"
                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
@@ -582,6 +598,10 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
         }
     }
 
+    public boolean isAccessibilityEnabled() {
+        return mInputView != null && mInputView.isAccessibilityEnabled();
+    }
+
     public boolean hasDistinctMultitouch() {
         return mInputView != null && mInputView.hasDistinctMultitouch();
     }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 25bf0e0b69a09046e36feb88a98ff766a08e0248..851430fb89583b8bf2fb3756949d987a6979eac4 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -37,6 +37,7 @@ import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.os.Message;
 import android.os.SystemClock;
+import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.TypedValue;
@@ -146,6 +147,9 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
     private final boolean mHasDistinctMultitouch;
     private int mOldPointerCount = 1;
 
+    // Accessibility
+    private boolean mIsAccessibilityEnabled;
+
     protected KeyDetector mKeyDetector = new ProximityKeyDetector();
 
     // Swipe gesture detector
@@ -523,7 +527,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
     }
 
     /**
-     * Return whether the device has distinct multi-touch panel.
+     * Returns whether the device has distinct multi-touch panel.
      * @return true if the device has distinct multi-touch panel.
      */
     @Override
@@ -531,6 +535,28 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         return mHasDistinctMultitouch;
     }
 
+    /**
+     * Enables or disables accessibility.
+     * @param accessibilityEnabled whether or not to enable accessibility
+     */
+    public void setAccessibilityEnabled(boolean accessibilityEnabled) {
+        mIsAccessibilityEnabled = accessibilityEnabled;
+
+        // Propagate this change to all existing pointer trackers.
+        for (PointerTracker tracker : mPointerTrackers) {
+            tracker.setAccessibilityEnabled(accessibilityEnabled);
+        }
+    }
+
+    /**
+     * Returns whether the device has accessibility enabled.
+     * @return true if the device has accessibility enabled.
+     */
+    @Override
+    public boolean isAccessibilityEnabled() {
+        return mIsAccessibilityEnabled;
+    }
+
     /**
      * Enables or disables the key feedback popup. This is a popup that shows a magnified
      * version of the depressed key. By default the preview is enabled.
@@ -1212,15 +1238,18 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         // TODO: cleanup this code into a multi-touch to single-touch event converter class?
         // If the device does not have distinct multi-touch support panel, ignore all multi-touch
         // events except a transition from/to single-touch.
-        if (!mHasDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) {
+        if ((!mHasDistinctMultitouch || mIsAccessibilityEnabled)
+                && pointerCount > 1 && oldPointerCount > 1) {
             return true;
         }
 
         // Track the last few movements to look for spurious swipes.
         mSwipeTracker.addMovement(me);
 
-        // Gesture detector must be enabled only when mini-keyboard is not on the screen.
-        if (mMiniKeyboardView == null
+        // Gesture detector must be enabled only when mini-keyboard is not on the screen and
+        // accessibility is not enabled.
+        // TODO: Reconcile gesture detection and accessibility features.
+        if (mMiniKeyboardView == null && !mIsAccessibilityEnabled
                 && mGestureDetector != null && mGestureDetector.onTouchEvent(me)) {
             dismissKeyPreview();
             mHandler.cancelKeyTimers();
@@ -1265,7 +1294,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
         // TODO: cleanup this code into a multi-touch to single-touch event converter class?
         // Translate mutli-touch event to single-touch events on the device that has no distinct
         // multi-touch panel.
-        if (!mHasDistinctMultitouch) {
+        if (!mHasDistinctMultitouch || mIsAccessibilityEnabled) {
             // Use only main (id=0) pointer tracker.
             PointerTracker tracker = getPointerTracker(0);
             if (pointerCount == 1 && oldPointerCount == 2) {
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index 6c3ad5edba57b176833c54fbe84376c6c428e48d..77e9caecce276ed6be5891dd0f0cb1f5b492580a 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -145,6 +145,10 @@ public class LatinKeyboardView extends KeyboardView {
         // If device has distinct multi touch panel, there is no need to check sudden jump.
         if (hasDistinctMultitouch())
             return false;
+        // If accessibiliy is enabled, stop looking for sudden jumps because it interferes
+        // with touch exploration of the keyboard.
+        if (isAccessibilityEnabled())
+            return false;
         final int action = me.getAction();
         final int x = (int) me.getX();
         final int y = (int) me.getY();
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index ac375473aa010e734010797d9e7b85c0dd3e9def..8e9d02247f81babe3d08204f6da07f2ad48f6d8d 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -38,6 +38,7 @@ public class PointerTracker {
         public void invalidateKey(Key key);
         public void showPreview(int keyIndex, PointerTracker tracker);
         public boolean hasDistinctMultitouch();
+        public boolean isAccessibilityEnabled();
     }
 
     public final int mPointerId;
@@ -68,6 +69,9 @@ public class PointerTracker {
 
     private final PointerTrackerKeyState mKeyState;
 
+    // true if accessibility is enabled in the parent keyboard
+    private boolean mIsAccessibilityEnabled;
+
     // true if keyboard layout has been changed.
     private boolean mKeyboardLayoutHasBeenChanged;
 
@@ -112,6 +116,7 @@ public class PointerTracker {
         mKeyDetector = keyDetector;
         mKeyboardSwitcher = KeyboardSwitcher.getInstance();
         mKeyState = new PointerTrackerKeyState(keyDetector);
+        mIsAccessibilityEnabled = proxy.isAccessibilityEnabled();
         mHasDistinctMultitouch = proxy.hasDistinctMultitouch();
         mConfigSlidingKeyInputEnabled = res.getBoolean(R.bool.config_sliding_key_input_enabled);
         mDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start);
@@ -128,6 +133,10 @@ public class PointerTracker {
         mListener = listener;
     }
 
+    public void setAccessibilityEnabled(boolean accessibilityEnabled) {
+        mIsAccessibilityEnabled = accessibilityEnabled;
+    }
+
     // Returns true if keyboard has been changed by this callback.
     private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) {
         if (DEBUG_LISTENER)
@@ -312,9 +321,10 @@ public class PointerTracker {
     private void onDownEventInternal(int x, int y, long eventTime) {
         int keyIndex = mKeyState.onDownKey(x, y, eventTime);
         // Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
-        // from modifier key, or 3) this pointer is on mini-keyboard.
+        // from modifier key, 3) this pointer is on mini-keyboard, or 4) accessibility is enabled.
         mIsAllowedSlidingKeyInput = mConfigSlidingKeyInputEnabled || isModifierInternal(keyIndex)
-                || mKeyDetector instanceof MiniKeyboardKeyDetector;
+                || mKeyDetector instanceof MiniKeyboardKeyDetector
+                || mIsAccessibilityEnabled;
         mKeyboardLayoutHasBeenChanged = false;
         mKeyAlreadyProcessed = false;
         mIsRepeatableKey = false;
@@ -327,7 +337,9 @@ public class PointerTracker {
                 keyIndex = mKeyState.onDownKey(x, y, eventTime);
         }
         if (isValidKeyIndex(keyIndex)) {
-            if (mKeys[keyIndex].mRepeatable) {
+            // Accessibility disables key repeat because users may need to pause on a key to hear
+            // its spoken description.
+            if (mKeys[keyIndex].mRepeatable && !mIsAccessibilityEnabled) {
                 repeatKey(keyIndex);
                 mHandler.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this);
                 mIsRepeatableKey = true;
@@ -517,8 +529,9 @@ public class PointerTracker {
         updateKeyGraphics(keyIndex);
         // The modifier key, such as shift key, should not be shown as preview when multi-touch is
         // supported. On the other hand, if multi-touch is not supported, the modifier key should
-        // be shown as preview.
-        if (mHasDistinctMultitouch && isModifier()) {
+        // be shown as preview. If accessibility is turned on, the modifier key should be shown as
+        // preview.
+        if (mHasDistinctMultitouch && isModifier() && !mIsAccessibilityEnabled) {
             mProxy.showPreview(NOT_A_KEY, this);
         } else {
             mProxy.showPreview(keyIndex, this);
@@ -526,6 +539,11 @@ public class PointerTracker {
     }
 
     private void startLongPressTimer(int keyIndex) {
+        // Accessibility disables long press because users are likely to need to pause on a key
+        // for an unspecified duration in order to hear the key's spoken description.
+        if (mIsAccessibilityEnabled) {
+            return;
+        }
         Key key = getKey(keyIndex);
         if (key.mCode == Keyboard.CODE_SHIFT) {
             mHandler.startLongPressShiftTimer(mLongPressShiftKeyTimeout, keyIndex, this);
diff --git a/java/src/com/android/inputmethod/latin/AccessibilityUtils.java b/java/src/com/android/inputmethod/latin/AccessibilityUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..cd3f9e0ad2fd87775b2d1acd16453a5e8bdcd55e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/AccessibilityUtils.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.TypedArray;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Utility functions for accessibility support.
+ */
+public class AccessibilityUtils {
+    /** Shared singleton instance. */
+    private static final AccessibilityUtils sInstance = new AccessibilityUtils();
+    private /* final */ LatinIME mService;
+    private /* final */ AccessibilityManager mAccessibilityManager;
+    private /* final */ Map<Integer, CharSequence> mDescriptions;
+
+    /**
+     * Returns a shared instance of AccessibilityUtils.
+     *
+     * @return A shared instance of AccessibilityUtils.
+     */
+    public static AccessibilityUtils getInstance() {
+        return sInstance;
+    }
+
+    /**
+     * Initializes (or re-initializes) the shared instance of AccessibilityUtils
+     * with the specified parent service and preferences.
+     *
+     * @param service The parent input method service.
+     * @param prefs The parent preferences.
+     */
+    public static void init(LatinIME service, SharedPreferences prefs) {
+        sInstance.initialize(service, prefs);
+    }
+
+    private AccessibilityUtils() {
+        // This class is not publicly instantiable.
+    }
+
+    /**
+     * Initializes (or re-initializes) with the specified parent service and
+     * preferences.
+     *
+     * @param service The parent input method service.
+     * @param prefs The parent preferences.
+     */
+    private void initialize(LatinIME service, SharedPreferences prefs) {
+        mService = service;
+        mAccessibilityManager = (AccessibilityManager) service.getSystemService(
+                Context.ACCESSIBILITY_SERVICE);
+        mDescriptions = null;
+    }
+
+    /**
+     * Returns true if accessibility is enabled.
+     *
+     * @return {@code true} if accessibility is enabled.
+     */
+    public boolean isAccessibilityEnabled() {
+        return mAccessibilityManager.isEnabled();
+    }
+
+    /**
+     * Speaks a key's action after it has been released. Does not speak letter
+     * keys since typed keys are already spoken aloud by TalkBack.
+     * <p>
+     * No-op if accessibility is not enabled.
+     * </p>
+     *
+     * @param primaryCode The primary code of the released key.
+     * @param switcher The input method's {@link KeyboardSwitcher}.
+     */
+    public void onRelease(int primaryCode, KeyboardSwitcher switcher) {
+        if (!isAccessibilityEnabled()) {
+            return;
+        }
+
+        int resId = -1;
+
+        switch (primaryCode) {
+            case Keyboard.CODE_SHIFT: {
+                if (switcher.isShiftedOrShiftLocked()) {
+                    resId = R.string.description_shift_on;
+                } else {
+                    resId = R.string.description_shift_off;
+                }
+                break;
+            }
+
+            case Keyboard.CODE_SWITCH_ALPHA_SYMBOL: {
+                if (switcher.isAlphabetMode()) {
+                    resId = R.string.description_symbols_off;
+                } else {
+                    resId = R.string.description_symbols_on;
+                }
+                break;
+            }
+        }
+
+        if (resId >= 0) {
+            speakDescription(mService.getResources().getText(resId));
+        }
+    }
+
+    /**
+     * Speaks a key's description for accessibility. If a key has an explicit
+     * description defined in keycodes.xml, that will be used. Otherwise, if the
+     * key is a Unicode character, then its character will be used.
+     * <p>
+     * No-op if accessibility is not enabled.
+     * </p>
+     *
+     * @param primaryCode The primary code of the pressed key.
+     * @param switcher The input method's {@link KeyboardSwitcher}.
+     */
+    public void onPress(int primaryCode, KeyboardSwitcher switcher) {
+        if (!isAccessibilityEnabled()) {
+            return;
+        }
+
+        // TODO Use the current keyboard state to read "Switch to symbols"
+        // instead of just "Symbols" (and similar for shift key).
+        CharSequence description = describeKey(primaryCode);
+        if (description == null && Character.isDefined((char) primaryCode)) {
+            description = Character.toString((char) primaryCode);
+        }
+
+        if (description != null) {
+            speakDescription(description);
+        }
+    }
+
+    /**
+     * Returns a text description for a given key code. If the key does not have
+     * an explicit description, returns <code>null</code>.
+     *
+     * @param keyCode An integer key code.
+     * @return A {@link CharSequence} describing the key or <code>null</code> if
+     *         no description is available.
+     */
+    private CharSequence describeKey(int keyCode) {
+        // If not loaded yet, load key descriptions from XML file.
+        if (mDescriptions == null) {
+            mDescriptions = loadDescriptions();
+        }
+
+        return mDescriptions.get(keyCode);
+    }
+
+    /**
+     * Loads key descriptions from resources.
+     */
+    private Map<Integer, CharSequence> loadDescriptions() {
+        final Map<Integer, CharSequence> descriptions = new HashMap<Integer, CharSequence>();
+        final TypedArray array = mService.getResources().obtainTypedArray(R.array.key_descriptions);
+
+        // Key descriptions are stored as a key code followed by a string.
+        for (int i = 0; i < array.length() - 1; i += 2) {
+            int code = array.getInteger(i, 0);
+            CharSequence desc = array.getText(i + 1);
+
+            descriptions.put(code, desc);
+        }
+
+        array.recycle();
+
+        return descriptions;
+    }
+
+    /**
+     * Sends a character sequence to be read aloud.
+     *
+     * @param description The {@link CharSequence} to be read aloud.
+     */
+    private void speakDescription(CharSequence description) {
+        // TODO We need to add an AccessibilityEvent type for IMEs.
+        final AccessibilityEvent event = AccessibilityEvent.obtain(
+                AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
+        event.setPackageName(mService.getPackageName());
+        event.setClassName(getClass().getName());
+        event.setAddedCount(description.length());
+        event.getText().add(description);
+
+        mAccessibilityManager.sendAccessibilityEvent(event);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 0c3509c775dd28ec637eddce4017ddd6bc5f93de..97f6b8d837bcd30fbce8c786ae97fabbdac094c5 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -158,6 +158,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
     private boolean mIsSettingsSuggestionStripOn;
     private boolean mApplicationSpecifiedCompletionOn;
 
+    private AccessibilityUtils mAccessibilityUtils;
+
     private final StringBuilder mComposing = new StringBuilder();
     private WordComposer mWord = new WordComposer();
     private CharSequence mBestWord;
@@ -371,6 +373,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
         LatinImeLogger.init(this, prefs);
         SubtypeSwitcher.init(this, prefs);
         KeyboardSwitcher.init(this, prefs);
+        AccessibilityUtils.init(this, prefs);
 
         super.onCreate();
 
@@ -378,6 +381,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
         mInputMethodId = Utils.getInputMethodId(mImm, getPackageName());
         mSubtypeSwitcher = SubtypeSwitcher.getInstance();
         mKeyboardSwitcher = KeyboardSwitcher.getInstance();
+        mAccessibilityUtils = AccessibilityUtils.getInstance();
 
         final Resources res = getResources();
         mResources = res;
@@ -560,8 +564,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
 
         updateCorrectionMode();
 
+        final boolean accessibilityEnabled = mAccessibilityUtils.isAccessibilityEnabled();
+
         inputView.setPreviewEnabled(mPopupOn);
         inputView.setProximityCorrectionEnabled(true);
+        inputView.setAccessibilityEnabled(accessibilityEnabled);
         // If we just entered a text field, maybe it has some old text that requires correction
         checkReCorrectionOnStart();
         inputView.setForeground(true);
@@ -1087,6 +1094,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
         }
         mLastKeyTime = when;
         KeyboardSwitcher switcher = mKeyboardSwitcher;
+        final boolean accessibilityEnabled = switcher.isAccessibilityEnabled();
         final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
         switch (primaryCode) {
         case Keyboard.CODE_DELETE:
@@ -1096,12 +1104,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
             break;
         case Keyboard.CODE_SHIFT:
             // Shift key is handled in onPress() when device has distinct multi-touch panel.
-            if (!distinctMultiTouch)
+            if (!distinctMultiTouch || accessibilityEnabled)
                 switcher.toggleShift();
             break;
         case Keyboard.CODE_SWITCH_ALPHA_SYMBOL:
             // Symbol key is handled in onPress() when device has distinct multi-touch panel.
-            if (!distinctMultiTouch)
+            if (!distinctMultiTouch || accessibilityEnabled)
                 switcher.changeKeyboardMode();
             break;
         case Keyboard.CODE_CANCEL:
@@ -1939,6 +1947,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
         } else {
             switcher.onOtherKeyPressed();
         }
+        mAccessibilityUtils.onPress(primaryCode, switcher);
     }
 
     @Override
@@ -1952,6 +1961,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
         } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
             switcher.onReleaseSymbol();
         }
+        mAccessibilityUtils.onRelease(primaryCode, switcher);
     }