Merge "Add input filter mechanism for accessibility."
diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java
index f6aeb39..03189ca 100755
--- a/core/java/android/view/InputEvent.java
+++ b/core/java/android/view/InputEvent.java
@@ -67,6 +67,15 @@
      */
     public abstract void setSource(int source);
 
+    /**
+     * Recycles the event.
+     * This method should only be used by the system since applications do not
+     * expect {@link KeyEvent} objects to be recycled, although {@link MotionEvent}
+     * objects are fine.  See {@link KeyEvent#recycle()} for details.
+     * @hide
+     */
+    public abstract void recycle();
+
     public int describeContents() {
         return 0;
     }
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 334c68e..9d00d02 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -81,6 +81,8 @@
 
     public final static int FLAG_INJECTED = 0x01000000;
     public final static int FLAG_TRUSTED = 0x02000000;
+    public final static int FLAG_FILTERED = 0x04000000;
+    public final static int FLAG_DISABLE_KEY_REPEAT = 0x08000000;
 
     public final static int FLAG_WOKE_HERE = 0x10000000;
     public final static int FLAG_BRIGHT_HERE = 0x20000000;
diff --git a/include/ui/Input.h b/include/ui/Input.h
index 8e8b61b..b22986d 100644
--- a/include/ui/Input.h
+++ b/include/ui/Input.h
@@ -128,6 +128,12 @@
     // input device or an application with system-wide event injection permission.
     POLICY_FLAG_TRUSTED = 0x02000000,
 
+    // Indicates that the input event has passed through an input filter.
+    POLICY_FLAG_FILTERED = 0x04000000,
+
+    // Disables automatic key repeating behavior.
+    POLICY_FLAG_DISABLE_KEY_REPEAT = 0x08000000,
+
     /* These flags are set by the input reader policy as it intercepts each event. */
 
     // Indicates that the screen was off when the event was received and the event
diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp
index 6ea068a..8363e8b 100644
--- a/services/input/InputDispatcher.cpp
+++ b/services/input/InputDispatcher.cpp
@@ -186,7 +186,7 @@
     mPolicy(policy),
     mPendingEvent(NULL), mAppSwitchSawKeyDown(false), mAppSwitchDueTime(LONG_LONG_MAX),
     mNextUnblockedEvent(NULL),
-    mDispatchEnabled(true), mDispatchFrozen(false),
+    mDispatchEnabled(true), mDispatchFrozen(false), mInputFilterEnabled(false),
     mFocusedWindow(NULL),
     mFocusedApplication(NULL),
     mCurrentInputTargetsValid(false),
@@ -725,7 +725,7 @@
         if (entry->repeatCount == 0
                 && entry->action == AKEY_EVENT_ACTION_DOWN
                 && (entry->policyFlags & POLICY_FLAG_TRUSTED)
-                && !entry->isInjected()) {
+                && (!(entry->policyFlags & POLICY_FLAG_DISABLE_KEY_REPEAT))) {
             if (mKeyRepeatState.lastKeyEntry
                     && mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) {
                 // We have seen two identical key downs in a row which indicates that the device
@@ -2402,7 +2402,18 @@
 
     bool needWake;
     { // acquire lock
-        AutoMutex _l(mLock);
+        mLock.lock();
+
+        if (mInputFilterEnabled) {
+            mLock.unlock();
+
+            policyFlags |= POLICY_FLAG_FILTERED;
+            if (!mPolicy->filterInputEvent(&event, policyFlags)) {
+                return; // event was consumed by the filter
+            }
+
+            mLock.lock();
+        }
 
         int32_t repeatCount = 0;
         KeyEntry* newEntry = mAllocator.obtainKeyEntry(eventTime,
@@ -2410,6 +2421,7 @@
                 metaState, repeatCount, downTime);
 
         needWake = enqueueInboundEventLocked(newEntry);
+        mLock.unlock();
     } // release lock
 
     if (needWake) {
@@ -2452,7 +2464,23 @@
 
     bool needWake;
     { // acquire lock
-        AutoMutex _l(mLock);
+        mLock.lock();
+
+        if (mInputFilterEnabled) {
+            mLock.unlock();
+
+            MotionEvent event;
+            event.initialize(deviceId, source, action, flags, edgeFlags, metaState, 0, 0,
+                    xPrecision, yPrecision, downTime, eventTime,
+                    pointerCount, pointerIds, pointerCoords);
+
+            policyFlags |= POLICY_FLAG_FILTERED;
+            if (!mPolicy->filterInputEvent(&event, policyFlags)) {
+                return; // event was consumed by the filter
+            }
+
+            mLock.lock();
+        }
 
         // Attempt batching and streaming of move events.
         if (action == AMOTION_EVENT_ACTION_MOVE
@@ -2491,6 +2519,7 @@
                 LOGD("Appended motion sample onto batch for most recent "
                         "motion event for this device in the inbound queue.");
 #endif
+                mLock.unlock();
                 return; // done!
             }
 
@@ -2579,6 +2608,7 @@
                             true /*resumeWithAppendedMotionSample*/);
 
                     runCommandsLockedInterruptible();
+                    mLock.unlock();
                     return; // done!
                 }
             }
@@ -2593,6 +2623,7 @@
                 pointerCount, pointerIds, pointerCoords);
 
         needWake = enqueueInboundEventLocked(newEntry);
