More native input event dispatching.

Added ANRs handling.
Added event injection.
Fixed a NPE ActivityManagerServer writing ANRs to the drop box.
Fixed HOME key interception.
Fixed trackball reporting.
Fixed pointer rotation in landscape mode.

Change-Id: I50340f559f22899ab924e220a78119ffc79469b7
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index df30c76..adb11c8 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -341,11 +341,4 @@
         }
     }
     */
-
-    void poke()
-    {
-        synchronized (this) {
-            nativeWake();
-        }
-    }
 }
diff --git a/core/java/android/view/InputChannel.java b/core/java/android/view/InputChannel.java
index 66a83b8..e5ebc69 100644
--- a/core/java/android/view/InputChannel.java
+++ b/core/java/android/view/InputChannel.java
@@ -26,7 +26,7 @@
  * to the ViewRoot through a Binder transaction as part of registering the Window.
  * @hide
  */
-public class InputChannel implements Parcelable {
+public final class InputChannel implements Parcelable {
     private static final String TAG = "InputChannel";
     
     public static final Parcelable.Creator<InputChannel> CREATOR
diff --git a/core/java/android/view/InputTarget.java b/core/java/android/view/InputTarget.java
index e56e03c..6ff7305 100644
--- a/core/java/android/view/InputTarget.java
+++ b/core/java/android/view/InputTarget.java
@@ -25,7 +25,7 @@
  * These parameters are used by the native input dispatching code.
  * @hide
  */
-public class InputTarget {
+public final class InputTarget {
     public InputChannel mInputChannel;
     public int mFlags;
     public long mTimeoutNanos;
diff --git a/include/ui/Input.h b/include/ui/Input.h
index 92ff872..979d6e8 100644
--- a/include/ui/Input.h
+++ b/include/ui/Input.h
@@ -58,8 +58,6 @@
 /*
  * Flags that flow alongside events in the input dispatch system to help with certain
  * policy decisions such as waking from device sleep.
- *
- * TODO This enumeration should probably be split up or relabeled for clarity.
  */
 enum {
     /* These flags originate in RawEvents and are generally set in the key map. */
@@ -73,6 +71,8 @@
     POLICY_FLAG_MENU = 0x00000040,
     POLICY_FLAG_LAUNCHER = 0x00000080,
 
+    POLICY_FLAG_RAW_MASK = 0x0000ffff,
+
     /* 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/include/ui/InputDispatcher.h b/include/ui/InputDispatcher.h
index 80a20c9..511ad20 100644
--- a/include/ui/InputDispatcher.h
+++ b/include/ui/InputDispatcher.h
@@ -35,6 +35,28 @@
 namespace android {
 
 /*
+ * Constants used to report the outcome of input event injection.
+ */
+enum {
+    /* (INTERNAL USE ONLY) Specifies that injection is pending and its outcome is unknown. */
+    INPUT_EVENT_INJECTION_PENDING = -1,
+
+    /* Injection succeeded. */
+    INPUT_EVENT_INJECTION_SUCCEEDED = 0,
+
+    /* Injection failed because the injector did not have permission to inject
+     * into the application with input focus. */
+    INPUT_EVENT_INJECTION_PERMISSION_DENIED = 1,
+
+    /* Injection failed because there were no available input targets. */
+    INPUT_EVENT_INJECTION_FAILED = 2,
+
+    /* Injection failed due to a timeout. */
+    INPUT_EVENT_INJECTION_TIMED_OUT = 3
+};
+
+
+/*
  * An input target specifies how an input event is to be dispatched to a particular window
  * including the window's input channel, control flags, a timeout, and an X / Y offset to
  * be added to input event coordinates to compensate for the absolute position of the
@@ -70,6 +92,7 @@
     float xOffset, yOffset;
 };
 
+
 /*
  * Input dispatcher policy interface.
  *
@@ -91,8 +114,11 @@
     /* Notifies the system that an input channel is unrecoverably broken. */
     virtual void notifyInputChannelBroken(const sp<InputChannel>& inputChannel) = 0;
 
-    /* Notifies the system that an input channel is not responding. */
-    virtual void notifyInputChannelANR(const sp<InputChannel>& inputChannel) = 0;
+    /* Notifies the system that an input channel is not responding.
+     * Returns true and a new timeout value if the dispatcher should keep waiting.
+     * Otherwise returns false. */
+    virtual bool notifyInputChannelANR(const sp<InputChannel>& inputChannel,
+            nsecs_t& outNewTimeout) = 0;
 
     /* Notifies the system that an input channel recovered from ANR. */
     virtual void notifyInputChannelRecoveredFromANR(const sp<InputChannel>& inputChannel) = 0;
@@ -100,12 +126,22 @@
     /* Gets the key repeat timeout or -1 if automatic key repeating is disabled. */
     virtual nsecs_t getKeyRepeatTimeout() = 0;
 
-    /* Gets the input targets for a key event. */
-    virtual void getKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags,
+    /* Gets the input targets for a key event.
+     * If the event is being injected, injectorPid and injectorUid should specify the
+     * process id and used id of the injecting application, otherwise they should both
+     * be -1.
+     * Returns one of the INPUT_EVENT_INJECTION_XXX constants. */
+    virtual int32_t getKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags,
+            int32_t injectorPid, int32_t injectorUid,
             Vector<InputTarget>& outTargets) = 0;
 
-    /* Gets the input targets for a motion event. */
-    virtual void getMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
+    /* Gets the input targets for a motion event.
+     * If the event is being injected, injectorPid and injectorUid should specify the
+     * process id and used id of the injecting application, otherwise they should both
+     * be -1.
+     * Returns one of the INPUT_EVENT_INJECTION_XXX constants. */
+    virtual int32_t getMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
+            int32_t injectorPid, int32_t injectorUid,
             Vector<InputTarget>& outTargets) = 0;
 };
 
@@ -139,6 +175,17 @@
             uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
             float xPrecision, float yPrecision, nsecs_t downTime) = 0;
 
+    /* Injects an input event and optionally waits for sync.
+     * This method may block even if sync is false because it must wait for previous events
+     * to be dispatched before it can determine whether input event injection will be
+     * permitted based on the current input focus.
+     * Returns one of the INPUT_EVENT_INJECTION_XXX constants.
+     *
+     * 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, bool sync, int32_t timeoutMillis) = 0;
+
     /* Registers or unregister input channels that may be used as targets for input events.
      *
      * These methods may be called on any thread (usually by the input manager).
@@ -183,6 +230,9 @@
             uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
             float xPrecision, float yPrecision, nsecs_t downTime);
 
+    virtual int32_t injectInputEvent(const InputEvent* event,
+            int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis);
+
     virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel);
     virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel);
 
@@ -205,7 +255,13 @@
         int32_t type;
         nsecs_t eventTime;
 
+        int32_t injectionResult; // initially INPUT_EVENT_INJECTION_PENDING
+        int32_t injectorPid;     // -1 if not injected
+        int32_t injectorUid;     // -1 if not injected
+
         bool dispatchInProgress; // initially false, set to true while dispatching
+
+        inline bool isInjected() { return injectorPid >= 0; }
     };
 
     struct ConfigurationChangedEntry : EventEntry {
@@ -293,6 +349,7 @@
     struct CommandEntry;
     typedef void (InputDispatcher::*Command)(CommandEntry* commandEntry);
 
+    class Connection;
     struct CommandEntry : Link<CommandEntry> {
         CommandEntry();
         ~CommandEntry();
@@ -300,7 +357,7 @@
         Command command;
 
         // parameters for the command (usage varies by command)
-        sp<InputChannel> inputChannel;
+        sp<Connection> connection;
     };
 
     // Generic queue implementation.
@@ -353,9 +410,16 @@
     public:
         Allocator();
 
-        ConfigurationChangedEntry* obtainConfigurationChangedEntry();
-        KeyEntry* obtainKeyEntry();
-        MotionEntry* obtainMotionEntry();
+        ConfigurationChangedEntry* obtainConfigurationChangedEntry(nsecs_t eventTime);
+        KeyEntry* obtainKeyEntry(nsecs_t eventTime,
+                int32_t deviceId, int32_t nature, uint32_t policyFlags, int32_t action,
+                int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
+                int32_t repeatCount, nsecs_t downTime);
+        MotionEntry* obtainMotionEntry(nsecs_t eventTime,
+                int32_t deviceId, int32_t nature, uint32_t policyFlags, int32_t action,
+                int32_t metaState, int32_t edgeFlags, float xPrecision, float yPrecision,
+                nsecs_t downTime, uint32_t pointerCount,
+                const int32_t* pointerIds, const PointerCoords* pointerCoords);
         DispatchEntry* obtainDispatchEntry(EventEntry* eventEntry);
         CommandEntry* obtainCommandEntry(Command command);
 
@@ -367,7 +431,7 @@
         void releaseCommandEntry(CommandEntry* entry);
 
         void appendMotionSample(MotionEntry* motionEntry,
-                nsecs_t eventTime, int32_t pointerCount, const PointerCoords* pointerCoords);
+                nsecs_t eventTime, const PointerCoords* pointerCoords);
 
     private:
         Pool<ConfigurationChangedEntry> mConfigurationChangeEntryPool;
@@ -376,6 +440,8 @@
         Pool<MotionSample> mMotionSamplePool;
         Pool<DispatchEntry> mDispatchEntryPool;
         Pool<CommandEntry> mCommandEntryPool;
+
+        void initializeEventEntry(EventEntry* entry, int32_t type, nsecs_t eventTime);
     };
 
     /* Manages the dispatch state associated with a single input channel. */
@@ -439,6 +505,8 @@
         }
 
         status_t initialize();
+
+        void setNextTimeoutTime(nsecs_t currentTime, nsecs_t timeout);
     };
 
     sp<InputDispatcherPolicyInterface> mPolicy;
@@ -455,8 +523,17 @@
     KeyedVector<int, sp<Connection> > mConnectionsByReceiveFd;
 
     // Active connections are connections that have a non-empty outbound queue.
+    // We don't use a ref-counted pointer here because we explicitly abort connections
+    // during unregistration which causes the connection's outbound queue to be cleared
+    // and the connection itself to be deactivated.
     Vector<Connection*> mActiveConnections;
 
+    // List of connections that have timed out.  Only used by dispatchOnce()
+    // We don't use a ref-counted pointer here because it is not possible for a connection
+    // to be unregistered while processing timed out connections since we hold the lock for
+    // the duration.
+    Vector<Connection*> mTimedOutConnections;
+
     // Preallocated key and motion event objects used only to ask the input dispatcher policy
     // for the targets of an event that is to be dispatched.
     KeyEvent mReusableKeyEvent;
@@ -468,6 +545,13 @@
     Vector<InputTarget> mCurrentInputTargets;
     bool mCurrentInputTargetsValid; // false while targets are being recomputed
 
+    // Event injection and synchronization.
+    Condition mInjectionResultAvailableCondition;
+    Condition mFullySynchronizedCondition;
+    bool isFullySynchronizedLocked();
+    EventEntry* createEntryFromInputEventLocked(const InputEvent* event);
+    void setInjectionResultLocked(EventEntry* entry, int32_t injectionResult);
+
     // Key repeat tracking.
     // XXX Move this up to the input reader instead.
     struct KeyRepeatState {
@@ -500,13 +584,18 @@
             nsecs_t currentTime, EventEntry* entry, bool resumeWithAppendedMotionSample);
 
     // Manage the dispatch cycle for a single connection.
-    void prepareDispatchCycleLocked(nsecs_t currentTime, Connection* connection,
+    // These methods are deliberately not Interruptible because doing all of the work
+    // with the mutex held makes it easier to ensure that connection invariants are maintained.
+    // If needed, the methods post commands to run later once the critical bits are done.
+    void prepareDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
             EventEntry* eventEntry, const InputTarget* inputTarget,
             bool resumeWithAppendedMotionSample);
-    void startDispatchCycleLocked(nsecs_t currentTime, Connection* connection);
-    void finishDispatchCycleLocked(nsecs_t currentTime, Connection* connection);
-    bool timeoutDispatchCycleLocked(nsecs_t currentTime, Connection* connection);
-    bool abortDispatchCycleLocked(nsecs_t currentTime, Connection* connection,
+    void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
+    void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
+    void timeoutDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
+    void resumeAfterTimeoutDispatchCycleLocked(nsecs_t currentTime,
+            const sp<Connection>& connection, nsecs_t newTimeout);
+    void abortDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
             bool broken);
     static bool handleReceiveCallback(int receiveFd, int events, void* data);
 
@@ -514,19 +603,17 @@
     void activateConnectionLocked(Connection* connection);
     void deactivateConnectionLocked(Connection* connection);
 
-    // Outbound policy interactions.
-    void doNotifyConfigurationChangedLockedInterruptible(CommandEntry* commandEntry);
-
     // Interesting events that we might like to log or tell the framework about.
     void onDispatchCycleStartedLocked(
-            nsecs_t currentTime, Connection* connection);
+            nsecs_t currentTime, const sp<Connection>& connection);
     void onDispatchCycleFinishedLocked(
-            nsecs_t currentTime, Connection* connection, bool recoveredFromANR);
+            nsecs_t currentTime, const sp<Connection>& connection, bool recoveredFromANR);
     void onDispatchCycleANRLocked(
-            nsecs_t currentTime, Connection* connection);
+            nsecs_t currentTime, const sp<Connection>& connection);
     void onDispatchCycleBrokenLocked(
-            nsecs_t currentTime, Connection* connection);
+            nsecs_t currentTime, const sp<Connection>& connection);
 
+    // Outbound policy interactions.
     void doNotifyInputChannelBrokenLockedInterruptible(CommandEntry* commandEntry);
     void doNotifyInputChannelANRLockedInterruptible(CommandEntry* commandEntry);
     void doNotifyInputChannelRecoveredFromANRLockedInterruptible(CommandEntry* commandEntry);
diff --git a/include/ui/InputManager.h b/include/ui/InputManager.h
index 3872c26..7509dd2 100644
--- a/include/ui/InputManager.h
+++ b/include/ui/InputManager.h
@@ -78,6 +78,15 @@
     /* Unregisters an input channel. */
     virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) = 0;
 
+    /* Injects an input event and optionally waits for sync.
+     * This method may block even if sync is false because it must wait for previous events
+     * to be dispatched before it can determine whether input event injection will be
+     * permitted based on the current input focus.
+     * Returns one of the INPUT_EVENT_INJECTION_XXX constants.
+     */
+    virtual int32_t injectInputEvent(const InputEvent* event,
+            int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis) = 0;
+
     /* Gets input device configuration. */
     virtual void getInputConfiguration(InputConfiguration* outConfiguration) const = 0;
 
