Merge "Adding APIs for an accessibility service to intercept key events." into jb-mr2-dev
diff --git a/api/current.txt b/api/current.txt
index cc465e2..2f79c9b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2075,6 +2075,7 @@
     method public final android.os.IBinder onBind(android.content.Intent);
     method protected boolean onGesture(int);
     method public abstract void onInterrupt();
+    method protected boolean onKeyEvent(android.view.KeyEvent);
     method protected void onServiceConnected();
     method public final boolean performGlobalAction(int);
     method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 811b92a..31de98d 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -24,6 +24,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
+import android.view.KeyEvent;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityInteractionClient;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -348,6 +349,7 @@
         public void onServiceConnected();
         public void onSetConnectionId(int connectionId);
         public boolean onGesture(int gestureId);
+        public boolean onKeyEvent(KeyEvent event);
     }
 
     private int mConnectionId;
@@ -413,6 +415,32 @@
     }
 
     /**
+     * Callback that allows an accessibility service to observe the key events
+     * before they are passed to the rest of the system. This means that the events
+     * are first delivered here before they are passed to the device policy, the
+     * input method, or applications.
+     * <p>
+     * <strong>Note:</strong> It is important that key events are handled in such
+     * a way that the event stream that would be passed to the rest of the system
+     * is well-formed. For example, handling the down event but not the up event
+     * and vice versa would generate an inconsistent event stream.
+     * </p>
+     * <p>
+     * <strong>Note:</strong> The key events delivered in this method are copies
+     * and modifying them will have no effect on the events that will be passed
+     * to the system. This method is intended to perform purely filtering
+     * functionality.
+     * <p>
+     *
+     * @param event The event to be processed.
+     * @return If true then the event will be consumed and not delivered to
+     *         applications, otherwise it will be delivered as usual.
+     */
+    protected boolean onKeyEvent(KeyEvent event) {
+        return false;
+    }
+
+    /**
      * Gets the root node in the currently active window if this service
      * can retrieve window content.
      *
@@ -535,6 +563,11 @@
             public boolean onGesture(int gestureId) {
                 return AccessibilityService.this.onGesture(gestureId);
             }
+
+            @Override
+            public boolean onKeyEvent(KeyEvent event) {
+                return AccessibilityService.this.onKeyEvent(event);
+            }
         });
     }
 
@@ -554,11 +587,14 @@
         private static final int DO_ON_ACCESSIBILITY_EVENT = 30;
         private static final int DO_ON_GESTURE = 40;
         private static final int DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE = 50;
+        private static final int DO_ON_KEY_EVENT = 60;
 
         private final HandlerCaller mCaller;
 
         private final Callbacks mCallback;
 
+        private int mConnectionId;
+
         public IAccessibilityServiceClientWrapper(Context context, Looper looper,
                 Callbacks callback) {
             mCallback = callback;
@@ -591,41 +627,65 @@
             mCaller.sendMessage(message);
         }
 
+        @Override
+        public void onKeyEvent(KeyEvent event, int sequence) {
+            Message message = mCaller.obtainMessageIO(DO_ON_KEY_EVENT, sequence, event);
+            mCaller.sendMessage(message);
+        }
+
         public void executeMessage(Message message) {
             switch (message.what) {
-                case DO_ON_ACCESSIBILITY_EVENT :
+                case DO_ON_ACCESSIBILITY_EVENT: {
                     AccessibilityEvent event = (AccessibilityEvent) message.obj;
                     if (event != null) {
                         AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event);
                         mCallback.onAccessibilityEvent(event);
                         event.recycle();
                     }
-                    return;
-                case DO_ON_INTERRUPT :
+                } return;
+                case DO_ON_INTERRUPT: {
                     mCallback.onInterrupt();
-                    return;
-                case DO_SET_SET_CONNECTION :
-                    final int connectionId = message.arg1;
+                } return;
+                case DO_SET_SET_CONNECTION: {
+                    mConnectionId = message.arg1;
                     IAccessibilityServiceConnection connection =
                         (IAccessibilityServiceConnection) message.obj;
                     if (connection != null) {
-                        AccessibilityInteractionClient.getInstance().addConnection(connectionId,
+                        AccessibilityInteractionClient.getInstance().addConnection(mConnectionId,
                                 connection);
-                        mCallback.onSetConnectionId(connectionId);
+                        mCallback.onSetConnectionId(mConnectionId);
                         mCallback.onServiceConnected();
                     } else {
-                        AccessibilityInteractionClient.getInstance().removeConnection(connectionId);
+                        AccessibilityInteractionClient.getInstance().removeConnection(mConnectionId);
                         AccessibilityInteractionClient.getInstance().clearCache();
                         mCallback.onSetConnectionId(AccessibilityInteractionClient.NO_ID);
                     }
-                    return;
-                case DO_ON_GESTURE :
+                } return;
+                case DO_ON_GESTURE: {
                     final int gestureId = message.arg1;
                     mCallback.onGesture(gestureId);
-                    return;
-                case DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE:
+                } return;
+                case DO_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE: {
                     AccessibilityInteractionClient.getInstance().clearCache();
-                    return;
+                } return;
+                case DO_ON_KEY_EVENT: {
+                    KeyEvent event = (KeyEvent) message.obj;
+                    try {
+                        IAccessibilityServiceConnection connection = AccessibilityInteractionClient
+                                .getInstance().getConnection(mConnectionId);
+                        if (connection != null) {
+                            final boolean result = mCallback.onKeyEvent(event);
+                            final int sequence = message.arg1;
+                            try {
+                                connection.setOnKeyEventResult(result, sequence);
+                            } catch (RemoteException re) {
+                                /* ignore */
+                            }
+                        }
+                    } finally {
+                        event.recycle();
+                    }
+                } return;
                 default :
                     Log.w(LOG_TAG, "Unknown message type " + message.what);
             }
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 5d684e3..c5e3d43a 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -18,6 +18,7 @@
 
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.view.accessibility.AccessibilityEvent;
+import android.view.KeyEvent;
 
 /**
  * Top-level interface to an accessibility service component.
@@ -35,4 +36,6 @@
     void onGesture(int gesture);
 
     void clearAccessibilityNodeInfoCache();
+
+    void onKeyEvent(in KeyEvent event, int sequence);
 }
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 7a29f35..3df06b5 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -31,144 +31,31 @@
 
     void setServiceInfo(in AccessibilityServiceInfo info);
 
-    /**
-     * Finds an {@link android.view.accessibility.AccessibilityNodeInfo} by accessibility id.
-     *
-     * @param accessibilityWindowId A unique window id. Use
-     *     {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
-     *     to query the currently active window.
-     * @param accessibilityNodeId A unique view id or virtual descendant id from
-     *     where to start the search. Use
-     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
-     *     to start from the root.
-     * @param interactionId The id of the interaction for matching with the callback result.
-     * @param callback Callback which to receive the result.
-     * @param flags Additional flags.
-     * @param threadId The id of the calling thread.
-     * @return Whether the call succeeded.
-     */
     boolean findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
         long accessibilityNodeId, int interactionId,
         IAccessibilityInteractionConnectionCallback callback, int flags, long threadId);
 