+        mLock.unlock();
     } // release lock
 
     if (needWake) {
@@ -2612,16 +2643,17 @@
 }
 
 int32_t InputDispatcher::injectInputEvent(const InputEvent* event,
-        int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) {
+        int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis,
+        uint32_t policyFlags) {
 #if DEBUG_INBOUND_EVENT_DETAILS
     LOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, "
-            "syncMode=%d, timeoutMillis=%d",
-            event->getType(), injectorPid, injectorUid, syncMode, timeoutMillis);
+            "syncMode=%d, timeoutMillis=%d, policyFlags=0x%08x",
+            event->getType(), injectorPid, injectorUid, syncMode, timeoutMillis, policyFlags);
 #endif
 
     nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis);
 
-    uint32_t policyFlags = POLICY_FLAG_INJECTED;
+    policyFlags |= POLICY_FLAG_INJECTED;
     if (hasInjectionPermission(injectorPid, injectorUid)) {
         policyFlags |= POLICY_FLAG_TRUSTED;
     }
@@ -2640,7 +2672,9 @@
             policyFlags |= POLICY_FLAG_VIRTUAL;
         }
 
-        mPolicy->interceptKeyBeforeQueueing(keyEvent, /*byref*/ policyFlags);
+        if (!(policyFlags & POLICY_FLAG_FILTERED)) {
+            mPolicy->interceptKeyBeforeQueueing(keyEvent, /*byref*/ policyFlags);
+        }
 
         if (policyFlags & POLICY_FLAG_WOKE_HERE) {
             flags |= AKEY_EVENT_FLAG_WOKE_HERE;
@@ -2664,8 +2698,10 @@
             return INPUT_EVENT_INJECTION_FAILED;
         }
 
-        nsecs_t eventTime = motionEvent->getEventTime();
-        mPolicy->interceptMotionBeforeQueueing(eventTime, /*byref*/ policyFlags);
+        if (!(policyFlags & POLICY_FLAG_FILTERED)) {
+            nsecs_t eventTime = motionEvent->getEventTime();
+            mPolicy->interceptMotionBeforeQueueing(eventTime, /*byref*/ policyFlags);
+        }
 
         mLock.lock();
         const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes();
@@ -2780,7 +2816,8 @@
                  injectionResult, injectionState->injectorPid, injectionState->injectorUid);
 #endif
 
-        if (injectionState->injectionIsAsync) {
+        if (injectionState->injectionIsAsync
+                && !(entry->policyFlags & POLICY_FLAG_FILTERED)) {
             // Log the outcome since the injector did not wait for the injection result.
             switch (injectionResult) {
             case INPUT_EVENT_INJECTION_SUCCEEDED:
@@ -2982,6 +3019,26 @@
     }
 }
 
+void InputDispatcher::setInputFilterEnabled(bool enabled) {
+#if DEBUG_FOCUS
+    LOGD("setInputFilterEnabled: enabled=%d", enabled);
+#endif
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        if (mInputFilterEnabled == enabled) {
+            return;
+        }
+
+        mInputFilterEnabled = enabled;
+        resetAndDropEverythingLocked("input filter is being enabled or disabled");
+    } // release lock
+
+    // Wake up poll loop since there might be work to do to drop everything.
+    mLooper->wake();
+}
+
 bool InputDispatcher::transferTouchFocus(const sp<InputChannel>& fromChannel,
         const sp<InputChannel>& toChannel) {
 #if DEBUG_FOCUS
diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h
index 48e4d43..162e606 100644
--- a/services/input/InputDispatcher.h
+++ b/services/input/InputDispatcher.h
@@ -176,6 +176,13 @@
      */
     virtual int32_t getMaxEventsPerSecond() = 0;
 
+    /* Filters an input event.
+     * Return true to dispatch the event unmodified, false to consume the event.
+     * A filter can also transform and inject events later by passing POLICY_FLAG_FILTERED
+     * to injectInputEvent.
+     */
+    virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) = 0;
+
     /* Intercepts a key event immediately before queueing it.
      * The policy can use this method as an opportunity to perform power management functions
      * and early event preprocessing such as updating policy flags.
@@ -266,7 +273,8 @@
      * This method may be called on any thread (usually by the input manager).
      */
     virtual int32_t injectInputEvent(const InputEvent* event,
-            int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) = 0;
+            int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis,
+            uint32_t policyFlags) = 0;
 
     /* Sets the list of input windows.
      *
@@ -286,6 +294,14 @@
      */
     virtual void setInputDispatchMode(bool enabled, bool frozen) = 0;
 
