diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
index 955cb4c425043af73cfdcb5e6c54276f33b5590f..67e21b10c800125e5aa25596a36b4ab805505ad6 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
@@ -18,13 +18,18 @@ package com.android.inputmethod.accessibility;
 
 import android.graphics.Rect;
 import android.inputmethodservice.InputMethodService;
+import android.os.Bundle;
+import android.os.SystemClock;
 import android.support.v4.view.ViewCompat;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
 import android.support.v4.view.accessibility.AccessibilityRecordCompat;
 import android.util.Log;
 import android.util.SparseArray;
+import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewParent;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.inputmethod.EditorInfo;
@@ -45,6 +50,7 @@ import com.android.inputmethod.keyboard.KeyboardView;
  */
 public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat {
     private static final String TAG = AccessibilityEntityProvider.class.getSimpleName();
+    private static final int UNDEFINED = Integer.MIN_VALUE;
 
     private final KeyboardView mKeyboardView;
     private final InputMethodService mInputMethodService;
@@ -60,6 +66,9 @@ public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat
     /** The parent view's cached on-screen location. */
     private final int[] mParentLocation = new int[2];
 
+    /** The virtual view identifier for the focused node. */
+    private int mAccessibilityFocusedView = UNDEFINED;
+
     public AccessibilityEntityProvider(KeyboardView keyboardView, InputMethodService inputMethod) {
         mKeyboardView = keyboardView;
         mInputMethodService = inputMethod;
@@ -124,7 +133,9 @@ public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat
     public AccessibilityNodeInfoCompat createAccessibilityNodeInfo(int virtualViewId) {
         AccessibilityNodeInfoCompat info = null;
 
-        if (virtualViewId == View.NO_ID) {
+        if (virtualViewId == UNDEFINED) {
+            return null;
+        } else  if (virtualViewId == View.NO_ID) {
             // We are requested to create an AccessibilityNodeInfo describing
             // this View, i.e. the root of the virtual sub-tree.
             info = AccessibilityNodeInfoCompat.obtain(mKeyboardView);
@@ -166,11 +177,114 @@ public class AccessibilityEntityProvider extends AccessibilityNodeProviderCompat
             info.setSource(mKeyboardView, virtualViewId);
             info.setBoundsInScreen(boundsInScreen);
             info.setEnabled(true);
+            info.setClickable(true);
+            info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
+
+            if (mAccessibilityFocusedView == virtualViewId) {
+                info.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
+            } else {
+                info.addAction(AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS);
+            }
         }
 
         return info;
     }
 
+    /**
+     * Simulates a key press by injecting touch events into the keyboard view.
+     * This avoids the complexity of trackers and listeners within the keyboard.
+     *
+     * @param key The key to press.
+     */
+    void simulateKeyPress(Key key) {
+        final int x = key.mX + (key.mWidth / 2);
+        final int y = key.mY + (key.mHeight / 2);
+        final long downTime = SystemClock.uptimeMillis();
+        final MotionEvent downEvent = MotionEvent.obtain(
+                downTime, downTime, MotionEvent.ACTION_DOWN, x, y, 0);
+        final MotionEvent upEvent = MotionEvent.obtain(
+                downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, x, y, 0);
+
+        mKeyboardView.onTouchEvent(downEvent);
+        mKeyboardView.onTouchEvent(upEvent);
+    }
+
+    @Override
+    public boolean performAction(int virtualViewId, int action, Bundle arguments) {
+        final Key key = mVirtualViewIdToKey.get(virtualViewId);
+
+        if (key == null) {
+            return false;
+        }
+
+        return performActionForKey(key, action, arguments);
+    }
+
+    /**
+     * Performs the specified accessibility action for the given key.
+     *
+     * @param key The on which to perform the action.
+     * @param action The action to perform.
+     * @param arguments The action's arguments.
+     * @return The result of performing the action, or false if the action is
+     *         not supported.
+     */
+    boolean performActionForKey(Key key, int action, Bundle arguments) {
+        final int virtualViewId = generateVirtualViewIdForKey(key);
+
+        switch (action) {
+        case AccessibilityNodeInfoCompat.ACTION_CLICK:
+            simulateKeyPress(key);
+            return true;
+        case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS:
+            if (mAccessibilityFocusedView == virtualViewId) {
+                return false;
+            }
+            mAccessibilityFocusedView = virtualViewId;
+            sendAccessibilityEventForKey(
+                    key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+            return true;
+        case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
+            if (mAccessibilityFocusedView != virtualViewId) {
+                return false;
+            }
+            mAccessibilityFocusedView = UNDEFINED;
+            sendAccessibilityEventForKey(
+                    key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public AccessibilityNodeInfoCompat findAccessibilityFocus(int virtualViewId) {
+        return createAccessibilityNodeInfo(mAccessibilityFocusedView);
+    }
+
+    @Override
+    public AccessibilityNodeInfoCompat accessibilityFocusSearch(int direction, int virtualViewId) {
+        // Focus search is not currently supported for IMEs.
+        return null;
+    }
+
+    /**
+     * Sends an accessibility event for the given {@link Key}.
+     *
+     * @param key The key that's sending the event.
+     * @param eventType The type of event to send.
+     */
+    void sendAccessibilityEventForKey(Key key, int eventType) {
+        final AccessibilityEvent event = createAccessibilityEvent(key, eventType);
+        final ViewParent parent = mKeyboardView.getParent();
+
+        if (parent == null) {
+            return;
+        }
+
+        parent.requestSendAccessibilityEvent(mKeyboardView, event);
+    }
+
     /**
      * Returns the context-specific description for a {@link Key}.
      *
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index 34817ba4efe6eb9a4de3b6e8ef67d1af06c9bd15..1b0e488bc4368a4768b1a13c35c62c0212acf042 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -21,10 +21,10 @@ import android.inputmethodservice.InputMethodService;
 import android.support.v4.view.AccessibilityDelegateCompat;
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
-import android.view.accessibility.AccessibilityEvent;
 
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
@@ -91,13 +91,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
      */
     @Override
     public AccessibilityEntityProvider getAccessibilityNodeProvider(View host) {
-        // Instantiate the provide only when requested. Since the system
-        // will call this method multiple times it is a good practice to
-        // cache the provider instance.
-        if (mAccessibilityNodeProvider == null) {
-            mAccessibilityNodeProvider = new AccessibilityEntityProvider(mView, mInputMethod);
-        }
-        return mAccessibilityNodeProvider;
+        return getAccessibilityNodeProvider();
     }
 
     /**
@@ -120,7 +114,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
             // Make sure we're not getting an EXIT event because the user slid
             // off the keyboard area, then force a key press.
             if (pointInView(x, y)) {
-                tracker.onRegisterKey(key);
+                getAccessibilityNodeProvider().simulateKeyPress(key);
             }
             //$FALL-THROUGH$
         case MotionEvent.ACTION_HOVER_ENTER:
@@ -136,6 +130,19 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
         return false;
     }
 
+    /**
+     * @return A lazily-instantiated node provider for this view proxy.
+     */
+    private AccessibilityEntityProvider getAccessibilityNodeProvider() {
+        // Instantiate the provide only when requested. Since the system
+        // will call this method multiple times it is a good practice to
+        // cache the provider instance.
+        if (mAccessibilityNodeProvider == null) {
+            mAccessibilityNodeProvider = new AccessibilityEntityProvider(mView, mInputMethod);
+        }
+        return mAccessibilityNodeProvider;
+    }
+
     /**
      * Utility method to determine whether the given point, in local
      * coordinates, is inside the view, where the area of the view is contracted
@@ -191,32 +198,24 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
             return false;
         }
 
+        final AccessibilityEntityProvider provider = getAccessibilityNodeProvider();
+
         switch (event.getAction()) {
         case MotionEvent.ACTION_HOVER_ENTER:
-            sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER);
+            provider.sendAccessibilityEventForKey(
+                    key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER);
+            provider.performActionForKey(
+                    key, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS, null);
             break;
         case MotionEvent.ACTION_HOVER_EXIT:
-            sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT);
+            provider.sendAccessibilityEventForKey(
+                    key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT);
             break;
         }
 
         return true;
     }
 
-    /**
-     * Populates and sends an {@link AccessibilityEvent} for the specified key.
-     *
-     * @param key The key to send an event for.
-     * @param eventType The type of event to send.
-     */
-    private void sendAccessibilityEventForKey(Key key, int eventType) {
-        final AccessibilityEntityProvider nodeProvider = getAccessibilityNodeProvider(null);
-        final AccessibilityEvent event = nodeProvider.createAccessibilityEvent(key, eventType);
-
-        // Propagates the event up the view hierarchy.
-        mView.getParent().requestSendAccessibilityEvent(mView, event);
-    }
-
     /**
      * Notifies the user of changes in the keyboard shift state.
      */
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 3d861c231ce5a9693dec029d91102d84ad4ca346..f4e4105e3e9ae953f2e64211e1c6cfa59e452799 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -111,6 +111,9 @@ public class KeyCodeDescriptionMapper {
             if (mKeyLabelMap.containsKey(label)) {
                 return context.getString(mKeyLabelMap.get(label));
             }
+
+            // Otherwise, return the label.
+            return key.mLabel;
         }
 
         // Just attempt to speak the description.