@@ -118,6 +127,9 @@
     virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel);
     virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel);
 
+    virtual int32_t injectInputEvent(const InputEvent* event,
+            int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis);
+
     virtual void getInputConfiguration(InputConfiguration* outConfiguration) const;
     virtual int32_t getScanCodeState(int32_t deviceId, int32_t deviceClasses,
             int32_t scanCode) const;
diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp
index 14dcada..2ad3382 100644
--- a/libs/ui/InputDispatcher.cpp
+++ b/libs/ui/InputDispatcher.cpp
@@ -25,6 +25,9 @@
 // Log debug messages about performance statistics.
 #define DEBUG_PERFORMANCE_STATISTICS 1
 
+// Log debug messages about input event injection.
+#define DEBUG_INJECTION 1
+
 #include <cutils/log.h>
 #include <ui/InputDispatcher.h>
 
@@ -43,6 +46,10 @@
             || keyCode == KEYCODE_DPAD_RIGHT;
 }
 
+static inline nsecs_t now() {
+    return systemTime(SYSTEM_TIME_MONOTONIC);
+}
+
 // --- InputDispatcher ---
 
 InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) :
@@ -84,7 +91,7 @@
     nsecs_t nextWakeupTime = LONG_LONG_MAX;
     { // acquire lock
         AutoMutex _l(mLock);
-        currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
+        currentTime = now();
 
         // Reset the key repeat timer whenever we disallow key events, even if the next event
         // is not a key.  This is to ensure that we abort a key repeat if the device is just coming
@@ -94,33 +101,33 @@
             resetKeyRepeatLocked();
         }
 
-        // Process timeouts for all connections and determine if there are any synchronous
-        // event dispatches pending.
+        // Detect and process timeouts for all connections and determine if there are any
+        // synchronous event dispatches pending.  This step is entirely non-interruptible.
         bool hasPendingSyncTarget = false;
-        for (size_t i = 0; i < mActiveConnections.size(); ) {
+        size_t activeConnectionCount = mActiveConnections.size();
+        for (size_t i = 0; i < activeConnectionCount; i++) {
             Connection* connection = mActiveConnections.itemAt(i);
 
-            nsecs_t connectionTimeoutTime  = connection->nextTimeoutTime;
-            if (connectionTimeoutTime <= currentTime) {
-                bool deactivated = timeoutDispatchCycleLocked(currentTime, connection);
-                if (deactivated) {
-                    // Don't increment i because the connection has been removed
-                    // from mActiveConnections (hence, deactivated).
-                    continue;
-                }
-            }
-
-            if (connectionTimeoutTime < nextWakeupTime) {
-                nextWakeupTime = connectionTimeoutTime;
-            }
-
             if (connection->hasPendingSyncTarget()) {
                 hasPendingSyncTarget = true;
             }
 
-            i += 1;
+            nsecs_t connectionTimeoutTime  = connection->nextTimeoutTime;
+            if (connectionTimeoutTime <= currentTime) {
+                mTimedOutConnections.add(connection);
+            } else if (connectionTimeoutTime < nextWakeupTime) {
+                nextWakeupTime = connectionTimeoutTime;
+            }
         }
 
+        size_t timedOutConnectionCount = mTimedOutConnections.size();
+        for (size_t i = 0; i < timedOutConnectionCount; i++) {
+            Connection* connection = mTimedOutConnections.itemAt(i);
+            timeoutDispatchCycleLocked(currentTime, connection);
+            skipPoll = true;
+        }
+        mTimedOutConnections.clear();
+
         // If we don't have a pending sync target, then we can begin delivering a new event.
         // (Otherwise we wait for dispatch to complete for that target.)
         if (! hasPendingSyncTarget) {
@@ -177,6 +184,11 @@
 
         // Run any deferred commands.
         skipPoll |= runCommandsLockedInterruptible();
+
+        // Wake up synchronization waiters, if needed.
+        if (isFullySynchronizedLocked()) {
+            mFullySynchronizedCondition.broadcast();
+        }
     } // release lock
 
     // If we dispatched anything, don't poll just now.  Wait for the next iteration.
@@ -202,6 +214,7 @@
         Command command = commandEntry->command;
         (this->*command)(commandEntry); // commands are implicitly 'LockedInterruptible'
 
+        commandEntry->connection.clear();
         mAllocator.releaseCommandEntry(commandEntry);
     } while (! mCommandQueue.isEmpty());
     return true;
@@ -272,28 +285,23 @@
     // Synthesize a key repeat after the repeat timeout expired.
     // We reuse the previous key entry if otherwise unreferenced.
     KeyEntry* entry = mKeyRepeatState.lastKeyEntry;
+    uint32_t policyFlags = entry->policyFlags & POLICY_FLAG_RAW_MASK;
     if (entry->refCount == 1) {
+        entry->eventTime = currentTime;
+        entry->downTime = currentTime;
+        entry->policyFlags = policyFlags;
         entry->repeatCount += 1;
     } else {
-        KeyEntry* newEntry = mAllocator.obtainKeyEntry();
-        newEntry->deviceId = entry->deviceId;
-        newEntry->nature = entry->nature;
-        newEntry->policyFlags = entry->policyFlags;
-        newEntry->action = entry->action;
-        newEntry->flags = entry->flags;
-        newEntry->keyCode = entry->keyCode;
-        newEntry->scanCode = entry->scanCode;
-        newEntry->metaState = entry->metaState;
-        newEntry->repeatCount = entry->repeatCount + 1;
+        KeyEntry* newEntry = mAllocator.obtainKeyEntry(currentTime,
+                entry->deviceId, entry->nature, policyFlags,
+                entry->action, entry->flags, entry->keyCode, entry->scanCode,
+                entry->metaState, entry->repeatCount + 1, currentTime);
 
         mKeyRepeatState.lastKeyEntry = newEntry;
         mAllocator.releaseKeyEntry(entry);
 
         entry = newEntry;
     }
-    entry->eventTime = currentTime;
-    entry->downTime = currentTime;
-    entry->policyFlags = 0;
 
     mKeyRepeatState.nextRepeatTime = currentTime + keyRepeatTimeout;
 
@@ -358,12 +366,15 @@
             entry->downTime, entry->eventTime);
 
     mCurrentInputTargets.clear();
-    mPolicy->getKeyEventTargets(& mReusableKeyEvent, entry->policyFlags,
+    int32_t injectionResult = mPolicy->getKeyEventTargets(& mReusableKeyEvent,
+            entry->policyFlags, entry->injectorPid, entry->injectorUid,
             mCurrentInputTargets);
 
     mLock.lock();
     mCurrentInputTargetsValid = true;
 
+    setInjectionResultLocked(entry, injectionResult);
+
     dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
 }
 
@@ -384,12 +395,15 @@
             entry->firstSample.pointerCoords);
 
     mCurrentInputTargets.clear();
-    mPolicy->getMotionEventTargets(& mReusableMotionEvent, entry->policyFlags,
+    int32_t injectionResult = mPolicy->getMotionEventTargets(& mReusableMotionEvent,
+            entry->policyFlags, entry->injectorPid, entry->injectorUid,
             mCurrentInputTargets);
 
     mLock.lock();
     mCurrentInputTargetsValid = true;
 
+    setInjectionResultLocked(entry, injectionResult);
+
     dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
 }
 
@@ -410,7 +424,7 @@
                 inputTarget.inputChannel->getReceivePipeFd());
         if (connectionIndex >= 0) {
             sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
-            prepareDispatchCycleLocked(currentTime, connection.get(), eventEntry, & inputTarget,
+            prepareDispatchCycleLocked(currentTime, connection, eventEntry, & inputTarget,
                     resumeWithAppendedMotionSample);
         } else {
             LOGW("Framework requested delivery of an input event to channel '%s' but it "
@@ -420,8 +434,8 @@
     }
 }
 
-void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, Connection* connection,
-        EventEntry* eventEntry, const InputTarget* inputTarget,
+void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
+        const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget,
         bool resumeWithAppendedMotionSample) {
 #if DEBUG_DISPATCH_CYCLE
     LOGD("channel '%s' ~ prepareDispatchCycle - flags=%d, timeout=%lldns, "
@@ -547,12 +561,13 @@
 
     // If the outbound queue was previously empty, start the dispatch cycle going.
     if (wasEmpty) {
-        activateConnectionLocked(connection);
+        activateConnectionLocked(connection.get());
         startDispatchCycleLocked(currentTime, connection);
     }
 }
 
-void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, Connection* connection) {
+void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
+        const sp<Connection>& connection) {
 #if DEBUG_DISPATCH_CYCLE
     LOGD("channel '%s' ~ startDispatchCycle",
             connection->getInputChannelName());
@@ -682,13 +697,14 @@
     connection->lastDispatchTime = currentTime;
 
     nsecs_t timeout = dispatchEntry->timeout;
-    connection->nextTimeoutTime = (timeout >= 0) ? currentTime + timeout : LONG_LONG_MAX;
+    connection->setNextTimeoutTime(currentTime, timeout);
 
     // Notify other system components.
     onDispatchCycleStartedLocked(currentTime, connection);
 }
 
-void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, Connection* connection) {
+void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
+        const sp<Connection>& connection) {
 #if DEBUG_DISPATCH_CYCLE
     LOGD("channel '%s' ~ finishDispatchCycle - %01.1fms since event, "
             "%01.1fms since dispatch",
@@ -756,31 +772,48 @@
     }
 
     // Outbound queue is empty, deactivate the connection.
-    deactivateConnectionLocked(connection);
+    deactivateConnectionLocked(connection.get());
 }
 
-bool InputDispatcher::timeoutDispatchCycleLocked(nsecs_t currentTime, Connection* connection) {
+void InputDispatcher::timeoutDispatchCycleLocked(nsecs_t currentTime,
+        const sp<Connection>& connection) {
 #if DEBUG_DISPATCH_CYCLE
     LOGD("channel '%s' ~ timeoutDispatchCycle",
             connection->getInputChannelName());
 #endif
 
     if (connection->status != Connection::STATUS_NORMAL) {
-        return false;
+        return;
     }
 
     // Enter the not responding state.
     connection->status = Connection::STATUS_NOT_RESPONDING;
     connection->lastANRTime = currentTime;
-    bool deactivated = abortDispatchCycleLocked(currentTime, connection, false /*(not) broken*/);
 
     // Notify other system components.
+    // This enqueues a command which will eventually either call
+    // resumeAfterTimeoutDispatchCycleLocked or abortDispatchCycleLocked.
     onDispatchCycleANRLocked(currentTime, connection);
-    return deactivated;
 }
 
-bool InputDispatcher::abortDispatchCycleLocked(nsecs_t currentTime, Connection* connection,
-        bool broken) {
+void InputDispatcher::resumeAfterTimeoutDispatchCycleLocked(nsecs_t currentTime,
+        const sp<Connection>& connection, nsecs_t newTimeout) {
+#if DEBUG_DISPATCH_CYCLE
+    LOGD("channel '%s' ~ resumeAfterTimeoutDispatchCycleLocked",
+            connection->getInputChannelName());
+#endif
+
+    if (connection->status != Connection::STATUS_NOT_RESPONDING) {
+        return;
+    }
+
+    // Resume normal dispatch.
+    connection->status = Connection::STATUS_NORMAL;
+    connection->setNextTimeoutTime(currentTime, newTimeout);
+}
+
+void InputDispatcher::abortDispatchCycleLocked(nsecs_t currentTime,
+        const sp<Connection>& connection, bool broken) {
 #if DEBUG_DISPATCH_CYCLE
     LOGD("channel '%s' ~ abortDispatchCycle - broken=%s",
             connection->getInputChannelName(), broken ? "true" : "false");
@@ -790,14 +823,13 @@
     connection->nextTimeoutTime = LONG_LONG_MAX;
 
     // Clear the outbound queue.
-    bool deactivated = ! connection->outboundQueue.isEmpty();
-    if (deactivated) {
+    if (! connection->outboundQueue.isEmpty()) {
         do {
             DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead();
             mAllocator.releaseDispatchEntry(dispatchEntry);
         } while (! connection->outboundQueue.isEmpty());
 
-        deactivateConnectionLocked(connection);
+        deactivateConnectionLocked(connection.get());
     }
 
     // Handle the case where the connection appears to be unrecoverably broken.
@@ -811,8 +843,6 @@
             onDispatchCycleBrokenLocked(currentTime, connection);
         }
     }
-
-    return deactivated;
 }
 
 bool InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data) {
@@ -828,13 +858,13 @@
             return false; // remove the callback
         }
 
-        nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
+        nsecs_t currentTime = now();
 
         sp<Connection> connection = d->mConnectionsByReceiveFd.valueAt(connectionIndex);
         if (events & (POLLERR | POLLHUP | POLLNVAL)) {
             LOGE("channel '%s' ~ Consumer closed input channel or an error occurred.  "
                     "events=0x%x", connection->getInputChannelName(), events);
-            d->abortDispatchCycleLocked(currentTime, connection.get(), true /*broken*/);
+            d->abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
             d->runCommandsLockedInterruptible();
             return false; // remove the callback
         }
@@ -849,12 +879,12 @@
         if (status) {
             LOGE("channel '%s' ~ Failed to receive finished signal.  status=%d",
                     connection->getInputChannelName(), status);
-            d->abortDispatchCycleLocked(currentTime, connection.get(), true /*broken*/);
+            d->abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
             d->runCommandsLockedInterruptible();
             return false; // remove the callback
         }
 
-        d->finishDispatchCycleLocked(currentTime, connection.get());
+        d->finishDispatchCycleLocked(currentTime, connection);
         d->runCommandsLockedInterruptible();
         return true;
     } // release lock