+    /* Sets whether input event filtering is enabled.
+     * When enabled, incoming input events are sent to the policy's filterInputEvent
+     * method instead of being dispatched.  The filter is expected to use
+     * injectInputEvent to inject the events it would like to have dispatched.
+     * It should include POLICY_FLAG_FILTERED in the policy flags during injection.
+     */
+    virtual void setInputFilterEnabled(bool enabled) = 0;
+
     /* Transfers touch focus from the window associated with one channel to the
      * window associated with the other channel.
      *
@@ -345,11 +361,13 @@
             int32_t switchCode, int32_t switchValue, uint32_t policyFlags) ;
 
     virtual int32_t injectInputEvent(const InputEvent* event,
-            int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis);
+            int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis,
+            uint32_t policyFlags);
 
     virtual void setInputWindows(const Vector<InputWindow>& inputWindows);
     virtual void setFocusedApplication(const InputApplication* inputApplication);
     virtual void setInputDispatchMode(bool enabled, bool frozen);
+    virtual void setInputFilterEnabled(bool enabled);
 
     virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
             const sp<InputChannel>& toChannel);
@@ -863,6 +881,7 @@
     // Dispatch state.
     bool mDispatchEnabled;
     bool mDispatchFrozen;
+    bool mInputFilterEnabled;
 
     Vector<InputWindow> mWindows;
 
diff --git a/services/input/tests/InputDispatcher_test.cpp b/services/input/tests/InputDispatcher_test.cpp
index 2f846c4..3650da0 100644
--- a/services/input/tests/InputDispatcher_test.cpp
+++ b/services/input/tests/InputDispatcher_test.cpp
@@ -67,6 +67,10 @@
         return 60;
     }
 
+    virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) {
+        return true;
+    }
+
     virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) {
     }
 
@@ -124,7 +128,7 @@
             /*action*/ -1, 0,
             AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME);
     ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
-            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
             << "Should reject key events with undefined action.";
 
     // Rejects ACTION_MULTIPLE since it is not supported despite being defined in the API.
@@ -132,7 +136,7 @@
             AKEY_EVENT_ACTION_MULTIPLE, 0,
             AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME);
     ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
-            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
             << "Should reject key events with ACTION_MULTIPLE.";
 }
 
@@ -150,7 +154,7 @@
             ARBITRARY_TIME, ARBITRARY_TIME,
             /*pointerCount*/ 1, pointerIds, pointerCoords);
     ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
-            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
             << "Should reject motion events with undefined action.";
 
     // Rejects pointer down with invalid index.
@@ -160,7 +164,7 @@
             ARBITRARY_TIME, ARBITRARY_TIME,
             /*pointerCount*/ 1, pointerIds, pointerCoords);
     ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
-            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
             << "Should reject motion events with pointer down index too large.";
 
     event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
@@ -169,7 +173,7 @@
             ARBITRARY_TIME, ARBITRARY_TIME,
             /*pointerCount*/ 1, pointerIds, pointerCoords);
     ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
-            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
             << "Should reject motion events with pointer down index too small.";
 
     // Rejects pointer up with invalid index.
@@ -179,7 +183,7 @@
             ARBITRARY_TIME, ARBITRARY_TIME,
             /*pointerCount*/ 1, pointerIds, pointerCoords);
     ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
-            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
             << "Should reject motion events with pointer up index too large.";
 
     event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
@@ -188,7 +192,7 @@
             ARBITRARY_TIME, ARBITRARY_TIME,
             /*pointerCount*/ 1, pointerIds, pointerCoords);
     ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
-            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
             << "Should reject motion events with pointer up index too small.";
 
     // Rejects motion events with invalid number of pointers.
@@ -197,7 +201,7 @@
             ARBITRARY_TIME, ARBITRARY_TIME,
             /*pointerCount*/ 0, pointerIds, pointerCoords);
     ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
-            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
             << "Should reject motion events with 0 pointers.";
 
     event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
@@ -205,7 +209,7 @@
             ARBITRARY_TIME, ARBITRARY_TIME,
             /*pointerCount*/ MAX_POINTERS + 1, pointerIds, pointerCoords);
     ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
-            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
             << "Should reject motion events with more than MAX_POINTERS pointers.";
 
     // Rejects motion events with invalid pointer ids.
@@ -215,7 +219,7 @@
             ARBITRARY_TIME, ARBITRARY_TIME,
             /*pointerCount*/ 1, pointerIds, pointerCoords);
     ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
-            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
             << "Should reject motion events with pointer ids less than 0.";
 
     pointerIds[0] = MAX_POINTER_ID + 1;
@@ -224,7 +228,7 @@
             ARBITRARY_TIME, ARBITRARY_TIME,
             /*pointerCount*/ 1, pointerIds, pointerCoords);
     ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
-            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
             << "Should reject motion events with pointer ids greater than MAX_POINTER_ID.";
 
     // Rejects motion events with duplicate pointer ids.