-    /**
-     * Finds {@link android.view.accessibility.AccessibilityNodeInfo}s by View text.
-     * The match is case insensitive containment. The search is performed in the window
-     * whose id is specified and starts from the node whose accessibility id is specified.
-     *
-     * @param accessibilityWindowId A unique window id. Use
-     *     {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
-     *     to query the currently active window.
-     * @param accessibilityNodeId A unique view id or virtual descendant id from
-     *     where to start the search. Use
-     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
-     *     to start from the root.
-     * @param text The searched text.
-     * @param interactionId The id of the interaction for matching with the callback result.
-     * @param callback Callback which to receive the result.
-     * @param threadId The id of the calling thread.
-     * @return Whether the call succeeded.
-     */
     boolean findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId,
         String text, int interactionId, IAccessibilityInteractionConnectionCallback callback,
         long threadId);
 
-    /**
-     * Finds an {@link android.view.accessibility.AccessibilityNodeInfo} by View id. The search
-     * is performed in the window whose id is specified and starts from the node whose
-     * accessibility id is specified.
-     *
-     * @param accessibilityWindowId A unique window id. Use
-     *     {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
-     *     to query the currently active window.
-     * @param accessibilityNodeId A unique view id or virtual descendant id from
-     *     where to start the search. Use
-     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
-     *     to start from the root.
-     * @param viewId The fully qualified resource name of the view id to find.
-     * @param interactionId The id of the interaction for matching with the callback result.
-     * @param callback Callback which to receive the result.
-     * @param threadId The id of the calling thread.
-     * @return Whether the call succeeded.
-     */
     boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
         long accessibilityNodeId, String viewId, int interactionId,
         IAccessibilityInteractionConnectionCallback callback, long threadId);
 