@@ -869,8 +899,7 @@
     { // acquire lock
         AutoMutex _l(mLock);
 
-        ConfigurationChangedEntry* newEntry = mAllocator.obtainConfigurationChangedEntry();
-        newEntry->eventTime = eventTime;
+        ConfigurationChangedEntry* newEntry = mAllocator.obtainConfigurationChangedEntry(eventTime);
 
         wasEmpty = mInboundQueue.isEmpty();
         mInboundQueue.enqueueAtTail(newEntry);
@@ -902,6 +931,9 @@
                     LOGV("Dropping movement key during app switch: keyCode=%d, action=%d",
                             keyEntry->keyCode, keyEntry->action);
                     mInboundQueue.dequeue(keyEntry);
+
+                    setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED);
+
                     mAllocator.releaseKeyEntry(keyEntry);
                 } else {
                     // stop at last non-movement key
@@ -928,18 +960,10 @@
     { // acquire lock
         AutoMutex _l(mLock);
 
-        KeyEntry* newEntry = mAllocator.obtainKeyEntry();
-        newEntry->eventTime = eventTime;
-        newEntry->deviceId = deviceId;
-        newEntry->nature = nature;
-        newEntry->policyFlags = policyFlags;
-        newEntry->action = action;
-        newEntry->flags = flags;
-        newEntry->keyCode = keyCode;
-        newEntry->scanCode = scanCode;
-        newEntry->metaState = metaState;
-        newEntry->repeatCount = 0;
-        newEntry->downTime = downTime;
+        int32_t repeatCount = 0;
+        KeyEntry* newEntry = mAllocator.obtainKeyEntry(eventTime,
+                deviceId, nature, policyFlags, action, flags, keyCode, scanCode,
+                metaState, repeatCount, downTime);
 
         wasEmpty = mInboundQueue.isEmpty();
         mInboundQueue.enqueueAtTail(newEntry);
@@ -992,7 +1016,8 @@
                 }
 
                 if (motionEntry->action != MOTION_EVENT_ACTION_MOVE
-                        || motionEntry->pointerCount != pointerCount) {
+                        || motionEntry->pointerCount != pointerCount
+                        || motionEntry->isInjected()) {
                     // Last motion event in the queue for this device is not compatible for
                     // appending new samples.  Stop here.
                     goto NoBatchingOrStreaming;
@@ -1000,7 +1025,7 @@
 
                 // The last motion event is a move and is compatible for appending.
                 // Do the batching magic.
-                mAllocator.appendMotionSample(motionEntry, eventTime, pointerCount, pointerCoords);
+                mAllocator.appendMotionSample(motionEntry, eventTime, pointerCoords);
 #if DEBUG_BATCHING
                 LOGD("Appended motion sample onto batch for most recent "
                         "motion event for this device in the inbound queue.");
@@ -1053,18 +1078,19 @@
                                     dispatchEntry->eventEntry);
                             if (syncedMotionEntry->action != MOTION_EVENT_ACTION_MOVE
                                     || syncedMotionEntry->deviceId != deviceId
-                                    || syncedMotionEntry->pointerCount != pointerCount) {
+                                    || syncedMotionEntry->pointerCount != pointerCount
+                                    || syncedMotionEntry->isInjected()) {
                                 goto NoBatchingOrStreaming;
                             }
 
                             // Found synced move entry.  Append sample and resume dispatch.
                             mAllocator.appendMotionSample(syncedMotionEntry, eventTime,
-                                    pointerCount, pointerCoords);
+                                    pointerCoords);
     #if DEBUG_BATCHING
                             LOGD("Appended motion sample onto batch for most recent synchronously "
                                     "dispatched motion event for this device in the outbound queues.");
     #endif
-                            nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
+                            nsecs_t currentTime = now();
                             dispatchEventToCurrentInputTargetsLocked(currentTime, syncedMotionEntry,
                                     true /*resumeWithAppendedMotionSample*/);
 
@@ -1079,24 +1105,10 @@
         }
 
         // Just enqueue a new motion event.
-        MotionEntry* newEntry = mAllocator.obtainMotionEntry();
-        newEntry->eventTime = eventTime;
-        newEntry->deviceId = deviceId;
-        newEntry->nature = nature;
-        newEntry->policyFlags = policyFlags;
-        newEntry->action = action;
-        newEntry->metaState = metaState;
-        newEntry->edgeFlags = edgeFlags;
-        newEntry->xPrecision = xPrecision;
-        newEntry->yPrecision = yPrecision;
-        newEntry->downTime = downTime;
-        newEntry->pointerCount = pointerCount;
-        newEntry->firstSample.eventTime = eventTime;
-        newEntry->lastSample = & newEntry->firstSample;
-        for (uint32_t i = 0; i < pointerCount; i++) {
-            newEntry->pointerIds[i] = pointerIds[i];
-            newEntry->firstSample.pointerCoords[i] = pointerCoords[i];
-        }
+        MotionEntry* newEntry = mAllocator.obtainMotionEntry(eventTime,
+                deviceId, nature, policyFlags, action, metaState, edgeFlags,
+                xPrecision, yPrecision, downTime,
+                pointerCount, pointerIds, pointerCoords);
 
         wasEmpty = mInboundQueue.isEmpty();
         mInboundQueue.enqueueAtTail(newEntry);
@@ -1107,6 +1119,133 @@
     }
 }
 