@@ -235,7 +239,7 @@
             ARBITRARY_TIME, ARBITRARY_TIME,
             /*pointerCount*/ 2, pointerIds, pointerCoords);
     ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(&event,
-            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0))
+            INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
             << "Should reject motion events with duplicate pointer ids.";
 }
 
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
index 60549c6..4c5f239 100644
--- a/services/input/tests/InputReader_test.cpp
+++ b/services/input/tests/InputReader_test.cpp
@@ -369,7 +369,8 @@
     }
 
     virtual int32_t injectInputEvent(const InputEvent* event,
-            int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) {
+            int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis,
+            uint32_t policyFlags) {
         ADD_FAILURE() << "Should never be called by input reader.";
         return INPUT_EVENT_INJECTION_FAILED;
     }
@@ -386,6 +387,10 @@
         ADD_FAILURE() << "Should never be called by input reader.";
     }
 
+    virtual void setInputFilterEnabled(bool enabled) {
+        ADD_FAILURE() << "Should never be called by input reader.";
+    }
+
     virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
             const sp<InputChannel>& toChannel) {
         ADD_FAILURE() << "Should never be called by input reader.";
diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
new file mode 100644
index 0000000..ced8feb
--- /dev/null
+++ b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -0,0 +1,86 @@
+/*
+ * 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.server.accessibility;
+
+import com.android.server.wm.InputFilter;
+
+import android.content.Context;
+import android.util.Slog;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.WindowManagerPolicy;
+
+/**
+ * Input filter for accessibility.
+ *
+ * Currently just a stub but will eventually implement touch exploration, etc.
+ */
+public class AccessibilityInputFilter extends InputFilter {
+    private static final String TAG = "AccessibilityInputFilter";
+    private static final boolean DEBUG = true;
+
+    private final Context mContext;
+
+    public AccessibilityInputFilter(Context context) {
+        super(context.getMainLooper());
+        mContext = context;
+    }
+
+    @Override
+    public void onInstalled() {
+        if (DEBUG) {
+            Slog.d(TAG, "Accessibility input filter installed.");
+        }
+        super.onInstalled();
+    }
+
+    @Override
+    public void onUninstalled() {
+        if (DEBUG) {
+            Slog.d(TAG, "Accessibility input filter uninstalled.");
+        }
+        super.onUninstalled();
+    }
+
+    @Override
+    public void onInputEvent(InputEvent event, int policyFlags) {
+        if (DEBUG) {
+            Slog.d(TAG, "Accessibility input filter received input event: "
+                    + event + ", policyFlags=0x" + Integer.toHexString(policyFlags));
+        }
+
+        // To prove that this is working as intended, we will silently transform
+        // Q key presses into non-repeating Z's as part of this stub implementation.
+        // TODO: Replace with the real thing.
+        if (event instanceof KeyEvent) {
+            final KeyEvent keyEvent = (KeyEvent)event;
+            if (keyEvent.getKeyCode() == KeyEvent.KEYCODE_Q) {
+                if (keyEvent.getRepeatCount() == 0) {
+                    sendInputEvent(new KeyEvent(keyEvent.getDownTime(), keyEvent.getEventTime(),
+                            keyEvent.getAction(), KeyEvent.KEYCODE_Z, keyEvent.getRepeatCount(),
+                            keyEvent.getMetaState(), keyEvent.getDeviceId(), keyEvent.getScanCode(),
+                            keyEvent.getFlags(), keyEvent.getSource()),
+                            policyFlags | WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
+                }
+                return;
+            }
+        }
+
+        super.onInputEvent(event, policyFlags);
+    }
+}
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index ba74d86..5257fb0 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -19,6 +19,7 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.HandlerCaller.SomeArgs;
+import com.android.server.wm.WindowManagerService;
 
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
@@ -43,6 +44,7 @@
 import android.os.IBinder;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.text.TextUtils.SimpleStringSplitter;
@@ -106,6 +108,7 @@
     private int mHandledFeedbackTypes = 0;
 
     private boolean mIsEnabled;
+    private AccessibilityInputFilter mInputFilter;
 
     /**
      * Handler for delayed event dispatch.
@@ -209,6 +212,7 @@
                         }
 
                         manageServicesLocked();
+                        updateInputFilterLocked();
                     }
                     
                     return;
@@ -249,6 +253,7 @@
                             unbindAllServicesLocked();
                         }
                         updateClientsLocked();
+                        updateInputFilterLocked();
                     }
                 }
             });
@@ -621,6 +626,25 @@
     }
 
     /**
+     * Installs or removes the accessibility input filter when accessibility is enabled
+     * or disabled.
+     */
+    private void updateInputFilterLocked() {
+        WindowManagerService wm = (WindowManagerService)ServiceManager.getService(
+                Context.WINDOW_SERVICE);
+        if (wm != null) {
+            if (mIsEnabled) {
+                if (mInputFilter == null) {
+                    mInputFilter = new AccessibilityInputFilter(mContext);
+                }
+                wm.setInputFilter(mInputFilter);
+            } else {
+                wm.setInputFilter(null);
+            }
+        }
+    }
+
+    /**
      * This class represents an accessibility service. It stores all per service
      * data required for the service management, provides API for starting/stopping the
      * service and is responsible for adding/removing the service in the data structures
diff --git a/services/java/com/android/server/wm/InputFilter.java b/services/java/com/android/server/wm/InputFilter.java
new file mode 100644
index 0000000..78b87fe
--- /dev/null
+++ b/services/java/com/android/server/wm/InputFilter.java
@@ -0,0 +1,231 @@
+/*
+ * 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.server.wm;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.WindowManagerPolicy;
+
+/**
+ * Filters input events before they are dispatched to the system.
+ * <p>
+ * At most one input filter can be installed by calling
+ * {@link WindowManagerService#setInputFilter}.  When an input filter is installed, the
+ * system's behavior changes as follows:
+ * <ul>
+ * <li>Input events are first delivered to the {@link WindowManagerPolicy}
+ * interception methods before queueing as usual.  This critical step takes care of managing
+ * the power state of the device and handling wake keys.</li>
+ * <li>Input events are then asynchronously delivered to the input filter's
+ * {@link #onInputEvent(InputEvent)} method instead of being enqueued for dispatch to
+ * applications as usual.  The input filter only receives input events that were
+ * generated by input device; the input filter will not receive input events that were
+ * injected into the system by other means, such as by instrumentation.</li>
+ * <li>The input filter processes and optionally transforms the stream of events.  For example,
+ * it may transform a sequence of motion events representing an accessibility gesture into
+ * a different sequence of motion events, key presses or other system-level interactions.
+ * The input filter can send events to be dispatched by calling
+ * {@link #sendInputEvent(InputEvent)} and passing appropriate policy flags for the
+ * input event.</li>
+ * </ul>
+ * </p>
+ * <h3>The importance of input event consistency</h3>
+ * <p>
+ * The input filter mechanism is very low-level.  At a minimum, it needs to ensure that it
+ * sends an internally consistent stream of input events to the dispatcher.  There are
+ * very important invariants to be maintained.
+ * </p><p>
+ * For example, if a key down is sent, a corresponding key up should also be sent eventually.
+ * Likewise, for touch events, each pointer must individually go down with
+ * {@link MotionEvent#ACTION_DOWN} or {@link MotionEvent#ACTION_POINTER_DOWN} and then
+ * individually go up with {@link MotionEvent#ACTION_POINTER_UP} or {@link MotionEvent#ACTION_UP}
+ * and the sequence of pointer ids used must be consistent throughout the gesture.
+ * </p><p>
+ * Sometimes a filter may wish to cancel a previously dispatched key or motion.  It should
+ * use {@link KeyEvent#FLAG_CANCELED} or {@link MotionEvent#ACTION_CANCEL} accordingly.
+ * </p><p>
+ * The input filter must take into account the fact that the input events coming from different
+ * devices or even different sources all consist of distinct streams of input.
+ * Use {@link InputEvent#getDeviceId()} and {@link InputEvent#getSource()} to identify
+ * the source of the event and its semantics.  There are be multiple sources of keys,
+ * touches and other input: they must be kept separate.
+ * </p>
+ * <h3>Policy flags</h3>
+ * <p>
+ * Input events received from the dispatcher and sent to the dispatcher have policy flags
+ * associated with them.  Policy flags control some functions of the dispatcher.
+ * </p><p>
+ * The early policy interception decides whether an input event should be delivered
+ * to applications or dropped.  The policy indicates its decision by setting the
+ * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} policy flag.  The input filter may
+ * sometimes receive events that do not have this flag set.  It should take note of
+ * the fact that the policy intends to drop the event, clean up its state, and
+ * then send appropriate cancelation events to the dispatcher if needed.
+ * </p><p>
+ * For example, suppose the input filter is processing a gesture and one of the touch events
+ * it receives does not have the {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag set.
+ * The input filter should clear its internal state about the gesture and then send key or
+ * motion events to the dispatcher to cancel any keys or pointers that are down.
+ * </p><p>
+ * Corollary: Events that set sent to the dispatcher should usually include the
+ * {@link WindowManagerPolicy#FLAG_PASS_TO_USER} flag.  Otherwise, they will be dropped!
+ * </p><p>
+ * It may be prudent to disable automatic key repeating for synthetically generated
+ * keys by setting the {@link WindowManagerPolicy#FLAG_DISABLE_KEY_REPEAT} policy flag.
+ * </p>
+ */
+public abstract class InputFilter {
+    private static final int MSG_INSTALL = 1;
+    private static final int MSG_UNINSTALL = 2;
+    private static final int MSG_INPUT_EVENT = 3;
+
+    private final H mH;
+    private Host mHost;
+
+    /**
+     * Creates the input filter.
+     *
+     * @param looper The looper to run callbacks on.
+     */
+    public InputFilter(Looper looper) {
+        mH = new H(looper);
+    }
+
+    /**
+     * Called when the input filter is installed.
+     * This method is guaranteed to be non-reentrant.
+     *
+     * @param host The input filter host environment.
+     */
+    final void install(Host host) {
+        mH.obtainMessage(MSG_INSTALL, host).sendToTarget();
+    }
+
+    /**
+     * Called when the input filter is uninstalled.
+     * This method is guaranteed to be non-reentrant.
+     */
+    final void uninstall() {
+        mH.obtainMessage(MSG_UNINSTALL).sendToTarget();
+    }
+
+    /**
+     * Called to enqueue the input event for filtering.
+     * The event will be recycled after the input filter processes it.
+     * This method is guaranteed to be non-reentrant.
+     *
+     * @param event The input event to enqueue.
+     */
+    final void filterInputEvent(InputEvent event, int policyFlags) {
+        mH.obtainMessage(MSG_INPUT_EVENT, policyFlags, 0, event).sendToTarget();
+    }
+
+    /**
+     * Sends an input event to the dispatcher.
+     *
+     * @param event The input event to publish.
+     * @param policyFlags The input event policy flags.
+     */
+    public void sendInputEvent(InputEvent event, int policyFlags) {
+        if (event == null) {
+            throw new IllegalArgumentException("event must not be null");
+        }
+        if (mHost == null) {
+            throw new IllegalStateException("Cannot send input event because the input filter " +
+                    "is not installed.");
+        }
+        mHost.sendInputEvent(event, policyFlags);
+    }
+
+    /**
+     * Called when an input event has been received from the dispatcher.
+     * <p>
+     * The default implementation sends the input event back to the dispatcher, unchanged.
+     * </p><p>
+     * The event will be recycled when this method returns.  If you want to keep it around,
+     * make a copy!
+     * </p>
+     *
+     * @param event The input event that was received.
+     * @param policyFlags The input event policy flags.
+     */
+    public void onInputEvent(InputEvent event, int policyFlags) {
+        sendInputEvent(event, policyFlags);
+    }
+
+    /**
+     * Called when the filter is installed into the dispatch pipeline.
+     * <p>
+     * This method is called before the input filter receives any input events.
+     * The input filter should take this opportunity to prepare itself.
+     * </p>
+     */
+    public void onInstalled() {
+    }
+
+    /**
+     * Called when the filter is uninstalled from the dispatch pipeline.
+     * <p>
+     * This method is called after the input filter receives its last input event.
+     * The input filter should take this opportunity to clean up.
+     * </p>
+     */
+    public void onUninstalled() {
+    }
+
+    private final class H extends Handler {
+        public H(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_INSTALL:
+                    mHost = (Host)msg.obj;
+                    onInstalled();
+                    break;
+
+                case MSG_UNINSTALL:
+                    try {
+                        onUninstalled();
+                    } finally {
+                        mHost = null;
+                    }
+                    break;
+
+                case MSG_INPUT_EVENT: {
+                    final InputEvent event = (InputEvent)msg.obj;
+                    try {
+                        onInputEvent(event, msg.arg1);
+                    } finally {
+                        event.recycle();
+                    }
+                    break;
+                }
+            }
+        }
+    }
+
+    interface Host {
+        public void sendInputEvent(InputEvent event, int policyFlags);
+    }
+}
diff --git a/services/java/com/android/server/wm/InputManager.java b/services/java/com/android/server/wm/InputManager.java
index ca1da95..b0978a3 100644
--- a/services/java/com/android/server/wm/InputManager.java
+++ b/services/java/com/android/server/wm/InputManager.java
@@ -42,6 +42,7 @@
 import android.view.Surface;
 import android.view.ViewConfiguration;
 import android.view.WindowManager;
+import android.view.WindowManagerPolicy;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -78,8 +79,10 @@
     private static native void nativeRegisterInputChannel(InputChannel inputChannel,
             InputWindowHandle inputWindowHandle, boolean monitor);
     private static native void nativeUnregisterInputChannel(InputChannel inputChannel);
+    private static native void nativeSetInputFilterEnabled(boolean enable);
     private static native int nativeInjectInputEvent(InputEvent event,
-            int injectorPid, int injectorUid, int syncMode, int timeoutMillis);
+            int injectorPid, int injectorUid, int syncMode, int timeoutMillis,
+            int policyFlags);
     private static native void nativeSetInputWindows(InputWindow[] windows);
     private static native void nativeSetInputDispatchMode(boolean enabled, boolean frozen);
     private static native void nativeSetSystemUiVisibility(int visibility);