-    /**
-     * Finds the {@link android.view.accessibility.AccessibilityNodeInfo} that has the specified
-     * focus type. The search is performed in the window whose id is specified and starts from
-     * the node whose accessibility id is specified.
-     *
-     * @param accessibilityWindowId A unique window id. Use
-     *     {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
-     *     to query the currently active window.
-     * @param accessibilityNodeId A unique view id or virtual descendant id from
-     *     where to start the search. Use
-     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
-     *     to start from the root.
-     * @param focusType The type of focus to find.
-     * @param interactionId The id of the interaction for matching with the callback result.
-     * @param callback Callback which to receive the result.
-     * @param threadId The id of the calling thread.
-     * @return Whether the call succeeded.
-     */
     boolean findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType,
         int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId);
 
-    /**
-     * Finds an {@link android.view.accessibility.AccessibilityNodeInfo} to take accessibility
-     * focus in the given direction. The search is performed in the window whose id is
-     * specified and starts from the node whose accessibility id is specified.
-     *
-     * @param accessibilityWindowId A unique window id. Use
-     *     {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
-     *     to query the currently active window.
-     * @param accessibilityNodeId A unique view id or virtual descendant id from
-     *     where to start the search. Use
-     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
-     *     to start from the root.
-     * @param direction The direction in which to search for focusable.
-     * @param interactionId The id of the interaction for matching with the callback result.
-     * @param callback Callback which to receive the result.
-     * @param threadId The id of the calling thread.
-     * @return Whether the call succeeded.
-     */
     boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction,
         int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId);
 
-    /**
-     * Performs an accessibility action on an
-     * {@link android.view.accessibility.AccessibilityNodeInfo}.
-     *
-     * @param accessibilityWindowId A unique window id. Use
-     *     {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
-     *     to query the currently active window.
-     * @param accessibilityNodeId A unique view id or virtual descendant id from
-     *     where to start the search. Use
-     *     {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
-     *     to start from the root.
-     * @param action The action to perform.
-     * @param arguments Optional action arguments.
-     * @param interactionId The id of the interaction for matching with the callback result.
-     * @param callback Callback which to receive the result.
-     * @param threadId The id of the calling thread.
-     * @return Whether the action was performed.
-     */
     boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId,
         int action, in Bundle arguments, int interactionId,
         IAccessibilityInteractionConnectionCallback callback, long threadId);
 
-    /**
-     * @return The associated accessibility service info.
-     */
     AccessibilityServiceInfo getServiceInfo();
 
-    /**
-     * Performs a global action, such as going home, going back, etc.
-     *
-     * @param action The action to perform.
-     * @return Whether the action was performed.
-     */
     boolean performGlobalAction(int action);
+
+    oneway void setOnKeyEventResult(boolean handled, int sequence);
 }
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 7d02342..d9799b6 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -31,6 +31,7 @@
 import android.util.Log;
 import android.view.Display;
 import android.view.InputEvent;
+import android.view.KeyEvent;
 import android.view.Surface;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityInteractionClient;
@@ -693,6 +694,11 @@
                         listener.onAccessibilityEvent(AccessibilityEvent.obtain(event));
                     }
                 }
+
+                @Override
+                public boolean onKeyEvent(KeyEvent event) {
+                    return false;
+                }
             });
         }
     }
diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 179db12..0d8a571 100644
--- a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -25,6 +25,7 @@
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.InputFilter;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.WindowManagerPolicy;
 import android.view.accessibility.AccessibilityEvent;
@@ -80,7 +81,7 @@
 
     private final Choreographer mChoreographer;
 
-    private int mCurrentDeviceId;
+    private int mCurrentTouchDeviceId;
 
     private boolean mInstalled;
 
@@ -98,6 +99,8 @@
 
     private boolean mHoverEventSequenceStarted;
 