+int32_t InputDispatcher::injectInputEvent(const InputEvent* event,
+        int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+    LOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, "
+            "sync=%d, timeoutMillis=%d",
+            event->getType(), injectorPid, injectorUid, sync, timeoutMillis);
+#endif
+
+    nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis);
+
+    EventEntry* injectedEntry;
+    bool wasEmpty;
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        injectedEntry = createEntryFromInputEventLocked(event);
+        injectedEntry->refCount += 1;
+        injectedEntry->injectorPid = injectorPid;
+        injectedEntry->injectorUid = injectorUid;
+
+        wasEmpty = mInboundQueue.isEmpty();
+        mInboundQueue.enqueueAtTail(injectedEntry);
+
+    } // release lock
+
+    if (wasEmpty) {
+        mPollLoop->wake();
+    }
+
+    int32_t injectionResult;
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        for (;;) {
+            injectionResult = injectedEntry->injectionResult;
+            if (injectionResult != INPUT_EVENT_INJECTION_PENDING) {
+                break;
+            }
+
+            nsecs_t remainingTimeout = endTime - now();
+            if (remainingTimeout <= 0) {
+                injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
+                sync = false;
+                break;
+            }
+
+            mInjectionResultAvailableCondition.waitRelative(mLock, remainingTimeout);
+        }
+
+        if (sync) {
+            while (! isFullySynchronizedLocked()) {
+                nsecs_t remainingTimeout = endTime - now();
+                if (remainingTimeout <= 0) {
+                    injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
+                    break;
+                }
+
+                mFullySynchronizedCondition.waitRelative(mLock, remainingTimeout);
+            }
+        }
+
+        mAllocator.releaseEventEntry(injectedEntry);
+    } // release lock
+
+    return injectionResult;
+}
+
+void InputDispatcher::setInjectionResultLocked(EventEntry* entry, int32_t injectionResult) {
+    if (entry->isInjected()) {
+#if DEBUG_INJECTION
+        LOGD("Setting input event injection result to %d.  "
+                "injectorPid=%d, injectorUid=%d",
+                 injectionResult, entry->injectorPid, entry->injectorUid);
+#endif
+
+        entry->injectionResult = injectionResult;
+        mInjectionResultAvailableCondition.broadcast();
+    }
+}
+
+bool InputDispatcher::isFullySynchronizedLocked() {
+    return mInboundQueue.isEmpty() && mActiveConnections.isEmpty();
+}
+
+InputDispatcher::EventEntry* InputDispatcher::createEntryFromInputEventLocked(
+        const InputEvent* event) {
+    switch (event->getType()) {
+    case INPUT_EVENT_TYPE_KEY: {
+        const KeyEvent* keyEvent = static_cast<const KeyEvent*>(event);
+        uint32_t policyFlags = 0; // XXX consider adding a policy flag to track injected events
+
+        KeyEntry* keyEntry = mAllocator.obtainKeyEntry(keyEvent->getEventTime(),
+                keyEvent->getDeviceId(), keyEvent->getNature(), policyFlags,
+                keyEvent->getAction(), keyEvent->getFlags(),
+                keyEvent->getKeyCode(), keyEvent->getScanCode(), keyEvent->getMetaState(),
+                keyEvent->getRepeatCount(), keyEvent->getDownTime());
+        return keyEntry;
+    }
+
+    case INPUT_EVENT_TYPE_MOTION: {
+        const MotionEvent* motionEvent = static_cast<const MotionEvent*>(event);
+        uint32_t policyFlags = 0; // XXX consider adding a policy flag to track injected events
+
+        const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes();
+        const PointerCoords* samplePointerCoords = motionEvent->getSamplePointerCoords();
+        size_t pointerCount = motionEvent->getPointerCount();
+
+        MotionEntry* motionEntry = mAllocator.obtainMotionEntry(*sampleEventTimes,
+                motionEvent->getDeviceId(), motionEvent->getNature(), policyFlags,
+                motionEvent->getAction(), motionEvent->getMetaState(), motionEvent->getEdgeFlags(),
+                motionEvent->getXPrecision(), motionEvent->getYPrecision(),
+                motionEvent->getDownTime(), uint32_t(pointerCount),
+                motionEvent->getPointerIds(), samplePointerCoords);
+        for (size_t i = motionEvent->getHistorySize(); i > 0; i--) {
+            sampleEventTimes += 1;
+            samplePointerCoords += pointerCount;
+            mAllocator.appendMotionSample(motionEntry, *sampleEventTimes, samplePointerCoords);
+        }
+        return motionEntry;
+    }
+
+    default:
+        assert(false);
+        return NULL;
+    }
+}
+
 void InputDispatcher::resetKeyRepeatLocked() {
     if (mKeyRepeatState.lastKeyEntry) {
         mAllocator.releaseKeyEntry(mKeyRepeatState.lastKeyEntry);
@@ -1169,8 +1308,8 @@
 
         connection->status = Connection::STATUS_ZOMBIE;
 
-        nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
-        abortDispatchCycleLocked(currentTime, connection.get(), true /*broken*/);
+        nsecs_t currentTime = now();
+        abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
 
         runCommandsLockedInterruptible();
     } // release lock
@@ -1202,11 +1341,11 @@
 }
 
 void InputDispatcher::onDispatchCycleStartedLocked(
-        nsecs_t currentTime, Connection* connection) {
+        nsecs_t currentTime, const sp<Connection>& connection) {
 }
 
 void InputDispatcher::onDispatchCycleFinishedLocked(
-        nsecs_t currentTime, Connection* connection, bool recoveredFromANR) {
+        nsecs_t currentTime, const sp<Connection>& connection, bool recoveredFromANR) {
     if (recoveredFromANR) {
         LOGI("channel '%s' ~ Recovered from ANR.  %01.1fms since event, "
                 "%01.1fms since dispatch, %01.1fms since ANR",
@@ -1217,12 +1356,12 @@
 
         CommandEntry* commandEntry = postCommandLocked(
                 & InputDispatcher::doNotifyInputChannelRecoveredFromANRLockedInterruptible);
-        commandEntry->inputChannel = connection->inputChannel;
+        commandEntry->connection = connection;
     }
 }
 
 void InputDispatcher::onDispatchCycleANRLocked(
-        nsecs_t currentTime, Connection* connection) {
+        nsecs_t currentTime, const sp<Connection>& connection) {
     LOGI("channel '%s' ~ Not responding!  %01.1fms since event, %01.1fms since dispatch",
             connection->getInputChannelName(),
             connection->getEventLatencyMillis(currentTime),
@@ -1230,47 +1369,64 @@
 
     CommandEntry* commandEntry = postCommandLocked(
             & InputDispatcher::doNotifyInputChannelANRLockedInterruptible);
-    commandEntry->inputChannel = connection->inputChannel;
+    commandEntry->connection = connection;
 }
 
 void InputDispatcher::onDispatchCycleBrokenLocked(
-        nsecs_t currentTime, Connection* connection) {
+        nsecs_t currentTime, const sp<Connection>& connection) {
     LOGE("channel '%s' ~ Channel is unrecoverably broken and will be disposed!",
             connection->getInputChannelName());
 
     CommandEntry* commandEntry = postCommandLocked(
             & InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible);
-    commandEntry->inputChannel = connection->inputChannel;
+    commandEntry->connection = connection;
 }
 
 void InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible(
         CommandEntry* commandEntry) {
-    mLock.unlock();
+    sp<Connection> connection = commandEntry->connection;
 
-    mPolicy->notifyInputChannelBroken(commandEntry->inputChannel);
-    commandEntry->inputChannel.clear();
+    if (connection->status != Connection::STATUS_ZOMBIE) {
+        mLock.unlock();
 
-    mLock.lock();
+        mPolicy->notifyInputChannelBroken(connection->inputChannel);
+
+        mLock.lock();
+    }
 }
 
 void InputDispatcher::doNotifyInputChannelANRLockedInterruptible(
         CommandEntry* commandEntry) {
-    mLock.unlock();
+    sp<Connection> connection = commandEntry->connection;
 
-    mPolicy->notifyInputChannelANR(commandEntry->inputChannel);
-    commandEntry->inputChannel.clear();
+    if (connection->status != Connection::STATUS_ZOMBIE) {
+        mLock.unlock();
 
-    mLock.lock();
+        nsecs_t newTimeout;
+        bool resume = mPolicy->notifyInputChannelANR(connection->inputChannel, newTimeout);
+
+        mLock.lock();
+
+        nsecs_t currentTime = now();
+        if (resume) {
+            resumeAfterTimeoutDispatchCycleLocked(currentTime, connection, newTimeout);
+        } else {
+            abortDispatchCycleLocked(currentTime, connection, false /*(not) broken*/);
+        }
+    }
 }
 
 void InputDispatcher::doNotifyInputChannelRecoveredFromANRLockedInterruptible(
         CommandEntry* commandEntry) {
-    mLock.unlock();
+    sp<Connection> connection = commandEntry->connection;
 
-    mPolicy->notifyInputChannelRecoveredFromANR(commandEntry->inputChannel);
-    commandEntry->inputChannel.clear();
+    if (connection->status != Connection::STATUS_ZOMBIE) {
+        mLock.unlock();
 
-    mLock.lock();
+        mPolicy->notifyInputChannelRecoveredFromANR(connection->inputChannel);
+
+        mLock.lock();
+    }
 }
 
 
@@ -1279,29 +1435,69 @@
 InputDispatcher::Allocator::Allocator() {
 }
 
+void InputDispatcher::Allocator::initializeEventEntry(EventEntry* entry, int32_t type,
+        nsecs_t eventTime) {
+    entry->type = type;
+    entry->refCount = 1;
+    entry->dispatchInProgress = false;
+    entry->injectionResult = INPUT_EVENT_INJECTION_PENDING;
+    entry->injectorPid = -1;
+    entry->injectorUid = -1;
+}
+
 InputDispatcher::ConfigurationChangedEntry*
-InputDispatcher::Allocator::obtainConfigurationChangedEntry() {
+InputDispatcher::Allocator::obtainConfigurationChangedEntry(nsecs_t eventTime) {
     ConfigurationChangedEntry* entry = mConfigurationChangeEntryPool.alloc();
-    entry->refCount = 1;
-    entry->type = EventEntry::TYPE_CONFIGURATION_CHANGED;
-    entry->dispatchInProgress = false;
+    initializeEventEntry(entry, EventEntry::TYPE_CONFIGURATION_CHANGED, eventTime);
     return entry;
 }
 
-InputDispatcher::KeyEntry* InputDispatcher::Allocator::obtainKeyEntry() {
+InputDispatcher::KeyEntry* InputDispatcher::Allocator::obtainKeyEntry(nsecs_t eventTime,
+        int32_t deviceId, int32_t nature, uint32_t policyFlags, int32_t action,
+        int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
+        int32_t repeatCount, nsecs_t downTime) {
     KeyEntry* entry = mKeyEntryPool.alloc();
-    entry->refCount = 1;
-    entry->type = EventEntry::TYPE_KEY;
-    entry->dispatchInProgress = false;
+    initializeEventEntry(entry, EventEntry::TYPE_KEY, eventTime);
+
+    entry->deviceId = deviceId;
+    entry->nature = nature;
+    entry->policyFlags = policyFlags;
+    entry->action = action;
+    entry->flags = flags;
+    entry->keyCode = keyCode;
+    entry->scanCode = scanCode;
+    entry->metaState = metaState;
+    entry->repeatCount = repeatCount;
+    entry->downTime = downTime;
     return entry;
 }
 
-InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry() {
+InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry(nsecs_t eventTime,
+        int32_t deviceId, int32_t nature, uint32_t policyFlags, int32_t action,
+        int32_t metaState, int32_t edgeFlags, float xPrecision, float yPrecision,
+        nsecs_t downTime, uint32_t pointerCount,
+        const int32_t* pointerIds, const PointerCoords* pointerCoords) {
     MotionEntry* entry = mMotionEntryPool.alloc();
-    entry->refCount = 1;
-    entry->type = EventEntry::TYPE_MOTION;
+    initializeEventEntry(entry, EventEntry::TYPE_MOTION, eventTime);
+
+    entry->eventTime = eventTime;
+    entry->deviceId = deviceId;
+    entry->nature = nature;
+    entry->policyFlags = policyFlags;
+    entry->action = action;
+    entry->metaState = metaState;
+    entry->edgeFlags = edgeFlags;
+    entry->xPrecision = xPrecision;
+    entry->yPrecision = yPrecision;
+    entry->downTime = downTime;
+    entry->pointerCount = pointerCount;
+    entry->firstSample.eventTime = eventTime;
     entry->firstSample.next = NULL;
-    entry->dispatchInProgress = false;
+    entry->lastSample = & entry->firstSample;
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        entry->pointerIds[i] = pointerIds[i];
+        entry->firstSample.pointerCoords[i] = pointerCoords[i];
+    }
     return entry;
 }
 
@@ -1379,10 +1575,11 @@
 }
 
 void InputDispatcher::Allocator::appendMotionSample(MotionEntry* motionEntry,
-        nsecs_t eventTime, int32_t pointerCount, const PointerCoords* pointerCoords) {
+        nsecs_t eventTime, const PointerCoords* pointerCoords) {
     MotionSample* sample = mMotionSamplePool.alloc();
     sample->eventTime = eventTime;
-    for (int32_t i = 0; i < pointerCount; i++) {
+    uint32_t pointerCount = motionEntry->pointerCount;
+    for (uint32_t i = 0; i < pointerCount; i++) {
         sample->pointerCoords[i] = pointerCoords[i];
     }
 
@@ -1407,6 +1604,10 @@
     return inputPublisher.initialize();
 }
 
+void InputDispatcher::Connection::setNextTimeoutTime(nsecs_t currentTime, nsecs_t timeout) {
+    nextTimeoutTime = (timeout >= 0) ? currentTime + timeout : LONG_LONG_MAX;
+}
+
 const char* InputDispatcher::Connection::getStatusLabel() const {
     switch (status) {
     case STATUS_NORMAL:
diff --git a/libs/ui/InputManager.cpp b/libs/ui/InputManager.cpp
index 7538dd0..32c58b4 100644
--- a/libs/ui/InputManager.cpp
+++ b/libs/ui/InputManager.cpp
@@ -80,6 +80,11 @@
     return mDispatcher->unregisterInputChannel(inputChannel);
 }
 
+int32_t InputManager::injectInputEvent(const InputEvent* event,
+        int32_t injectorPid, int32_t injectorUid, bool sync, int32_t timeoutMillis) {
+    return mDispatcher->injectInputEvent(event, injectorPid, injectorUid, sync, timeoutMillis);
+}
+
 void InputManager::getInputConfiguration(InputConfiguration* outConfiguration) const {
     mReader->getCurrentInputConfiguration(outConfiguration);
 }
diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp
index 5a280ae..1824054 100644
--- a/libs/ui/InputReader.cpp
+++ b/libs/ui/InputReader.cpp
@@ -1444,7 +1444,7 @@
         case InputReaderPolicyInterface::ROTATION_90: {
             float xTemp = x;
             x = y;
-            y = mDisplayHeight - xTemp;
+            y = mDisplayWidth - xTemp;
             break;
         }
         case InputReaderPolicyInterface::ROTATION_180: {
@@ -1454,7 +1454,7 @@
         }
         case InputReaderPolicyInterface::ROTATION_270: {
             float xTemp = x;
-            x = mDisplayWidth - y;
+            x = mDisplayHeight - y;
             y = xTemp;
             break;
         }
@@ -1510,7 +1510,7 @@
 
     uint32_t fields = device->trackball.accumulator.fields;
     bool downChanged = fields & InputDevice::TrackballState::Accumulator::FIELD_BTN_MOUSE;
-    bool deltaChanged = (fields & DELTA_FIELDS) == DELTA_FIELDS;
+    bool deltaChanged = fields & DELTA_FIELDS;
 
     bool down;
     if (downChanged) {
@@ -1546,10 +1546,10 @@
 
     int32_t pointerId = 0;
     PointerCoords pointerCoords;
-    pointerCoords.x = device->trackball.accumulator.relX
-            * device->trackball.precalculated.xScale;
-    pointerCoords.y = device->trackball.accumulator.relY
-            * device->trackball.precalculated.yScale;
+    pointerCoords.x = fields & InputDevice::TrackballState::Accumulator::FIELD_REL_X
+            ? device->trackball.accumulator.relX * device->trackball.precalculated.xScale : 0;
+    pointerCoords.y = fields & InputDevice::TrackballState::Accumulator::FIELD_REL_Y
+            ? device->trackball.accumulator.relY * device->trackball.precalculated.yScale : 0;
     pointerCoords.pressure = 1.0f; // XXX Consider making this 1.0f if down, 0 otherwise.
     pointerCoords.size = 0;
 
diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java
index 72c4166..8d9bb29 100644
--- a/services/java/com/android/server/InputManager.java
+++ b/services/java/com/android/server/InputManager.java
@@ -81,6 +81,10 @@
     private static native boolean nativeHasKeys(int[] keyCodes, boolean[] keyExists);
     private static native void nativeRegisterInputChannel(InputChannel inputChannel);
     private static native void nativeUnregisterInputChannel(InputChannel inputChannel);
+    private static native int nativeInjectKeyEvent(KeyEvent event, int nature,
+            int injectorPid, int injectorUid, boolean sync, int timeoutMillis);
+    private static native int nativeInjectMotionEvent(MotionEvent event, int nature,
+            int injectorPid, int injectorUid, boolean sync, int timeoutMillis);
     
     // Device class as defined by EventHub.
     private static final int CLASS_KEYBOARD = 0x00000001;
@@ -90,6 +94,12 @@
     private static final int CLASS_TOUCHSCREEN_MT = 0x00000010;
     private static final int CLASS_DPAD = 0x00000020;
     
+    // Input event injection constants defined in InputDispatcher.h.
+    static final int INPUT_EVENT_INJECTION_SUCCEEDED = 0;
+    static final int INPUT_EVENT_INJECTION_PERMISSION_DENIED = 1;
+    static final int INPUT_EVENT_INJECTION_FAILED = 2;
+    static final int INPUT_EVENT_INJECTION_TIMED_OUT = 3;
+    
     public InputManager(Context context,
             WindowManagerService windowManagerService,
             WindowManagerPolicy windowManagerPolicy,
@@ -215,37 +225,63 @@
         nativeUnregisterInputChannel(inputChannel);
     }
     
-    // TBD where this really belongs, duplicate copy in WindowManagerService
-    static final int INJECT_FAILED = 0;
-    static final int INJECT_SUCCEEDED = 1;
-    static final int INJECT_NO_PERMISSION = -1;
-    
     /**
      * Injects a key event into the event system on behalf of an application.
+     * This method may block even if sync is false because it must wait for previous events
+     * to be dispatched before it can determine whether input event injection will be
+     * permitted based on the current input focus.
      * @param event The event to inject.
      * @param nature The nature of the event.
+     * @param injectorPid The pid of the injecting application.
+     * @param injectorUid The uid of the injecting application.
      * @param sync If true, waits for the event to be completed before returning.
-     * @param pid The pid of the injecting application.
-     * @param uid The uid of the injecting application.
-     * @return INJECT_SUCCEEDED, INJECT_FAILED or INJECT_NO_PERMISSION
+     * @param timeoutMillis The injection timeout in milliseconds.
+     * @return One of the INPUT_EVENT_INJECTION_XXX constants.
      */
-    public int injectKeyEvent(KeyEvent event, int nature, boolean sync, int pid, int uid) {
-        // TODO
-        return INJECT_FAILED;
+    public int injectKeyEvent(KeyEvent event, int nature, int injectorPid, int injectorUid,
+            boolean sync, int timeoutMillis) {
+        if (event == null) {
+            throw new IllegalArgumentException("event must not be null");
+        }
+        if (injectorPid < 0 || injectorUid < 0) {
+            throw new IllegalArgumentException("injectorPid and injectorUid must not be negative.");
+        }
+        if (timeoutMillis <= 0) {
+            throw new IllegalArgumentException("timeoutMillis must be positive");
+        }
+        
+        return nativeInjectKeyEvent(event, nature, injectorPid, injectorUid,
+                sync, timeoutMillis);
     }
     
     /**
      * Injects a motion event into the event system on behalf of an application.
+     * This method may block even if sync is false because it must wait for previous events
+     * to be dispatched before it can determine whether input event injection will be
+     * permitted based on the current input focus.
      * @param event The event to inject.
      * @param nature The nature of the event.
      * @param sync If true, waits for the event to be completed before returning.
-     * @param pid The pid of the injecting application.
-     * @param uid The uid of the injecting application.
-     * @return INJECT_SUCCEEDED, INJECT_FAILED or INJECT_NO_PERMISSION
+     * @param injectorPid The pid of the injecting application.
+     * @param injectorUid The uid of the injecting application.
+     * @param sync If true, waits for the event to be completed before returning.
+     * @param timeoutMillis The injection timeout in milliseconds.
+     * @return One of the INPUT_EVENT_INJECTION_XXX constants.
      */
-    public int injectMotionEvent(MotionEvent event, int nature, boolean sync, int pid, int uid) {
-        // TODO
-        return INJECT_FAILED;
+    public int injectMotionEvent(MotionEvent event, int nature, int injectorPid, int injectorUid,
+            boolean sync, int timeoutMillis) {
+        if (event == null) {
+            throw new IllegalArgumentException("event must not be null");
+        }
+        if (injectorPid < 0 || injectorUid < 0) {
+            throw new IllegalArgumentException("injectorPid and injectorUid must not be negative.");
+        }
+        if (timeoutMillis <= 0) {
+            throw new IllegalArgumentException("timeoutMillis must be positive");
+        }
+        
+        return nativeInjectMotionEvent(event, nature, injectorPid, injectorUid,
+                sync, timeoutMillis);
     }
     
     public void dump(PrintWriter pw) {
@@ -271,8 +307,6 @@
         private static final boolean DEBUG_VIRTUAL_KEYS = false;
         private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml";
         
-        private final InputTargetList mReusableInputTargetList = new InputTargetList();
-        
         @SuppressWarnings("unused")
         public boolean isScreenOn() {
             return mPowerManagerService.isScreenOn();
@@ -309,6 +343,21 @@
         }
         
         @SuppressWarnings("unused")
+        public void notifyInputChannelBroken(InputChannel inputChannel) {
+            mWindowManagerService.notifyInputChannelBroken(inputChannel);
+        }
+
+        @SuppressWarnings("unused")
+        public long notifyInputChannelANR(InputChannel inputChannel) {
+            return mWindowManagerService.notifyInputChannelANR(inputChannel);
+        }
+
+        @SuppressWarnings("unused")
+        public void notifyInputChannelRecoveredFromANR(InputChannel inputChannel) {
+            mWindowManagerService.notifyInputChannelRecoveredFromANR(inputChannel);
+        }
+        
+        @SuppressWarnings("unused")
         public int hackInterceptKey(int deviceId, int type, int scanCode,
                 int keyCode, int policyFlags, int value, long whenNanos, boolean isScreenOn) {
             RawInputEvent event = new RawInputEvent();
@@ -437,24 +486,23 @@
             return names.toArray(new String[names.size()]);
         }
         
+        // TODO All code related to target identification should be moved down into native.
         @SuppressWarnings("unused")
-        public InputTarget[] getKeyEventTargets(KeyEvent event, int nature, int policyFlags) {
-            mReusableInputTargetList.clear();
-            
-            mWindowManagerService.getKeyEventTargets(mReusableInputTargetList,
-                    event, nature, policyFlags);
-            
-            return mReusableInputTargetList.toNullTerminatedArray();
+        public int getKeyEventTargets(InputTargetList inputTargets,
+                KeyEvent event, int nature, int policyFlags,
+                int injectorPid, int injectorUid) {
+            inputTargets.clear();
+            return mWindowManagerService.getKeyEventTargetsTd(
+                    inputTargets, event, nature, policyFlags, injectorPid, injectorUid);
         }
         
         @SuppressWarnings("unused")
-        public InputTarget[] getMotionEventTargets(MotionEvent event, int nature, int policyFlags) {
-            mReusableInputTargetList.clear();
-            
-            mWindowManagerService.getMotionEventTargets(mReusableInputTargetList,
-                    event, nature, policyFlags);
-            
-            return mReusableInputTargetList.toNullTerminatedArray();
+        public int getMotionEventTargets(InputTargetList inputTargets,
+                MotionEvent event, int nature, int policyFlags,
+                int injectorPid, int injectorUid) {
+            inputTargets.clear();
+            return mWindowManagerService.getMotionEventTargetsTd(
+                    inputTargets, event, nature, policyFlags, injectorPid, injectorUid);
         }
     }
 }
diff --git a/services/java/com/android/server/InputTargetList.java b/services/java/com/android/server/InputTargetList.java
index 1575612..83acc8f 100644
--- a/services/java/com/android/server/InputTargetList.java
+++ b/services/java/com/android/server/InputTargetList.java
@@ -29,7 +29,7 @@
  * 
  * @hide
  */
-public class InputTargetList {
+public final class InputTargetList {
     private InputTarget[] mArray;
     private int mCount;
     
@@ -55,7 +55,7 @@
             count -= 1;
             mArray[count].recycle();
         }
-        // mArray[0] could be set to null here but we do it in toNullTerminatedArray()
+        mArray[0] = null;
     }
     
     /**
@@ -91,7 +91,7 @@
         
         mArray[mCount] = inputTarget;
         mCount += 1;
-        // mArray[mCount] could be set to null here but we do it in toNullTerminatedArray()
+        mArray[mCount] = null;
     }
     
     /**
@@ -99,7 +99,6 @@
      * @return The input target array.
      */
     public InputTarget[] toNullTerminatedArray() {
-        mArray[mCount] = null;
         return mArray;
     }
 }
\ No newline at end of file
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 9bc3931..95ab5bc 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -203,6 +203,10 @@
     /** Adjustment to time to perform a dim, to make it more dramatic.
      */
     static final int DIM_DURATION_MULTIPLIER = 6;
+    
+    // Maximum number of milliseconds to wait for input event injection.
+    // FIXME is this value reasonable?
+    private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
 
     static final int INJECT_FAILED = 0;
     static final int INJECT_SUCCEEDED = 1;
@@ -447,8 +451,6 @@
     final ArrayList<AppWindowToken> mToTopApps = new ArrayList<AppWindowToken>();
     final ArrayList<AppWindowToken> mToBottomApps = new ArrayList<AppWindowToken>();
 
-    //flag to detect fat touch events
-    boolean mFatTouch = false;
     Display mDisplay;
 
     H mH = new H();
@@ -5072,106 +5074,336 @@
         mPolicy.adjustConfigurationLw(config);
         return true;
     }
+    
+    /* Notifies the window manager about a broken input channel.
+     * 
+     * Called by the InputManager.
+     */
+    public void notifyInputChannelBroken(InputChannel inputChannel) {
+        synchronized (mWindowMap) {
+            WindowState windowState = getWindowStateForInputChannelLocked(inputChannel);
+            if (windowState == null) {
+                return; // irrelevant
+            }
+            
+            Slog.i(TAG, "WINDOW DIED " + windowState);
+            removeWindowLocked(windowState.mSession, windowState);
+        }
+    }
+    
+    /* Notifies the window manager about a broken input channel.
+     * 
+     * Called by the InputManager.
+     */
+    public long notifyInputChannelANR(InputChannel inputChannel) {
+        IApplicationToken appToken;
+        synchronized (mWindowMap) {
+            WindowState windowState = getWindowStateForInputChannelLocked(inputChannel);
+            if (windowState == null) {
+                return -2; // irrelevant, abort dispatching (-2)
+            }
+            
+            Slog.i(TAG, "Input event dispatching timed out sending to "
+                    + windowState.mAttrs.getTitle());
+            appToken = windowState.getAppToken();
+        }
+        
+        try {
+            // Notify the activity manager about the timeout and let it decide whether
+            // to abort dispatching or keep waiting.
+            boolean abort = appToken.keyDispatchingTimedOut();
+            if (abort) {
+                return -2; // abort dispatching
+            }
+            
+            // Return new timeout.
+            // We use -1 for infinite timeout to avoid clash with -2 magic number.
+            long newTimeout = appToken.getKeyDispatchingTimeout() * 1000000;
+            return newTimeout < 0 ? -1 : newTimeout;
+        } catch (RemoteException ex) {
+            return -2; // abort dispatching
+        }
+    }
+
+    /* Notifies the window manager about a broken input channel.
+     * 
+     * Called by the InputManager.
+     */
+    public void notifyInputChannelRecoveredFromANR(InputChannel inputChannel) {
+        // Nothing to do just now.
+        // Just wait for the user to dismiss the ANR dialog.
+        
+        // TODO We could try to automatically dismiss the ANR dialog on recovery
+        //      although that might be disorienting.
+    }
+    
+    private WindowState getWindowStateForInputChannelLocked(InputChannel inputChannel) {
+        int windowCount = mWindows.size();
+        for (int i = 0; i < windowCount; i++) {
+            WindowState windowState = (WindowState) mWindows.get(i);
+            if (windowState.mInputChannel == inputChannel) {
+                return windowState;
+            }
+        }
+        
+        return null;
+    }
 
     // -------------------------------------------------------------
     // Input Events and Focus Management
     // -------------------------------------------------------------
     
-    public void getKeyEventTargets(InputTargetList inputTargets,
-            KeyEvent event, int nature, int policyFlags) {
+    private boolean checkInjectionPermissionTd(WindowState focus,
+            int injectorPid, int injectorUid) {
+        if (injectorUid > 0 && (focus == null || injectorUid != focus.mSession.mUid)) {
+            if (mContext.checkPermission(
+                    android.Manifest.permission.INJECT_EVENTS, injectorPid, injectorUid)
+                    != PackageManager.PERMISSION_GRANTED) {
+                Slog.w(TAG, "Permission denied: injecting key event from pid "
+                        + injectorPid + " uid " + injectorUid + " to window " + focus
+                        + " owned by uid " + focus.mSession.mUid);
+                return false;
+            }
+        }
+        return true;
+    }
+    
+    /* Gets the input targets for a key event.
+     * 
+     * Called by the InputManager on the InputDispatcher thread.
+     */
+    public int getKeyEventTargetsTd(InputTargetList inputTargets,
+            KeyEvent event, int nature, int policyFlags, int injectorPid, int injectorUid) {
         if (DEBUG_INPUT) Slog.v(TAG, "Dispatch key: " + event);
 
         // TODO what do we do with mDisplayFrozen?
         // TODO what do we do with focus.mToken.paused?
         
         WindowState focus = getFocusedWindow();
-        wakeupIfNeeded(focus, LocalPowerManager.BUTTON_EVENT);
         
-        addInputTarget(inputTargets, focus, InputTarget.FLAG_SYNC);
-    }
-    
-    // Target of Motion events
-    WindowState mTouchFocus;
-
-    // Windows above the target who would like to receive an "outside"
-    // touch event for any down events outside of them.
-    // (This is a linked list by way of WindowState.mNextOutsideTouch.)
-    WindowState mOutsideTouchTargets;
-    
-    private void clearTouchFocus() {
-        mTouchFocus = null;
-        mOutsideTouchTargets = null;
-    }
-    
-    public void getMotionEventTargets(InputTargetList inputTargets,
-            MotionEvent event, int nature, int policyFlags) {
-        if (nature == InputQueue.INPUT_EVENT_NATURE_TRACKBALL) {
-            // More or less the same as for keys...
-            WindowState focus = getFocusedWindow();
-            wakeupIfNeeded(focus, LocalPowerManager.BUTTON_EVENT);
-            
-            addInputTarget(inputTargets, focus, InputTarget.FLAG_SYNC);
-            return;
+        if (! checkInjectionPermissionTd(focus, injectorPid, injectorUid)) {
+            return InputManager.INPUT_EVENT_INJECTION_PERMISSION_DENIED;
         }
         
-        int action = event.getAction();
+        if (mPolicy.interceptKeyTi(focus, event.getKeyCode(), event.getMetaState(),
+                event.getAction() == KeyEvent.ACTION_DOWN,
+                event.getRepeatCount(), event.getFlags())) {
+            // Policy consumed the event.
+            return InputManager.INPUT_EVENT_INJECTION_SUCCEEDED;
+        }
+        
+        if (focus == null) {
+            return InputManager.INPUT_EVENT_INJECTION_FAILED;
+        }
+        
+        wakeupIfNeeded(focus, LocalPowerManager.BUTTON_EVENT);
+        
+        addInputTargetTd(inputTargets, focus, InputTarget.FLAG_SYNC);
+        return InputManager.INPUT_EVENT_INJECTION_SUCCEEDED;
+    }
+    
+    /* Gets the input targets for a motion event.
+     * 
+     * Called by the InputManager on the InputDispatcher thread.
+     */
+    public int getMotionEventTargetsTd(InputTargetList inputTargets,
+            MotionEvent event, int nature, int policyFlags, int injectorPid, int injectorUid) {
+        switch (nature) {
+            case InputQueue.INPUT_EVENT_NATURE_TRACKBALL:
+                return getMotionEventTargetsForTrackballTd(inputTargets, event, policyFlags,
+                        injectorPid, injectorUid);
+                
+            case InputQueue.INPUT_EVENT_NATURE_TOUCH:
+                return getMotionEventTargetsForTouchTd(inputTargets, event, policyFlags,
+                        injectorPid, injectorUid);
+                
+            default:
+                return InputManager.INPUT_EVENT_INJECTION_FAILED;
+        }
+    }
+    
+    /* Gets the input targets for a trackball event.
+     * 
+     * Called by the InputManager on the InputDispatcher thread.
+     */
+    private int getMotionEventTargetsForTrackballTd(InputTargetList inputTargets,
+            MotionEvent event, int policyFlags, int injectorPid, int injectorUid) {
+        WindowState focus = getFocusedWindow();
+        
+        if (! checkInjectionPermissionTd(focus, injectorPid, injectorUid)) {
+            return InputManager.INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+        }
+        
+        if (focus == null) {
+            return InputManager.INPUT_EVENT_INJECTION_FAILED;
+        }
+        
+        wakeupIfNeeded(focus, LocalPowerManager.BUTTON_EVENT);
+        
+        addInputTargetTd(inputTargets, focus, InputTarget.FLAG_SYNC);
+        return InputManager.INPUT_EVENT_INJECTION_SUCCEEDED;
+    }
+    
+    /* Set to true when a fat touch has been detected during the processing of a touch event.
+     * 
+     * Only used by getMotionEventTargetsForTouchTd.
+     * Set to true whenever a fat touch is detected and reset to false on ACTION_UP.
+     */
+    private boolean mFatTouch;
+    
+    /* Set to true when we think the touch event.
+     * 
+     * Only used by getMotionEventTargetsForTouchTd.
+     * Set to true on ACTION_DOWN and set to false on ACTION_UP.
+     */
+    private boolean mTouchDown;
+    
+    /* Current target of Motion events.
+     * 
+     * Only used by getMotionEventTargetsForTouchTd.
+     * Initialized on ACTION_DOWN and cleared on ACTION_UP.
+     */
+    private WindowState mTouchFocus;
 
-        // TODO detect cheek presses somewhere... either here or in native code
+    /* Windows above the target that would like to receive an "outside" touch event
+     * for any down events outside of them.
+     * 
+     * Only used by getMotionEventTargetsForTouchTd.
+     * Initialized on ACTION_DOWN and cleared immediately afterwards.
+     */
+    private ArrayList<WindowState> mOutsideTouchTargets = new ArrayList<WindowState>();
+    
+    /* Wallpaper windows that are currently receiving touch events.
+     * 
+     * Only used by getMotionEventTargetsForTouchTd.
+     * Initialized on ACTION_DOWN and cleared on ACTION_UP.
+     */
+    private ArrayList<WindowState> mWallpaperTouchTargets = new ArrayList<WindowState>();
+    
+    /* Gets the input targets for a touch event.
+     * 
+     * Called by the InputManager on the InputDispatcher thread.
+     */
+    private int getMotionEventTargetsForTouchTd(InputTargetList inputTargets,
+            MotionEvent event, int policyFlags, int injectorPid, int injectorUid) {
+        final int action = event.getAction();
+        
+        if (action == MotionEvent.ACTION_DOWN) {
+            updateTouchFocusBeforeDownTd(event, policyFlags);
+        } else {
+            updateTouchFocusBeforeNonDownTd(event, policyFlags);
+        }
+
+        boolean skipDelivery = false;
+        int touchTargetFlags = 0;
+        
+        int injectionResult = InputManager.INPUT_EVENT_INJECTION_SUCCEEDED;
+        WindowState focusedTouchTarget = mTouchFocus;
+        if (focusedTouchTarget == null) {
+            // In this case we are either dropping the event, or have received
+            // a move or up without a down.  It is common to receive move
+            // events in such a way, since this means the user is moving the
+            // pointer without actually pressing down.  All other cases should
+            // be atypical, so let's log them.
+            if (action != MotionEvent.ACTION_MOVE) {
+                Slog.w(TAG, "No window to dispatch pointer action " + action);
+                injectionResult = InputManager.INPUT_EVENT_INJECTION_FAILED;
+            }
+        } else {
+            // We have a valid focused touch target.
+            if (! checkInjectionPermissionTd(focusedTouchTarget, injectorPid, injectorUid)) {
+                return InputManager.INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+            }
+            
+            wakeupIfNeeded(focusedTouchTarget, eventType(event));
+            
+            if ((focusedTouchTarget.mAttrs.flags &
+                    WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES) != 0) {
+                // Target wants to ignore fat touch events
+                boolean cheekPress = mPolicy.isCheekPressedAgainstScreen(event);
+                
+                if (cheekPress) {
+                    if ((action == MotionEvent.ACTION_DOWN)) {
+                        mFatTouch = true;
+                        skipDelivery = true;
+                    } else {
+                        if (! mFatTouch) {
+                            // cancel the earlier event
+                            touchTargetFlags |= InputTarget.FLAG_CANCEL;
+                            mFatTouch = true;
+                        } else {
+                            skipDelivery = true;
+                        }
+                    }
+                }
+            }
+        }
+        
+        if (! skipDelivery) {
+            int outsideTargetCount = mOutsideTouchTargets.size();
+            for (int i = 0; i < outsideTargetCount; i++) {
+                WindowState outsideTouchTarget = mOutsideTouchTargets.get(i);
+                addInputTargetTd(inputTargets, outsideTouchTarget,
+                        InputTarget.FLAG_OUTSIDE | touchTargetFlags);
+            }
+            
+            int wallpaperTargetCount = mWallpaperTouchTargets.size();
+            for (int i = 0; i < wallpaperTargetCount; i++) {
+                WindowState wallpaperTouchTarget = mWallpaperTouchTargets.get(i);
+                addInputTargetTd(inputTargets, wallpaperTouchTarget,
+                        touchTargetFlags);
+            }
+            
+            if (focusedTouchTarget != null) {
+                addInputTargetTd(inputTargets, focusedTouchTarget,
+                        InputTarget.FLAG_SYNC | touchTargetFlags);
+            }
+        }
+
+        if (action == MotionEvent.ACTION_UP) {
+            updateTouchFocusAfterUpTd(event, policyFlags);
+        }
+        
+        return injectionResult;
+    }
+    
+    private void updateTouchFocusBeforeDownTd(MotionEvent event, int policyFlags) {
+        if (mTouchDown) {
+            // This is weird, we got a down, but we thought it was already down!
+            // XXX: We should probably send an ACTION_UP to the current target.
+            Slog.w(TAG, "Pointer down received while already down in: " + mTouchFocus);
+            updateTouchFocusAfterUpTd(event, policyFlags);
+        }
+        
+        mTouchDown = true;
+        mPowerManager.logPointerDownEvent();
         
         final boolean screenWasOff = (policyFlags & WindowManagerPolicy.FLAG_BRIGHT_HERE) != 0;
-        
-        WindowState target = mTouchFocus;
-        
-        if (action == MotionEvent.ACTION_UP) {
-            // let go of our target
-            mPowerManager.logPointerUpEvent();
-            clearTouchFocus();
-        } else if (action == MotionEvent.ACTION_DOWN) {
-            // acquire a new target
-            mPowerManager.logPointerDownEvent();
-        
-            synchronized (mWindowMap) {
-                if (mTouchFocus != null) {
-                    // this is weird, we got a pen down, but we thought it was
-                    // already down!
-                    // XXX: We should probably send an ACTION_UP to the current
-                    // target.
-                    Slog.w(TAG, "Pointer down received while already down in: "
-                            + mTouchFocus);
-                    clearTouchFocus();
+        synchronized (mWindowMap) {
+            final int x = (int) event.getX();
+            final int y = (int) event.getY();
+
+            final ArrayList windows = mWindows;
+            final int N = windows.size();
+            WindowState topErrWindow = null;
+            final Rect tmpRect = mTempRect;
+            for (int i= N - 1; i >= 0; i--) {
+                WindowState child = (WindowState) windows.get(i);
+                //Slog.i(TAG, "Checking dispatch to: " + child);
+                
+                final int flags = child.mAttrs.flags;
+                if ((flags & WindowManager.LayoutParams.FLAG_SYSTEM_ERROR) != 0) {
+                    if (topErrWindow == null) {
+                        topErrWindow = child;
+                    }
                 }
-
-                // ACTION_DOWN is special, because we need to lock next events to
-                // the window we'll land onto.
-                final int x = (int) event.getX();
-                final int y = (int) event.getY();
-
-                final ArrayList windows = mWindows;
-                final int N = windows.size();
-                WindowState topErrWindow = null;
-                final Rect tmpRect = mTempRect;
-                for (int i=N-1; i>=0; i--) {
-                    WindowState child = (WindowState)windows.get(i);
-                    //Slog.i(TAG, "Checking dispatch to: " + child);
-                    final int flags = child.mAttrs.flags;
-                    if ((flags & WindowManager.LayoutParams.FLAG_SYSTEM_ERROR) != 0) {
-                        if (topErrWindow == null) {
-                            topErrWindow = child;
-                        }
-                    }
-                    if (!child.isVisibleLw()) {
-                        //Slog.i(TAG, "Not visible!");
-                        continue;
-                    }
-                    if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
-                        //Slog.i(TAG, "Not touchable!");
-                        if ((flags & WindowManager.LayoutParams
-                                .FLAG_WATCH_OUTSIDE_TOUCH) != 0) {
-                            child.mNextOutsideTouch = mOutsideTouchTargets;
-                            mOutsideTouchTargets = child;
-                        }
-                        continue;
-                    }
+                
+                if (!child.isVisibleLw()) {
+                    //Slog.i(TAG, "Not visible!");
+                    continue;
+                }
+                
+                if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) == 0) {
                     tmpRect.set(child.mFrame);
                     if (child.mTouchableInsets == ViewTreeObserver
                                 .InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT) {
@@ -5197,7 +5429,7 @@
                         |WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
                     if (tmpRect.contains(x, y) || touchFlags == 0) {
                         //Slog.i(TAG, "Using this target!");
-                        if (!screenWasOff || (flags &
+                        if (! screenWasOff || (flags &
                                 WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING) != 0) {
                             mTouchFocus = child;
                         } else {
@@ -5206,143 +5438,76 @@
                         }
                         break;
                     }
-
-                    if ((flags & WindowManager.LayoutParams
-                            .FLAG_WATCH_OUTSIDE_TOUCH) != 0) {
-                        child.mNextOutsideTouch = mOutsideTouchTargets;
-                        mOutsideTouchTargets = child;
-                        //Slog.i(TAG, "Adding to outside target list: " + child);
-                    }
                 }
 
-                // if there's an error window but it's not accepting
-                // focus (typically because it is not yet visible) just
-                // wait for it -- any other focused window may in fact
-                // be in ANR state.
-                if (topErrWindow != null && mTouchFocus != topErrWindow) {
-                    mTouchFocus = null;
+                if ((flags & WindowManager.LayoutParams
+                        .FLAG_WATCH_OUTSIDE_TOUCH) != 0) {
+                    //Slog.i(TAG, "Adding to outside target list: " + child);
+                    mOutsideTouchTargets.add(child);
                 }
             }
-            
-            target = mTouchFocus;
-        }
-        
-        if (target != null) {
-            wakeupIfNeeded(target, eventType(event));
-        }
-        
-        int targetFlags = 0;
-        if (target == null) {
-            // In this case we are either dropping the event, or have received
-            // a move or up without a down.  It is common to receive move
-            // events in such a way, since this means the user is moving the
-            // pointer without actually pressing down.  All other cases should
-            // be atypical, so let's log them.
-            if (action != MotionEvent.ACTION_MOVE) {
-                Slog.w(TAG, "No window to dispatch pointer action " + action);
-            }
-        } else {
-            if ((target.mAttrs.flags &
-                    WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES) != 0) {
-                //target wants to ignore fat touch events
-                boolean cheekPress = mPolicy.isCheekPressedAgainstScreen(event);
-                //explicit flag to return without processing event further
-                boolean returnFlag = false;
-                if((action == MotionEvent.ACTION_DOWN)) {
-                    mFatTouch = false;
-                    if(cheekPress) {
-                        mFatTouch = true;
-                        returnFlag = true;
-                    }
-                } else {
-                    if(action == MotionEvent.ACTION_UP) {
-                        if(mFatTouch) {
-                            //earlier even was invalid doesnt matter if current up is cheekpress or not
-                            mFatTouch = false;
-                            returnFlag = true;
-                        } else if(cheekPress) {
-                            //cancel the earlier event
-                            targetFlags |= InputTarget.FLAG_CANCEL;
-                            action = MotionEvent.ACTION_CANCEL;
-                        }
-                    } else if(action == MotionEvent.ACTION_MOVE) {
-                        if(mFatTouch) {
-                            //two cases here
-                            //an invalid down followed by 0 or moves(valid or invalid)
-                            //a valid down,  invalid move, more moves. want to ignore till up
-                            returnFlag = true;
-                        } else if(cheekPress) {
-                            //valid down followed by invalid moves
-                            //an invalid move have to cancel earlier action
-                            targetFlags |= InputTarget.FLAG_CANCEL;
-                            action = MotionEvent.ACTION_CANCEL;
-                            if (DEBUG_INPUT) Slog.v(TAG, "Sending cancel for invalid ACTION_MOVE");
-                            //note that the subsequent invalid moves will not get here
-                            mFatTouch = true;
-                        }
-                    }
-                } //else if action
-                if(returnFlag) {
-                    return;
-                }
-            } //end if target
-        }        
-        
-        synchronized (mWindowMap) {
-            if (target != null && ! target.isVisibleLw()) {
-                target = null;
+
+            // If there's an error window but it's not accepting focus (typically because
+            // it is not yet visible) just wait for it -- any other focused window may in fact
+            // be in ANR state.
+            if (topErrWindow != null && mTouchFocus != topErrWindow) {
+                mTouchFocus = null;
             }
             
-            if (action == MotionEvent.ACTION_DOWN) {
-                while (mOutsideTouchTargets != null) {
-                    addInputTarget(inputTargets, mOutsideTouchTargets,
-                            InputTarget.FLAG_OUTSIDE | targetFlags);
-                    mOutsideTouchTargets = mOutsideTouchTargets.mNextOutsideTouch;
-                }
+            // Drop the touch focus if the window is not visible.
+            if (mTouchFocus != null && ! mTouchFocus.isVisibleLw()) {
+                mTouchFocus = null;
             }
             
-            // If we sent an initial down to the wallpaper, then continue
-            // sending events until the final up.
-            // Alternately if we are on top of the wallpaper, then the wallpaper also
-            // gets to see this movement.
-            if (mSendingPointersToWallpaper ||
-                    (target != null && action == MotionEvent.ACTION_DOWN
-                            && mWallpaperTarget == target
-                            && target.mAttrs.type != WindowManager.LayoutParams.TYPE_KEYGUARD)) {
+            // Determine wallpaper targets.
+            if (mTouchFocus != null
+                    && mTouchFocus == mWallpaperTarget
+                    && mTouchFocus.mAttrs.type != WindowManager.LayoutParams.TYPE_KEYGUARD) {
                 int curTokenIndex = mWallpaperTokens.size();
                 while (curTokenIndex > 0) {
                     curTokenIndex--;
                     WindowToken token = mWallpaperTokens.get(curTokenIndex);
+                    
                     int curWallpaperIndex = token.windows.size();
                     while (curWallpaperIndex > 0) {
                         curWallpaperIndex--;
                         WindowState wallpaper = token.windows.get(curWallpaperIndex);
                         if ((wallpaper.mAttrs.flags &
-                                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
-                            continue;
+                                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) == 0) {
+                            mWallpaperTouchTargets.add(wallpaper);
                         }
-                        
-                        switch (action) {
-                            case MotionEvent.ACTION_DOWN:
-                                mSendingPointersToWallpaper = true;
-                                break;
-                            case MotionEvent.ACTION_UP:
-                                mSendingPointersToWallpaper = false;
-                                break;
-                        }
-                        
-                        addInputTarget(inputTargets, wallpaper, targetFlags);
                     }
                 }
             }
-            
-            if (target != null) {
-                addInputTarget(inputTargets, target, InputTarget.FLAG_SYNC | targetFlags);
+        }
+    }
+
+    private void updateTouchFocusBeforeNonDownTd(MotionEvent event, int policyFlags) {
+        synchronized (mWindowMap) {
+            // Drop the touch focus if the window is not visible.
+            if (mTouchFocus != null && ! mTouchFocus.isVisibleLw()) {
+                mTouchFocus = null;
+                mWallpaperTouchTargets.clear();
             }
         }
     }
     
-    private void addInputTarget(InputTargetList inputTargets, WindowState window, int flags) {
+    private void updateTouchFocusAfterUpTd(MotionEvent event, int policyFlags) {
+        mFatTouch = false;
+        mTouchDown = false;
+        mTouchFocus = null;
+        mOutsideTouchTargets.clear();
+        mWallpaperTouchTargets.clear();
+        
+        mPowerManager.logPointerUpEvent();
+    }
+
+    /* Adds a window to a list of input targets.
+     * Do NOT call this method while holding any locks because the call to
+     * appToken.getKeyDispatchingTimeout() can potentially call into the ActivityManager
+     * and create a deadlock hazard.
+     */
+    private void addInputTargetTd(InputTargetList inputTargets, WindowState window, int flags) {
         if (window.mInputChannel == null) {
             return;
         }
@@ -5874,8 +6039,8 @@
         
         final int result;
         if (ENABLE_NATIVE_INPUT_DISPATCH) {
-            result = mInputManager.injectKeyEvent(newEvent,
-                    InputQueue.INPUT_EVENT_NATURE_KEY, sync, pid, uid);
+            result = mInputManager.injectKeyEvent(newEvent, InputQueue.INPUT_EVENT_NATURE_KEY,
+                    pid, uid, sync, INJECTION_TIMEOUT_MILLIS);
         } else {
             result = dispatchKey(newEvent, pid, uid);
             if (sync) {
@@ -5884,14 +6049,7 @@
         }
         
         Binder.restoreCallingIdentity(ident);
-        switch (result) {
-            case INJECT_NO_PERMISSION:
-                throw new SecurityException(
-                        "Injecting to another application requires INJECT_EVENTS permission");
-            case INJECT_SUCCEEDED:
-                return true;
-        }
-        return false;
+        return reportInjectionResult(result);
     }
 
     /**
@@ -5910,8 +6068,8 @@
         
         final int result;
         if (ENABLE_NATIVE_INPUT_DISPATCH) {
-            result = mInputManager.injectMotionEvent(ev,
-                    InputQueue.INPUT_EVENT_NATURE_TOUCH, sync, pid, uid);
+            result = mInputManager.injectMotionEvent(ev, InputQueue.INPUT_EVENT_NATURE_TOUCH,
+                    pid, uid, sync, INJECTION_TIMEOUT_MILLIS);
         } else {
             result = dispatchPointer(null, ev, pid, uid);
             if (sync) {
@@ -5920,14 +6078,7 @@
         }
         
         Binder.restoreCallingIdentity(ident);
-        switch (result) {
-            case INJECT_NO_PERMISSION:
-                throw new SecurityException(
-                        "Injecting to another application requires INJECT_EVENTS permission");
-            case INJECT_SUCCEEDED:
-                return true;
-        }
-        return false;
+        return reportInjectionResult(result);
     }
 
     /**
@@ -5946,8 +6097,8 @@
         
         final int result;
         if (ENABLE_NATIVE_INPUT_DISPATCH) {
-            result = mInputManager.injectMotionEvent(ev,
-                    InputQueue.INPUT_EVENT_NATURE_TRACKBALL, sync, pid, uid);
+            result = mInputManager.injectMotionEvent(ev, InputQueue.INPUT_EVENT_NATURE_TRACKBALL,
+                    pid, uid, sync, INJECTION_TIMEOUT_MILLIS);
         } else {
             result = dispatchTrackball(null, ev, pid, uid);
             if (sync) {
@@ -5956,14 +6107,37 @@
         }
         
         Binder.restoreCallingIdentity(ident);
-        switch (result) {
-            case INJECT_NO_PERMISSION:
-                throw new SecurityException(
-                        "Injecting to another application requires INJECT_EVENTS permission");
-            case INJECT_SUCCEEDED:
-                return true;
+        return reportInjectionResult(result);
+    }
+    
+    private boolean reportInjectionResult(int result) {
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            switch (result) {
+                case InputManager.INPUT_EVENT_INJECTION_PERMISSION_DENIED:
+                    Slog.w(TAG, "Input event injection permission denied.");
+                    throw new SecurityException(
+                            "Injecting to another application requires INJECT_EVENTS permission");
+                case InputManager.INPUT_EVENT_INJECTION_SUCCEEDED:
+                    Slog.v(TAG, "Input event injection succeeded.");
+                    return true;
+                case InputManager.INPUT_EVENT_INJECTION_TIMED_OUT:
+                    Slog.w(TAG, "Input event injection timed out.");
+                    return false;
+                case InputManager.INPUT_EVENT_INJECTION_FAILED:
+                default:
+                    Slog.w(TAG, "Input event injection failed.");
+                    return false;
+            }
+        } else {
+            switch (result) {
+                case INJECT_NO_PERMISSION:
+                    throw new SecurityException(
+                            "Injecting to another application requires INJECT_EVENTS permission");
+                case INJECT_SUCCEEDED:
+                    return true;
+            }
+            return false;
         }
-        return false;
     }
 
     private WindowState getFocusedWindow() {
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index ab3922f..1a6119a 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -50,6 +50,9 @@
     jmethodID isScreenBright;
     jmethodID notifyConfigurationChanged;
     jmethodID notifyLidSwitchChanged;
+    jmethodID notifyInputChannelBroken;
+    jmethodID notifyInputChannelANR;
+    jmethodID notifyInputChannelRecoveredFromANR;
     jmethodID virtualKeyFeedback;
     jmethodID hackInterceptKey;
     jmethodID goToSleep;
@@ -73,6 +76,13 @@
     jfieldID height;
 } gVirtualKeyDefinitionClassInfo;
 
+static struct {
+    jclass clazz;
+
+    jmethodID ctor;
+    jfieldID mArray;
+} gInputTargetListClassInfo;
+
 // ----------------------------------------------------------------------------
 
 class NativeInputManager : public virtual RefBase,
@@ -89,6 +99,10 @@
     void setDisplaySize(int32_t displayId, int32_t width, int32_t height);
     void setDisplayOrientation(int32_t displayId, int32_t orientation);
 
+    status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel,
+            jweak inputChannelObjWeak);
+    status_t unregisterInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel);
+
     /* --- InputReaderPolicyInterface implementation --- */
 
     virtual bool getDisplayInfo(int32_t displayId,
@@ -112,18 +126,20 @@
 
     virtual void notifyConfigurationChanged(nsecs_t when);
     virtual void notifyInputChannelBroken(const sp<InputChannel>& inputChannel);
-    virtual void notifyInputChannelANR(const sp<InputChannel>& inputChannel);
+    virtual bool notifyInputChannelANR(const sp<InputChannel>& inputChannel,
+            nsecs_t& outNewTimeout);
     virtual void notifyInputChannelRecoveredFromANR(const sp<InputChannel>& inputChannel);
     virtual nsecs_t getKeyRepeatTimeout();
-    virtual void getKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags,
-            Vector<InputTarget>& outTargets);
-    virtual void getMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
-            Vector<InputTarget>& outTargets);
+    virtual int32_t getKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags,
+            int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets);
+    virtual int32_t getMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
+            int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets);
 
 private:
     sp<InputManager> mInputManager;
 
     jobject mCallbacksObj;
+    jobject mReusableInputTargetListObj;
 
     // Cached filtering policies.
     int32_t mFilterTouchEvents;
@@ -138,12 +154,20 @@
     bool isScreenOn();
     bool isScreenBright();
 
+    // Weak references to all currently registered input channels by receive fd.
+    Mutex mInputChannelRegistryLock;
+    KeyedVector<int, jweak> mInputChannelObjWeakByReceiveFd;
+
+    jobject getInputChannelObjLocal(JNIEnv* env, const sp<InputChannel>& inputChannel);
+
     static inline JNIEnv* jniEnv() {
         return AndroidRuntime::getJNIEnv();
     }
 
     static bool isAppSwitchKey(int32_t keyCode);
-    static bool checkExceptionFromCallback(JNIEnv* env, const char* methodName);
+    static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
+    static void populateInputTargets(JNIEnv* env, jobject inputTargetListObj,
+            Vector<InputTarget>& outTargets);
 };
 
 // ----------------------------------------------------------------------------
@@ -155,6 +179,11 @@
 
     mCallbacksObj = env->NewGlobalRef(callbacksObj);
 
+    jobject inputTargetListObj = env->NewObject(gInputTargetListClassInfo.clazz,
+            gInputTargetListClassInfo.ctor);
+    mReusableInputTargetListObj = env->NewGlobalRef(inputTargetListObj);
+    env->DeleteLocalRef(inputTargetListObj);
+
     sp<EventHub> eventHub = new EventHub();
     mInputManager = new InputManager(eventHub, this, this);
 }
@@ -163,13 +192,14 @@
     JNIEnv* env = jniEnv();
 
     env->DeleteGlobalRef(mCallbacksObj);
+    env->DeleteGlobalRef(mReusableInputTargetListObj);
 }
 
 bool NativeInputManager::isAppSwitchKey(int32_t keyCode) {
     return keyCode == KEYCODE_HOME || keyCode == KEYCODE_ENDCALL;
 }
 
-bool NativeInputManager::checkExceptionFromCallback(JNIEnv* env, const char* methodName) {
+bool NativeInputManager::checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
     if (env->ExceptionCheck()) {
         LOGE("An exception was thrown by callback '%s'.", methodName);
         LOGE_EX(env);
@@ -196,6 +226,86 @@
     }
 }
 
+status_t NativeInputManager::registerInputChannel(JNIEnv* env,
+        const sp<InputChannel>& inputChannel, jobject inputChannelObj) {
+    jweak inputChannelObjWeak = env->NewWeakGlobalRef(inputChannelObj);
+    if (! inputChannelObjWeak) {
+        LOGE("Could not create weak reference for input channel.");
+        LOGE_EX(env);
+        return NO_MEMORY;
+    }
+
+    status_t status;
+    {
+        AutoMutex _l(mInputChannelRegistryLock);
+
+        ssize_t index = mInputChannelObjWeakByReceiveFd.indexOfKey(
+                inputChannel->getReceivePipeFd());
+        if (index >= 0) {
+            LOGE("Input channel object '%s' has already been registered",
+                    inputChannel->getName().string());
+            status = INVALID_OPERATION;
+            goto DeleteWeakRef;
+        }
+
+        mInputChannelObjWeakByReceiveFd.add(inputChannel->getReceivePipeFd(),
+                inputChannelObjWeak);
+    }
+
+    status = mInputManager->registerInputChannel(inputChannel);
+    if (! status) {
+        return OK;
+    }
+
+    {
+        AutoMutex _l(mInputChannelRegistryLock);
+        mInputChannelObjWeakByReceiveFd.removeItem(inputChannel->getReceivePipeFd());
+    }
+
+DeleteWeakRef:
+    env->DeleteWeakGlobalRef(inputChannelObjWeak);
+    return status;
+}
+
+status_t NativeInputManager::unregisterInputChannel(JNIEnv* env,
+        const sp<InputChannel>& inputChannel) {
+    jweak inputChannelObjWeak;
+    {
+        AutoMutex _l(mInputChannelRegistryLock);
+
+        ssize_t index = mInputChannelObjWeakByReceiveFd.indexOfKey(
+                inputChannel->getReceivePipeFd());
+        if (index < 0) {
+            LOGE("Input channel object '%s' is not currently registered",
+                    inputChannel->getName().string());
+            return INVALID_OPERATION;
+        }
+
+        inputChannelObjWeak = mInputChannelObjWeakByReceiveFd.valueAt(index);
+        mInputChannelObjWeakByReceiveFd.removeItemsAt(index);
+    }
+
+    env->DeleteWeakGlobalRef(inputChannelObjWeak);
+
+    return mInputManager->unregisterInputChannel(inputChannel);
+}
+
+jobject NativeInputManager::getInputChannelObjLocal(JNIEnv* env,
+        const sp<InputChannel>& inputChannel) {
+    {
+        AutoMutex _l(mInputChannelRegistryLock);
+
+        ssize_t index = mInputChannelObjWeakByReceiveFd.indexOfKey(
+                inputChannel->getReceivePipeFd());
+        if (index < 0) {
+            return NULL;
+        }
+
+        jweak inputChannelObjWeak = mInputChannelObjWeakByReceiveFd.valueAt(index);
+        return env->NewLocalRef(inputChannelObjWeak);
+    }
+}
+
 bool NativeInputManager::getDisplayInfo(int32_t displayId,
         int32_t* width, int32_t* height, int32_t* orientation) {
     bool result = false;
@@ -216,7 +326,7 @@
     JNIEnv* env = jniEnv();
 
     jboolean result = env->CallBooleanMethod(mCallbacksObj, gCallbacksClassInfo.isScreenOn);
-    if (checkExceptionFromCallback(env, "isScreenOn")) {
+    if (checkAndClearExceptionFromCallback(env, "isScreenOn")) {
         return true;
     }
     return result;
@@ -226,7 +336,7 @@
     JNIEnv* env = jniEnv();
 
     jboolean result = env->CallBooleanMethod(mCallbacksObj, gCallbacksClassInfo.isScreenBright);
-    if (checkExceptionFromCallback(env, "isScreenBright")) {
+    if (checkAndClearExceptionFromCallback(env, "isScreenBright")) {
         return true;
     }
     return result;
@@ -245,7 +355,7 @@
 
     env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.virtualKeyFeedback,
             when, deviceId, action, flags, keyCode, scanCode, metaState, downTime);
-    checkExceptionFromCallback(env, "virtualKeyFeedback");
+    checkAndClearExceptionFromCallback(env, "virtualKeyFeedback");
 }
 
 int32_t NativeInputManager::interceptKey(nsecs_t when,
@@ -267,7 +377,7 @@
 
     jint wmActions = env->CallIntMethod(mCallbacksObj, gCallbacksClassInfo.hackInterceptKey,
             deviceId, EV_KEY, scanCode, keyCode, policyFlags, down ? 1 : 0, when, isScreenOn);
-    if (checkExceptionFromCallback(env, "hackInterceptKey")) {
+    if (checkAndClearExceptionFromCallback(env, "hackInterceptKey")) {
         wmActions = 0;
     }
 
@@ -284,12 +394,12 @@
 
     if (wmActions & WM_ACTION_GO_TO_SLEEP) {
         env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.goToSleep, when);
-        checkExceptionFromCallback(env, "goToSleep");
+        checkAndClearExceptionFromCallback(env, "goToSleep");
     }
 
     if (wmActions & WM_ACTION_POKE_USER_ACTIVITY) {
         env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.pokeUserActivityForKey, when);
-        checkExceptionFromCallback(env, "pokeUserActivityForKey");
+        checkAndClearExceptionFromCallback(env, "pokeUserActivityForKey");
     }
 
     if (wmActions & WM_ACTION_PASS_TO_USER) {
@@ -297,7 +407,7 @@
 
         if (down && isAppSwitchKey(keyCode)) {
             env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyAppSwitchComing);
-            checkExceptionFromCallback(env, "notifyAppSwitchComing");
+            checkAndClearExceptionFromCallback(env, "notifyAppSwitchComing");
 
             actions |= InputReaderPolicyInterface::ACTION_APP_SWITCH_COMING;
         }
@@ -358,7 +468,7 @@
     case SW_LID:
         env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyLidSwitchChanged,
                 when, switchValue == 0);
-        checkExceptionFromCallback(env, "notifyLidSwitchChanged");
+        checkAndClearExceptionFromCallback(env, "notifyLidSwitchChanged");
         break;
     }
 
@@ -371,7 +481,7 @@
 
         jboolean result = env->CallBooleanMethod(mCallbacksObj,
                 gCallbacksClassInfo.filterTouchEvents);
-        if (checkExceptionFromCallback(env, "filterTouchEvents")) {
+        if (checkAndClearExceptionFromCallback(env, "filterTouchEvents")) {
             result = false;
         }
 
@@ -386,7 +496,7 @@
 
         jboolean result = env->CallBooleanMethod(mCallbacksObj,
                 gCallbacksClassInfo.filterJumpyTouchEvents);
-        if (checkExceptionFromCallback(env, "filterJumpyTouchEvents")) {
+        if (checkAndClearExceptionFromCallback(env, "filterJumpyTouchEvents")) {
             result = false;
         }
 
@@ -400,10 +510,10 @@
     JNIEnv* env = jniEnv();
 
     jstring deviceNameStr = env->NewStringUTF(deviceName.string());
-    if (! checkExceptionFromCallback(env, "getVirtualKeyDefinitions")) {
+    if (! checkAndClearExceptionFromCallback(env, "getVirtualKeyDefinitions")) {
         jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacksObj,
                 gCallbacksClassInfo.getVirtualKeyDefinitions, deviceNameStr));
-        if (! checkExceptionFromCallback(env, "getVirtualKeyDefinitions") && result) {
+        if (! checkAndClearExceptionFromCallback(env, "getVirtualKeyDefinitions") && result) {
             jsize length = env->GetArrayLength(result);
             for (jsize i = 0; i < length; i++) {
                 jobject item = env->GetObjectArrayElement(result, i);
@@ -433,7 +543,7 @@
 
     jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacksObj,
             gCallbacksClassInfo.getExcludedDeviceNames));
-    if (! checkExceptionFromCallback(env, "getExcludedDeviceNames") && result) {
+    if (! checkAndClearExceptionFromCallback(env, "getExcludedDeviceNames") && result) {
         jsize length = env->GetArrayLength(result);
         for (jsize i = 0; i < length; i++) {
             jstring item = jstring(env->GetObjectArrayElement(result, i));
@@ -460,7 +570,7 @@
 
     env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyConfigurationChanged,
             when, config.touchScreen, config.keyboard, config.navigation);
-    checkExceptionFromCallback(env, "notifyConfigurationChanged");
+    checkAndClearExceptionFromCallback(env, "notifyConfigurationChanged");
 }
 
 void NativeInputManager::notifyInputChannelBroken(const sp<InputChannel>& inputChannel) {
@@ -468,16 +578,47 @@
     LOGD("notifyInputChannelBroken - inputChannel='%s'", inputChannel->getName().string());
 #endif
 
-    // TODO
+    JNIEnv* env = jniEnv();
+
+    jobject inputChannelObjLocal = getInputChannelObjLocal(env, inputChannel);
+    if (inputChannelObjLocal) {
+        env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyInputChannelBroken,
+                inputChannelObjLocal);
+        checkAndClearExceptionFromCallback(env, "notifyInputChannelBroken");
+
+        env->DeleteLocalRef(inputChannelObjLocal);
+    }
 }
 
-void NativeInputManager::notifyInputChannelANR(const sp<InputChannel>& inputChannel) {
+bool NativeInputManager::notifyInputChannelANR(const sp<InputChannel>& inputChannel,
+        nsecs_t& outNewTimeout) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
     LOGD("notifyInputChannelANR - inputChannel='%s'",
             inputChannel->getName().string());
 #endif
 
-    // TODO
+    JNIEnv* env = jniEnv();
+
+    jlong newTimeout;
+    jobject inputChannelObjLocal = getInputChannelObjLocal(env, inputChannel);
+    if (inputChannelObjLocal) {
+        newTimeout = env->CallLongMethod(mCallbacksObj,
+                gCallbacksClassInfo.notifyInputChannelANR, inputChannelObjLocal);
+        if (checkAndClearExceptionFromCallback(env, "notifyInputChannelANR")) {
+            newTimeout = -2;
+        }
+
+        env->DeleteLocalRef(inputChannelObjLocal);
+    } else {
+        newTimeout = -2;
+    }
+
+    if (newTimeout == -2) {
+        return false; // abort
+    }
+
+    outNewTimeout = newTimeout;
+    return true; // resume
 }
 
 void NativeInputManager::notifyInputChannelRecoveredFromANR(const sp<InputChannel>& inputChannel) {
@@ -486,7 +627,16 @@
             inputChannel->getName().string());
 #endif
 
-    // TODO
+    JNIEnv* env = jniEnv();
+
+    jobject inputChannelObjLocal = getInputChannelObjLocal(env, inputChannel);
+    if (inputChannelObjLocal) {
+        env->CallVoidMethod(mCallbacksObj, gCallbacksClassInfo.notifyInputChannelRecoveredFromANR,
+                inputChannelObjLocal);
+        checkAndClearExceptionFromCallback(env, "notifyInputChannelRecoveredFromANR");
+
+        env->DeleteLocalRef(inputChannelObjLocal);
+    }
 }
 
 nsecs_t NativeInputManager::getKeyRepeatTimeout() {
@@ -499,73 +649,83 @@
     }
 }
 
-void NativeInputManager::getKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags,
-        Vector<InputTarget>& outTargets) {
+int32_t NativeInputManager::getKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags,
+        int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
-    LOGD("getKeyEventTargets - policyFlags=%d", policyFlags);
+    LOGD("getKeyEventTargets - policyFlags=%d, injectorPid=%d, injectorUid=%d",
+            policyFlags, injectorPid, injectorUid);
 #endif
 
     JNIEnv* env = jniEnv();
 
+    jint injectionResult;
     jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
     if (! keyEventObj) {
         LOGE("Could not obtain DVM KeyEvent object to get key event targets.");
+        injectionResult = INPUT_EVENT_INJECTION_FAILED;
     } else {
-        jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacksObj,
-                gCallbacksClassInfo.getKeyEventTargets,
-                keyEventObj, jint(keyEvent->getNature()), jint(policyFlags)));
-        if (! checkExceptionFromCallback(env, "getKeyEventTargets") && result) {
-            jsize length = env->GetArrayLength(result);
-            for (jsize i = 0; i < length; i++) {
-                jobject item = env->GetObjectArrayElement(result, i);
-                if (! item) {
-                    break; // found null element indicating end of used portion of the array
-                }
-
-                outTargets.add();
-                android_view_InputTarget_toNative(env, item, & outTargets.editTop());
-
-                env->DeleteLocalRef(item);
-            }
-            env->DeleteLocalRef(result);
+        jint injectionResult = env->CallIntMethod(mCallbacksObj,
+                gCallbacksClassInfo.getKeyEventTargets, mReusableInputTargetListObj,
+                keyEventObj, jint(keyEvent->getNature()), jint(policyFlags),
+                jint(injectorPid), jint(injectorUid));
+        if (checkAndClearExceptionFromCallback(env, "getKeyEventTargets")) {
+            injectionResult = INPUT_EVENT_INJECTION_FAILED;
+        } else {
+            populateInputTargets(env, mReusableInputTargetListObj, outTargets);
         }
         env->DeleteLocalRef(keyEventObj);
     }
+    return injectionResult;
 }
 
-void NativeInputManager::getMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
-        Vector<InputTarget>& outTargets) {
+int32_t NativeInputManager::getMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
+        int32_t injectorPid, int32_t injectorUid, Vector<InputTarget>& outTargets) {
 #if DEBUG_INPUT_DISPATCHER_POLICY
-    LOGD("getMotionEventTargets - policyFlags=%d", policyFlags);
+    LOGD("getMotionEventTargets - policyFlags=%d, injectorPid=%d, injectorUid=%d",
+            policyFlags, injectorPid, injectorUid);
 #endif
 
     JNIEnv* env = jniEnv();
 
+    jint injectionResult;
     jobject motionEventObj = android_view_MotionEvent_fromNative(env, motionEvent);
     if (! motionEventObj) {
         LOGE("Could not obtain DVM MotionEvent object to get key event targets.");
+        injectionResult = INPUT_EVENT_INJECTION_FAILED;
     } else {
-        jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacksObj,
-                gCallbacksClassInfo.getMotionEventTargets,
-                motionEventObj, jint(motionEvent->getNature()), jint(policyFlags)));
-        if (! checkExceptionFromCallback(env, "getMotionEventTargets") && result) {
-            jsize length = env->GetArrayLength(result);
-            for (jsize i = 0; i < length; i++) {
-                jobject item = env->GetObjectArrayElement(result, i);
-                if (! item) {
-                    break; // found null element indicating end of used portion of the array
-                }
-
-                outTargets.add();
-                android_view_InputTarget_toNative(env, item, & outTargets.editTop());
-
-                env->DeleteLocalRef(item);
-            }
-            env->DeleteLocalRef(result);
+        jint injectionResult = env->CallIntMethod(mCallbacksObj,
+                gCallbacksClassInfo.getMotionEventTargets, mReusableInputTargetListObj,
+                motionEventObj, jint(motionEvent->getNature()), jint(policyFlags),
+                jint(injectorPid), jint(injectorUid));
+        if (checkAndClearExceptionFromCallback(env, "getMotionEventTargets")) {
+            injectionResult = INPUT_EVENT_INJECTION_FAILED;
+        } else {
+            populateInputTargets(env, mReusableInputTargetListObj, outTargets);
         }
         android_view_MotionEvent_recycle(env, motionEventObj);
         env->DeleteLocalRef(motionEventObj);
     }
+    return injectionResult;
+}
+
+void NativeInputManager::populateInputTargets(JNIEnv* env, jobject inputTargetListObj,
+        Vector<InputTarget>& outTargets) {
+    jobjectArray inputTargetArray = jobjectArray(env->GetObjectField(
+            inputTargetListObj, gInputTargetListClassInfo.mArray));
+
+    jsize length = env->GetArrayLength(inputTargetArray);
+    for (jsize i = 0; i < length; i++) {
+        jobject item = env->GetObjectArrayElement(inputTargetArray, i);
+        if (! item) {
+            break; // found null element indicating end of used portion of the array
+        }
+
+        outTargets.add();
+        android_view_InputTarget_toNative(env, item, & outTargets.editTop());
+
+        env->DeleteLocalRef(item);
+    }
+    env->DeleteLocalRef(inputTargetArray);
 }
 
 
@@ -686,7 +846,7 @@
             "the input manager!", inputChannel->getName().string());
 
     if (gNativeInputManager != NULL) {
-        gNativeInputManager->getInputManager()->unregisterInputChannel(inputChannel);
+        gNativeInputManager->unregisterInputChannel(env, inputChannel);
     }
 }
 
@@ -703,7 +863,9 @@
         return;
     }
 
-    status_t status = gNativeInputManager->getInputManager()->registerInputChannel(inputChannel);
+
+    status_t status = gNativeInputManager->registerInputChannel(
+            env, inputChannel, inputChannelObj);
     if (status) {
         jniThrowRuntimeException(env, "Failed to register input channel.  "
                 "Check logs for details.");
@@ -729,13 +891,41 @@
 
     android_view_InputChannel_setDisposeCallback(env, inputChannelObj, NULL, NULL);
 
-    status_t status = gNativeInputManager->getInputManager()->unregisterInputChannel(inputChannel);
+    status_t status = gNativeInputManager->unregisterInputChannel(env, inputChannel);
     if (status) {
         jniThrowRuntimeException(env, "Failed to unregister input channel.  "
                 "Check logs for details.");
     }
 }
 
+static jint android_server_InputManager_nativeInjectKeyEvent(JNIEnv* env, jclass clazz,
+        jobject keyEventObj, jint nature, jint injectorPid, jint injectorUid,
+        jboolean sync, jint timeoutMillis) {
+    if (checkInputManagerUnitialized(env)) {
+        return INPUT_EVENT_INJECTION_FAILED;
+    }
+
+    KeyEvent keyEvent;
+    android_view_KeyEvent_toNative(env, keyEventObj, nature, & keyEvent);
+
+    return gNativeInputManager->getInputManager()->injectInputEvent(& keyEvent,
+            injectorPid, injectorUid, sync, timeoutMillis);
+}
+
+static jint android_server_InputManager_nativeInjectMotionEvent(JNIEnv* env, jclass clazz,
+        jobject motionEventObj, jint nature, jint injectorPid, jint injectorUid,
+        jboolean sync, jint timeoutMillis) {
+    if (checkInputManagerUnitialized(env)) {
+        return INPUT_EVENT_INJECTION_FAILED;
+    }
+
+    MotionEvent motionEvent;
+    android_view_MotionEvent_toNative(env, motionEventObj, nature, & motionEvent);
+
+    return gNativeInputManager->getInputManager()->injectInputEvent(& motionEvent,
+            injectorPid, injectorUid, sync, timeoutMillis);
+}
+
 // ----------------------------------------------------------------------------
 
 static JNINativeMethod gInputManagerMethods[] = {
@@ -759,7 +949,11 @@
     { "nativeRegisterInputChannel", "(Landroid/view/InputChannel;)V",
             (void*) android_server_InputManager_nativeRegisterInputChannel },
     { "nativeUnregisterInputChannel", "(Landroid/view/InputChannel;)V",
-            (void*) android_server_InputManager_nativeUnregisterInputChannel }
+            (void*) android_server_InputManager_nativeUnregisterInputChannel },
+    { "nativeInjectKeyEvent", "(Landroid/view/KeyEvent;IIIZI)I",
+            (void*) android_server_InputManager_nativeInjectKeyEvent },
+    { "nativeInjectMotionEvent", "(Landroid/view/MotionEvent;IIIZI)I",
+            (void*) android_server_InputManager_nativeInjectMotionEvent }
 };
 
 #define FIND_CLASS(var, className) \
@@ -796,6 +990,15 @@
     GET_METHOD_ID(gCallbacksClassInfo.notifyLidSwitchChanged, gCallbacksClassInfo.clazz,
             "notifyLidSwitchChanged", "(JZ)V");
 
+    GET_METHOD_ID(gCallbacksClassInfo.notifyInputChannelBroken, gCallbacksClassInfo.clazz,
+            "notifyInputChannelBroken", "(Landroid/view/InputChannel;)V");
+
+    GET_METHOD_ID(gCallbacksClassInfo.notifyInputChannelANR, gCallbacksClassInfo.clazz,
+            "notifyInputChannelANR", "(Landroid/view/InputChannel;)J");
+
+    GET_METHOD_ID(gCallbacksClassInfo.notifyInputChannelRecoveredFromANR, gCallbacksClassInfo.clazz,
+            "notifyInputChannelRecoveredFromANR", "(Landroid/view/InputChannel;)V");
+
     GET_METHOD_ID(gCallbacksClassInfo.virtualKeyFeedback, gCallbacksClassInfo.clazz,
             "virtualKeyFeedback", "(JIIIIIIJ)V");
 
@@ -825,10 +1028,12 @@
             "getExcludedDeviceNames", "()[Ljava/lang/String;");
 
     GET_METHOD_ID(gCallbacksClassInfo.getKeyEventTargets, gCallbacksClassInfo.clazz,
-            "getKeyEventTargets", "(Landroid/view/KeyEvent;II)[Landroid/view/InputTarget;");
+            "getKeyEventTargets",
+            "(Lcom/android/server/InputTargetList;Landroid/view/KeyEvent;IIII)I");
 
     GET_METHOD_ID(gCallbacksClassInfo.getMotionEventTargets, gCallbacksClassInfo.clazz,
-            "getMotionEventTargets", "(Landroid/view/MotionEvent;II)[Landroid/view/InputTarget;");
+            "getMotionEventTargets",
+            "(Lcom/android/server/InputTargetList;Landroid/view/MotionEvent;IIII)I");
 
     // VirtualKeyDefinition
 
@@ -850,6 +1055,16 @@
     GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.height, gVirtualKeyDefinitionClassInfo.clazz,
             "height", "I");
 
+    // InputTargetList
+
+    FIND_CLASS(gInputTargetListClassInfo.clazz, "com/android/server/InputTargetList");
+
+    GET_METHOD_ID(gInputTargetListClassInfo.ctor, gInputTargetListClassInfo.clazz,
+            "<init>", "()V");
+
+    GET_FIELD_ID(gInputTargetListClassInfo.mArray, gInputTargetListClassInfo.clazz,
+            "mArray", "[Landroid/view/InputTarget;");
+
     return 0;
 }