@@ -117,6 +120,11 @@
     /** The key is down but is a virtual key press that is being emulated by the system. */
     public static final int KEY_STATE_VIRTUAL = 2;
 
+    // State for the currently installed input filter.
+    final Object mInputFilterLock = new Object();
+    InputFilter mInputFilter;
+    InputFilterHost mInputFilterHost;
+
     public InputManager(Context context, WindowManagerService windowManagerService) {
         this.mContext = context;
         this.mWindowManagerService = windowManagerService;
@@ -268,7 +276,42 @@
         
         nativeUnregisterInputChannel(inputChannel);
     }
-    
+
+    /**
+     * Sets an input filter that will receive all input events before they are dispatched.
+     * The input filter may then reinterpret input events or inject new ones.
+     *
+     * To ensure consistency, the input dispatcher automatically drops all events
+     * in progress whenever an input filter is installed or uninstalled.  After an input
+     * filter is uninstalled, it can no longer send input events unless it is reinstalled.
+     * Any events it attempts to send after it has been uninstalled will be dropped.
+     *
+     * @param filter The input filter, or null to remove the current filter.
+     */
+    public void setInputFilter(InputFilter filter) {
+        synchronized (mInputFilterLock) {
+            final InputFilter oldFilter = mInputFilter;
+            if (oldFilter == filter) {
+                return; // nothing to do
+            }
+
+            if (oldFilter != null) {
+                mInputFilter = null;
+                mInputFilterHost.disconnectLocked();
+                mInputFilterHost = null;
+                oldFilter.uninstall();
+            }
+
+            if (filter != null) {
+                mInputFilter = filter;
+                mInputFilterHost = new InputFilterHost();
+                filter.install(mInputFilterHost);
+            }
+
+            nativeSetInputFilterEnabled(filter != null);
+        }
+    }
+
     /**
      * Injects an input event into the event system on behalf of an application.
      * The synchronization mode determines whether the method blocks while waiting for
@@ -304,9 +347,10 @@
             throw new IllegalArgumentException("timeoutMillis must be positive");
         }
 
-        return nativeInjectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis);
+        return nativeInjectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis,
+                WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
     }
-    
+
     /**
      * Gets information about the input device with the specified id.
      * @param id The device id.
@@ -370,6 +414,27 @@
         }
     }
 
+    private final class InputFilterHost implements InputFilter.Host {
+        private boolean mDisconnected;
+
+        public void disconnectLocked() {
+            mDisconnected = true;
+        }
+
+        public void sendInputEvent(InputEvent event, int policyFlags) {
+            if (event == null) {
+                throw new IllegalArgumentException("event must not be null");
+            }
+
+            synchronized (mInputFilterLock) {
+                if (!mDisconnected) {
+                    nativeInjectInputEvent(event, 0, 0, INPUT_EVENT_INJECTION_SYNC_NONE, 0,
+                            policyFlags | WindowManagerPolicy.FLAG_FILTERED);
+                }
+            }
+        }
+    }
+
     private static final class PointerIcon {
         public Bitmap bitmap;
         public float hotSpotX;
@@ -415,7 +480,7 @@
     /*
      * Callbacks from native.
      */