+    private boolean mKeyEventSequenceStarted;
+
     AccessibilityInputFilter(Context context, AccessibilityManagerService service) {
         super(context.getMainLooper());
         mContext = context;
@@ -133,11 +136,21 @@
             Slog.d(TAG, "Received event: " + event + ", policyFlags=0x" 
                     + Integer.toHexString(policyFlags));
         }
-        if (mEventHandler == null) {
+        if (event instanceof MotionEvent
+                && event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)) {
+            MotionEvent motionEvent = (MotionEvent) event;
+            onMotionEvent(motionEvent, policyFlags);
+        } else if (event instanceof KeyEvent
+                && event.isFromSource(InputDevice.SOURCE_KEYBOARD)) {
+            KeyEvent keyEvent = (KeyEvent) event;
+            onKeyEvent(keyEvent, policyFlags);
+        } else {
             super.onInputEvent(event, policyFlags);
-            return;
         }
-        if (event.getSource() != InputDevice.SOURCE_TOUCHSCREEN) {
+    }
+
+    private void onMotionEvent(MotionEvent event, int policyFlags) {
+        if (mEventHandler == null) {
             super.onInputEvent(event, policyFlags);
             return;
         }
@@ -149,26 +162,25 @@
             return;
         }
         final int deviceId = event.getDeviceId();