-    private class Callbacks {
+    private final class Callbacks {
         static final String TAG = "InputManager-Callbacks";
         
         private static final boolean DEBUG_VIRTUAL_KEYS = false;
@@ -443,7 +508,19 @@
             return mWindowManagerService.mInputMonitor.notifyANR(
                     inputApplicationHandle, inputWindowHandle);
         }
-        
+
+        @SuppressWarnings("unused")
+        final boolean filterInputEvent(InputEvent event, int policyFlags) {
+            synchronized (mInputFilterLock) {
+                if (mInputFilter != null) {
+                    mInputFilter.filterInputEvent(event, policyFlags);
+                    return false;
+                }
+            }
+            event.recycle();
+            return true;
+        }
+
         @SuppressWarnings("unused")
         public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) {
             return mWindowManagerService.mInputMonitor.interceptKeyBeforeQueueing(
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 33e6a36..79c4518 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -4604,6 +4604,10 @@
         return mInputManager.monitorInput(inputChannelName);
     }
 
+    public void setInputFilter(InputFilter filter) {
+        mInputManager.setInputFilter(filter);
+    }
+
     public InputDevice getInputDevice(int deviceId) {
         return mInputManager.getInputDevice(deviceId);
     }
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index 96cf4bd..ab2c125 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -56,6 +56,7 @@
     jmethodID notifyLidSwitchChanged;
     jmethodID notifyInputChannelBroken;
     jmethodID notifyANR;
+    jmethodID filterInputEvent;
     jmethodID interceptKeyBeforeQueueing;
     jmethodID interceptMotionBeforeQueueingWhenScreenOff;
     jmethodID interceptKeyBeforeDispatching;
@@ -174,6 +175,7 @@
     virtual nsecs_t getKeyRepeatTimeout();
     virtual nsecs_t getKeyRepeatDelay();
     virtual int32_t getMaxEventsPerSecond();
+    virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags);
     virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags);
     virtual void interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags);
     virtual bool interceptKeyBeforeDispatching(const sp<InputWindowHandle>& inputWindowHandle,
@@ -638,6 +640,38 @@
     return android_server_PowerManagerService_isScreenBright();
 }
 
+bool NativeInputManager::filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) {
+    jobject inputEventObj;
+
+    JNIEnv* env = jniEnv();
+    switch (inputEvent->getType()) {
+    case AINPUT_EVENT_TYPE_KEY:
+        inputEventObj = android_view_KeyEvent_fromNative(env,
+                static_cast<const KeyEvent*>(inputEvent));
+        break;
+    case AINPUT_EVENT_TYPE_MOTION:
+        inputEventObj = android_view_MotionEvent_obtainAsCopy(env,
+                static_cast<const MotionEvent*>(inputEvent));
+        break;
+    default:
+        return true; // dispatch the event normally
+    }
+
+    if (!inputEventObj) {
+        LOGE("Failed to obtain input event object for filterInputEvent.");
+        return true; // dispatch the event normally
+    }
+
+    // The callee is responsible for recycling the event.
+    jboolean pass = env->CallBooleanMethod(mCallbacksObj, gCallbacksClassInfo.filterInputEvent,
+            inputEventObj, policyFlags);
+    if (checkAndClearExceptionFromCallback(env, "filterInputEvent")) {
+        pass = true;
+    }
+    env->DeleteLocalRef(inputEventObj);
+    return pass;
+}
+
 void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent,
         uint32_t& policyFlags) {
     // Policy:
@@ -1005,9 +1039,18 @@
     }
 }
 