-        if (mCurrentDeviceId != deviceId) {
+        if (mCurrentTouchDeviceId != deviceId) {
+            mCurrentTouchDeviceId = deviceId;
             mMotionEventSequenceStarted = false;
             mHoverEventSequenceStarted = false;
             mEventHandler.clear();
-            mCurrentDeviceId = deviceId;
         }
-        if (mCurrentDeviceId < 0) {
+        if (mCurrentTouchDeviceId < 0) {
             super.onInputEvent(event, policyFlags);
             return;
         }
         // We do not handle scroll events.
-        MotionEvent motionEvent = (MotionEvent) event;
-        if (motionEvent.getActionMasked() == MotionEvent.ACTION_SCROLL) {
+        if (event.getActionMasked() == MotionEvent.ACTION_SCROLL) {
             super.onInputEvent(event, policyFlags);
             return;
         }
         // Wait for a down touch event to start processing.
-        if (motionEvent.isTouchEvent()) {
+        if (event.isTouchEvent()) {
             if (!mMotionEventSequenceStarted) {
-                if (motionEvent.getActionMasked() != MotionEvent.ACTION_DOWN) {
+                if (event.getActionMasked() != MotionEvent.ACTION_DOWN) {
                     return;
                 }
                 mMotionEventSequenceStarted = true;
@@ -176,7 +188,7 @@
         } else {
             // Wait for an enter hover event to start processing.
             if (!mHoverEventSequenceStarted) {
-                if (motionEvent.getActionMasked() != MotionEvent.ACTION_HOVER_ENTER) {
+                if (event.getActionMasked() != MotionEvent.ACTION_HOVER_ENTER) {
                     return;
                 }
                 mHoverEventSequenceStarted = true;
@@ -185,6 +197,22 @@
         batchMotionEvent((MotionEvent) event, policyFlags);
     }
 
+    private void onKeyEvent(KeyEvent event, int policyFlags) {
+        if ((policyFlags & WindowManagerPolicy.FLAG_PASS_TO_USER) == 0) {
+            mKeyEventSequenceStarted = false;
+            super.onInputEvent(event, policyFlags);
+            return;
+        }
+        // Wait for a down key event to start processing.
+        if (!mKeyEventSequenceStarted) {
+            if (event.getAction() != KeyEvent.ACTION_DOWN) {
+                return;
+            }
+            mKeyEventSequenceStarted = true;
+        }
+        mAms.notifyKeyEvent(event, policyFlags);
+    }
+
     private void scheduleProcessBatchedEvents() {
         mChoreographer.postCallback(Choreographer.CALLBACK_INPUT,
                 mProcessBatchedEventsRunnable, null);
@@ -286,6 +314,13 @@
         }
     }
 
+    void reset() {
+        setEnabledFeatures(0);
+        mKeyEventSequenceStarted = false;
+        mMotionEventSequenceStarted = false;
+        mHoverEventSequenceStarted = false;
+    }
+
     private void enableFeatures() {
         mMotionEventSequenceStarted = false;
         mHoverEventSequenceStarted = false;
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index 527e891..110c4da 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -61,16 +61,20 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.text.TextUtils.SimpleStringSplitter;
+import android.util.Pools.Pool;
+import android.util.Pools.SimplePool;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.IWindow;
 import android.view.IWindowManager;
 import android.view.InputDevice;
+import android.view.InputEventConsistencyVerifier;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.MagnificationSpec;
 import android.view.WindowManager;
+import android.view.WindowManagerPolicy;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityInteractionClient;
 import android.view.accessibility.AccessibilityManager;
@@ -132,6 +136,8 @@
 
     private static final int OWN_PROCESS_ID = android.os.Process.myPid();
 
+    private static final int MAX_POOL_SIZE = 10;
+
     private static int sIdCounter = 0;
 
     private static int sNextWindowId;
@@ -140,6 +146,9 @@
 
     private final Object mLock = new Object();
 
+    private final Pool<PendingEvent> mPendingEventPool =
+            new SimplePool<PendingEvent>(MAX_POOL_SIZE);
+
     private final SimpleStringSplitter mStringColonSplitter =
             new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
 
@@ -633,6 +642,17 @@
         }
     }
 
+    boolean notifyKeyEvent(KeyEvent event, int policyFlags) {
+        synchronized (mLock) {
+            KeyEvent localClone = KeyEvent.obtain(event);
+            boolean handled = notifyKeyEventLocked(localClone, policyFlags, false);
+            if (!handled) {
+                handled = notifyKeyEventLocked(localClone, policyFlags, true);
+            }
+            return handled;
+        }
+    }
+
     /**
      * Gets the bounds of the accessibility focus in the active window.
      *
@@ -798,6 +818,27 @@
         return false;
     }
 
+    private boolean notifyKeyEventLocked(KeyEvent event, int policyFlags, boolean isDefault) {
+        // TODO: Now we are giving the key events to the last enabled
+        //       service that can handle them which is the last one
+        //       in our list since we write the last enabled as the
+        //       last record in the enabled services setting. Ideally,
+        //       the user should make the call which service handles
+        //       key events. However, only one service should handle
+        //       key events to avoid user frustration when different
+        //       behavior is observed from different combinations of
+        //       enabled accessibility services.
+        UserState state = getCurrentUserStateLocked();
+        for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+            Service service = state.mBoundServices.get(i);
+            if (service.mIsDefault == isDefault) {
+                service.notifyKeyEvent(event, policyFlags);
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void notifyClearAccessibilityNodeInfoCacheLocked() {
         UserState state = getCurrentUserStateLocked();
         for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
@@ -1119,8 +1160,7 @@
         boolean setInputFilter = false;
         AccessibilityInputFilter inputFilter = null;
         synchronized (mLock) {
-            if ((userState.mIsAccessibilityEnabled && userState.mIsTouchExplorationEnabled)
-                    || userState.mIsDisplayMagnificationEnabled) {
+            if (userState.mIsAccessibilityEnabled) {
                 if (!mHasInputFilter) {
                     mHasInputFilter = true;
                     if (mInputFilter == null) {
@@ -1141,7 +1181,7 @@
             } else {
                 if (mHasInputFilter) {
                     mHasInputFilter = false;
-                    mInputFilter.setEnabledFeatures(0);
+                    mInputFilter.reset();
                     inputFilter = null;
                     setInputFilter = true;
                 }
@@ -1446,6 +1486,7 @@
         public static final int MSG_ANNOUNCE_NEW_USER_IF_NEEDED = 5;
         public static final int MSG_UPDATE_INPUT_FILTER = 6;
         public static final int MSG_SHOW_ENABLED_TOUCH_EXPLORATION_DIALOG = 7;
+        public static final int MSG_SEND_KEY_EVENT_TO_INPUT_FILTER = 8;
 
         public MainHandler(Looper looper) {
             super(looper);
@@ -1464,6 +1505,16 @@
                     }
                     event.recycle();
                 } break;
+                case MSG_SEND_KEY_EVENT_TO_INPUT_FILTER: {
+                    KeyEvent event = (KeyEvent) msg.obj;
+                    final int policyFlags = msg.arg1;
+                    synchronized (mLock) {
+                        if (mHasInputFilter && mInputFilter != null) {
+                            mInputFilter.sendInputEvent(event, policyFlags);
+                        }
+                    }
+                    event.recycle();
+                } break;
                 case MSG_SEND_STATE_TO_CLIENTS: {
                     final int clientState = msg.arg1;
                     final int userId = msg.arg2;
@@ -1536,6 +1587,22 @@
         }
     }
 
+    private PendingEvent obtainPendingEventLocked(KeyEvent event, int policyFlags, int sequence) {
+        PendingEvent pendingEvent = mPendingEventPool.acquire();
+        if (pendingEvent == null) {
+            pendingEvent = new PendingEvent();
+        }
+        pendingEvent.event = event;
+        pendingEvent.policyFlags = policyFlags;
+        pendingEvent.sequence = sequence;
+        return pendingEvent;
+    }
+
+    private void recyclePendingEventLocked(PendingEvent pendingEvent) {
+        pendingEvent.clear();
+        mPendingEventPool.release(pendingEvent);
+    }
+
     /**
      * This class represents an accessibility service. It stores all per service
      * data required for the service management, provides API for starting/stopping the
@@ -1545,12 +1612,7 @@
      * connection for the service.
      */
     class Service extends IAccessibilityServiceConnection.Stub
-            implements ServiceConnection, DeathRecipient {
-
-        // We pick the MSBs to avoid collision since accessibility event types are
-        // used as message types allowing us to remove messages per event type. 
-        private static final int MSG_ON_GESTURE = 0x80000000;
-        private static final int MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE = 0x40000000;
+            implements ServiceConnection, DeathRecipient {;
 
         final int mUserId;
 
@@ -1594,29 +1656,22 @@
         final SparseArray<AccessibilityEvent> mPendingEvents =
             new SparseArray<AccessibilityEvent>();
 
-        /**
-         * Handler for delayed event dispatch.
-         */
-        public Handler mHandler = new Handler(mMainHandler.getLooper()) {
+        final KeyEventDispatcher mKeyEventDispatcher = new KeyEventDispatcher();
+
+        // Handler only for dispatching accessibility events since we use event
+        // types as message types allowing us to remove messages per event type.
+        public Handler mEventDispatchHandler = new Handler(mMainHandler.getLooper()) {
             @Override
             public void handleMessage(Message message) {
-                final int type = message.what;
-                switch (type) {
-                    case MSG_ON_GESTURE: {
-                        final int gestureId = message.arg1;
-                        notifyGestureInternal(gestureId);
-                    } break;
-                    case MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE: {
-                        notifyClearAccessibilityNodeInfoCacheInternal();
-                    } break;
-                    default: {
-                        final int eventType = type;
-                        notifyAccessibilityEventInternal(eventType);
-                    } break;
-                }
+                final int eventType =  message.what;
+                notifyAccessibilityEventInternal(eventType);
             }
         };
 
+        // Handler for scheduling method invocations on the main thread.
+        public InvocationHandler mInvocationHandler = new InvocationHandler(
+                mMainHandler.getLooper());
+
         public Service(int userId, ComponentName componentName,
                 AccessibilityServiceInfo accessibilityServiceInfo) {
             mUserId = userId;
@@ -1703,6 +1758,7 @@
                 return false;
             }
             UserState userState = getUserStateLocked(mUserId);
+            mKeyEventDispatcher.flush();
             if (!mIsAutomation) {
                 mContext.unbindService(this);
             } else {
@@ -1718,6 +1774,11 @@
         }
 
         @Override
+        public void setOnKeyEventResult(boolean handled, int sequence) {
+            mKeyEventDispatcher.setOnKeyEventResult(handled, sequence);
+        }
+
+        @Override
         public AccessibilityServiceInfo getServiceInfo() {
             synchronized (mLock) {
                 return mAccessibilityServiceInfo;
@@ -2109,6 +2170,7 @@
 
         public void binderDied() {
             synchronized (mLock) {
+                mKeyEventDispatcher.flush();
                 UserState userState = getUserStateLocked(mUserId);
                 // The death recipient is unregistered in removeServiceLocked
                 removeServiceLocked(this, userState);
@@ -2141,12 +2203,12 @@
 
                 final int what = eventType;
                 if (oldEvent != null) {
-                    mHandler.removeMessages(what);
+                    mEventDispatchHandler.removeMessages(what);
                     oldEvent.recycle();
                 }
 
-                Message message = mHandler.obtainMessage(what);
-                mHandler.sendMessageDelayed(message, mNotificationTimeout);
+                Message message = mEventDispatchHandler.obtainMessage(what);
+                mEventDispatchHandler.sendMessageDelayed(message, mNotificationTimeout);
             }
         }
 
@@ -2211,11 +2273,18 @@
         }
 
         public void notifyGesture(int gestureId) {
-            mHandler.obtainMessage(MSG_ON_GESTURE, gestureId, 0).sendToTarget();
+            mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_GESTURE,
+                    gestureId, 0).sendToTarget();
+        }
+
+        public void notifyKeyEvent(KeyEvent event, int policyFlags) {
+            mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_KEY_EVENT,
+                    policyFlags, 0, event).sendToTarget();
         }
 
         public void notifyClearAccessibilityNodeInfoCache() {
-            mHandler.sendEmptyMessage(MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE);
+            mInvocationHandler.sendEmptyMessage(
+                    InvocationHandler.MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE);
         }
 
         private void notifyGestureInternal(int gestureId) {
@@ -2230,6 +2299,10 @@
             }
         }
 
+        private void notifyKeyEventInternal(KeyEvent event, int policyFlags) {
+            mKeyEventDispatcher.notifyKeyEvent(event, policyFlags);
+        }
+
         private void notifyClearAccessibilityNodeInfoCacheInternal() {
             IAccessibilityServiceClient listener = mServiceInterface;
             if (listener != null) {
@@ -2339,6 +2412,177 @@
             }
             return null;
         }
+
+        private final class InvocationHandler extends Handler {
+
+            public static final int MSG_ON_GESTURE = 1;
+            public static final int MSG_ON_KEY_EVENT = 2;
+            public static final int MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE = 3;
+            public static final int MSG_ON_KEY_EVENT_TIMEOUT = 4;
+
+            public InvocationHandler(Looper looper) {
+                super(looper, null, true);
+            }
+
+            @Override
+            public void handleMessage(Message message) {
+                final int type = message.what;
+                switch (type) {
+                    case MSG_ON_GESTURE: {
+                        final int gestureId = message.arg1;
+                        notifyGestureInternal(gestureId);
+                    } break;
+                    case MSG_ON_KEY_EVENT: {
+                        KeyEvent event = (KeyEvent) message.obj;
+                        final int policyFlags = message.arg1;
+                        notifyKeyEventInternal(event, policyFlags);
+                    } break;
+                    case MSG_CLEAR_ACCESSIBILITY_NODE_INFO_CACHE: {
+                        notifyClearAccessibilityNodeInfoCacheInternal();
+                    } break;
+                    case MSG_ON_KEY_EVENT_TIMEOUT: {
+                        PendingEvent eventState = (PendingEvent) message.obj;
+                        setOnKeyEventResult(false, eventState.sequence);
+                    } break;
+                    default: {
+                        throw new IllegalArgumentException("Unknown message: " + type);
+                    }
+                }
+            }
+        }
+
+        private final class KeyEventDispatcher {
+
+            private static final long ON_KEY_EVENT_TIMEOUT_MILLIS = 500;
+
+            private PendingEvent mPendingEvents;
+
+            private final InputEventConsistencyVerifier mSentEventsVerifier =
+                    InputEventConsistencyVerifier.isInstrumentationEnabled()
+                            ? new InputEventConsistencyVerifier(
+                                    this, 0, KeyEventDispatcher.class.getSimpleName()) : null;
+
+            public void notifyKeyEvent(KeyEvent event, int policyFlags) {
+                final PendingEvent pendingEvent;
+
+                synchronized (mLock) {
+                    pendingEvent = addPendingEventLocked(event, policyFlags);
+                }
+
+                Message message = mInvocationHandler.obtainMessage(
+                        InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT, pendingEvent);
+                mInvocationHandler.sendMessageDelayed(message, ON_KEY_EVENT_TIMEOUT_MILLIS);
+
+                try {
+                    // Accessibility services are exclusively not in the system
+                    // process, therefore no need to clone the motion event to
+                    // prevent tampering. It will be cloned in the IPC call.
+                    mServiceInterface.onKeyEvent(pendingEvent.event, pendingEvent.sequence);
+                } catch (RemoteException re) {
+                    setOnKeyEventResult(false, pendingEvent.sequence);
+                }
+            }
+
+            public void setOnKeyEventResult(boolean handled, int sequence) {
+                synchronized (mLock) {
+                    PendingEvent pendingEvent = removePendingEventLocked(sequence);
+                    if (pendingEvent != null) {
+                        mInvocationHandler.removeMessages(
+                                InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT,
+                                pendingEvent);
+                        pendingEvent.handled = handled;
+                        finishPendingEventLocked(pendingEvent);
+                    }
+                }
+            }
+
+            public void flush() {
+                synchronized (mLock) {
+                    cancelAllPendingEventsLocked();
+                    mSentEventsVerifier.reset();
+                }
+            }
+
+            private PendingEvent addPendingEventLocked(KeyEvent event, int policyFlags) {
+                final int sequence = event.getSequenceNumber();
+                PendingEvent pendingEvent = obtainPendingEventLocked(event, policyFlags, sequence);
+                pendingEvent.next = mPendingEvents;
+                mPendingEvents = pendingEvent;
+                return pendingEvent;
+            }
+
+            private PendingEvent removePendingEventLocked(int sequence) {
+                PendingEvent previous = null;
+                PendingEvent current = mPendingEvents;
+
+                while (current != null) {
+                    if (current.sequence == sequence) {
+                        if (previous != null) {
+                            previous.next = current.next;
+                        } else {
+                            mPendingEvents = current.next;
+                        }
+                        current.next = null;
+                        return current;
+                    }
+                    previous = current;
+                    current = current.next;
+                }
+                return null;
+            }
+
+            private void finishPendingEventLocked(PendingEvent pendingEvent) {
+                if (!pendingEvent.handled) {
+                    sendKeyEventToInputFilter(pendingEvent.event, pendingEvent.policyFlags);
+                }
+                // Nullify the event since we do not want it to be
+                // recycled yet. It will be sent to the input filter.
+                pendingEvent.event = null;
+                recyclePendingEventLocked(pendingEvent);
+            }
+
+            private void sendKeyEventToInputFilter(KeyEvent event, int policyFlags) {
+                if (DEBUG) {
+                    Slog.i(LOG_TAG, "Injecting event: " + event);
+                }
+                if (mSentEventsVerifier != null) {
+                    mSentEventsVerifier.onKeyEvent(event, 0);
+                }
+                policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
+                mMainHandler.obtainMessage(MainHandler.MSG_SEND_KEY_EVENT_TO_INPUT_FILTER,
+                        policyFlags, 0, event).sendToTarget();
+            }
+
+            private void cancelAllPendingEventsLocked() {
+                while (mPendingEvents != null) {
+                    PendingEvent pendingEvent = removePendingEventLocked(mPendingEvents.sequence);
+                    pendingEvent.handled = false;
+                    mInvocationHandler.removeMessages(InvocationHandler.MSG_ON_KEY_EVENT_TIMEOUT,
+                            pendingEvent);
+                    finishPendingEventLocked(pendingEvent);
+                }
+            }
+        }
+    }
+
+    private static final class PendingEvent {
+        PendingEvent next;
+
+        KeyEvent event;
+        int policyFlags;
+        int sequence;
+        boolean handled;
+
+        public void clear() {
+            if (event != null) {
+                event.recycle();
+                event = null;
+            }
+            next = null;
+            policyFlags = 0;
+            sequence = 0;
+            handled = false;
+        }
     }
 
     final class SecurityPolicy {
diff --git a/services/java/com/android/server/accessibility/EventStreamTransformation.java b/services/java/com/android/server/accessibility/EventStreamTransformation.java
index 3289a15..8c93e7b 100644
--- a/services/java/com/android/server/accessibility/EventStreamTransformation.java
+++ b/services/java/com/android/server/accessibility/EventStreamTransformation.java
@@ -57,7 +57,7 @@
 interface EventStreamTransformation {
 
     /**
-     * Receives motion event. Passed are the event transformed by previous
+     * Receives a motion event. Passed are the event transformed by previous
      * transformations and the raw event to which no transformations have
      * been applied.
      *