+static void android_server_InputManager_nativeSetInputFilterEnabled(JNIEnv* env, jclass clazz,
+        jboolean enabled) {
+    if (checkInputManagerUnitialized(env)) {
+        return;
+    }
+
+    gNativeInputManager->getInputManager()->getDispatcher()->setInputFilterEnabled(enabled);
+}
+
 static jint android_server_InputManager_nativeInjectInputEvent(JNIEnv* env, jclass clazz,
         jobject inputEventObj, jint injectorPid, jint injectorUid,
-        jint syncMode, jint timeoutMillis) {
+        jint syncMode, jint timeoutMillis, jint policyFlags) {
     if (checkInputManagerUnitialized(env)) {
         return INPUT_EVENT_INJECTION_FAILED;
     }
@@ -1021,7 +1064,8 @@
         }
 
         return gNativeInputManager->getInputManager()->getDispatcher()->injectInputEvent(
-                & keyEvent, injectorPid, injectorUid, syncMode, timeoutMillis);
+                & keyEvent, injectorPid, injectorUid, syncMode, timeoutMillis,
+                uint32_t(policyFlags));
     } else if (env->IsInstanceOf(inputEventObj, gMotionEventClassInfo.clazz)) {
         const MotionEvent* motionEvent = android_view_MotionEvent_getNativePtr(env, inputEventObj);
         if (!motionEvent) {
@@ -1030,7 +1074,8 @@
         }
 
         return gNativeInputManager->getInputManager()->getDispatcher()->injectInputEvent(
-                motionEvent, injectorPid, injectorUid, syncMode, timeoutMillis);
+                motionEvent, injectorPid, injectorUid, syncMode, timeoutMillis,
+                uint32_t(policyFlags));
     } else {
         jniThrowRuntimeException(env, "Invalid input event type.");
         return INPUT_EVENT_INJECTION_FAILED;
@@ -1200,7 +1245,9 @@
             (void*) android_server_InputManager_nativeRegisterInputChannel },
     { "nativeUnregisterInputChannel", "(Landroid/view/InputChannel;)V",
             (void*) android_server_InputManager_nativeUnregisterInputChannel },
-    { "nativeInjectInputEvent", "(Landroid/view/InputEvent;IIII)I",
+    { "nativeSetInputFilterEnabled", "(Z)V",
+            (void*) android_server_InputManager_nativeSetInputFilterEnabled },
+    { "nativeInjectInputEvent", "(Landroid/view/InputEvent;IIIII)I",
             (void*) android_server_InputManager_nativeInjectInputEvent },
     { "nativeSetInputWindows", "([Lcom/android/server/wm/InputWindow;)V",
             (void*) android_server_InputManager_nativeSetInputWindows },
@@ -1257,6 +1304,9 @@
             "notifyANR",
             "(Lcom/android/server/wm/InputApplicationHandle;Lcom/android/server/wm/InputWindowHandle;)J");
 
+    GET_METHOD_ID(gCallbacksClassInfo.filterInputEvent, clazz,
+            "filterInputEvent", "(Landroid/view/InputEvent;I)Z");
+
     GET_METHOD_ID(gCallbacksClassInfo.interceptKeyBeforeQueueing, clazz,
             "interceptKeyBeforeQueueing", "(Landroid/view/KeyEvent;IZ)I");