Native input dispatch rewrite work in progress.

The old dispatch mechanism has been left in place and continues to
be used by default for now.  To enable native input dispatch,
edit the ENABLE_NATIVE_DISPATCH constant in WindowManagerPolicy.

Includes part of the new input event NDK API.  Some details TBD.

To wire up input dispatch, as the ViewRoot adds a window to the
window session it receives an InputChannel object as an output
argument.  The InputChannel encapsulates the file descriptors for a
shared memory region and two pipe end-points.  The ViewRoot then
provides the InputChannel to the InputQueue.  Behind the
scenes, InputQueue simply attaches handlers to the native PollLoop object
that underlies the MessageQueue.  This way MessageQueue doesn't need
to know anything about input dispatch per-se, it just exposes (in native
code) a PollLoop that other components can use to monitor file descriptor
state changes.

There can be zero or more targets for any given input event.  Each
input target is specified by its input channel and some parameters
including flags, an X/Y coordinate offset, and the dispatch timeout.
An input target can request either synchronous dispatch (for foreground apps)
or asynchronous dispatch (fire-and-forget for wallpapers and "outside"
targets).  Currently, finding the appropriate input targets for an event
requires a call back into the WindowManagerServer from native code.
In the future this will be refactored to avoid most of these callbacks
except as required to handle pending focus transitions.

End-to-end event dispatch mostly works!

To do: event injection, rate limiting, ANRs, testing, optimization, etc.

Change-Id: I8c36b2b9e0a2d27392040ecda0f51b636456de25
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index d394a46..df30c76 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -16,13 +16,11 @@
 
 package android.os;
 
-import java.util.ArrayList;
-
 import android.util.AndroidRuntimeException;
 import android.util.Config;
 import android.util.Log;
 
-import com.android.internal.os.RuntimeInit;
+import java.util.ArrayList;
 
 /**
  * Low-level class holding the list of messages to be dispatched by a
@@ -34,11 +32,18 @@
  */
 public class MessageQueue {
     Message mMessages;
-    private final ArrayList mIdleHandlers = new ArrayList();
+    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
     private boolean mQuiting = false;
-    private int mObject = 0;    // used by native code
     boolean mQuitAllowed = true;
 
+    @SuppressWarnings("unused")
+    private int mPtr; // used by native code
+    
+    private native void nativeInit();
+    private native void nativeDestroy();
+    private native boolean nativePollOnce(int timeoutMillis);
+    private native void nativeWake();
+
     /**
      * Callback interface for discovering when a thread is going to block
      * waiting for more messages.
@@ -85,55 +90,39 @@
             mIdleHandlers.remove(handler);
         }
     }
-
-    // Add an input pipe to the set being selected over.  If token is
-    // negative, remove 'handler's entry from the current set and forget
-    // about it.
-    void setInputToken(int token, int region, Handler handler) {
-        if (token >= 0) nativeRegisterInputStream(token, region, handler);
-        else nativeUnregisterInputStream(token);
-    }
-
+    
     MessageQueue() {
         nativeInit();
     }
-    private native void nativeInit();
-
-    /**
-     * @param token fd of the readable end of the input stream
-     * @param region fd of the ashmem region used for data transport alongside the 'token' fd
-     * @param handler Handler from which to make input messages based on data read from the fd
-     */
-    private native void nativeRegisterInputStream(int token, int region, Handler handler);
-    private native void nativeUnregisterInputStream(int token);
-    private native void nativeSignal();
-
-    /**
-     * Wait until the designated time for new messages to arrive.
-     *
-     * @param when Timestamp in SystemClock.uptimeMillis() base of the next message in the queue.
-     *    If 'when' is zero, the method will check for incoming messages without blocking.  If
-     *    'when' is negative, the method will block forever waiting for the next message.
-     * @return
-     */
-    private native int nativeWaitForNext(long when);
+    
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            nativeDestroy();
+        } finally {
+            super.finalize();
+        }
+    }
 
     final Message next() {
         boolean tryIdle = true;
         // when we start out, we'll just touch the input pipes and then go from there
-        long timeToNextEventMillis = 0;
+        int timeToNextEventMillis = 0;
 
         while (true) {
             long now;
             Object[] idlers = null;
 
-            nativeWaitForNext(timeToNextEventMillis);
+            boolean dispatched = nativePollOnce(timeToNextEventMillis);
 
             // Try to retrieve the next message, returning if found.
             synchronized (this) {
                 now = SystemClock.uptimeMillis();
                 Message msg = pullNextLocked(now);
-                if (msg != null) return msg;
+                if (msg != null) {
+                    return msg;
+                }
+                
                 if (tryIdle && mIdleHandlers.size() > 0) {
                     idlers = mIdleHandlers.toArray();
                 }
@@ -170,9 +159,14 @@
             synchronized (this) {
                 // No messages, nobody to tell about it...  time to wait!
                 if (mMessages != null) {
-                    if (mMessages.when - now > 0) {
+                    long longTimeToNextEventMillis = mMessages.when - now;
+                    
+                    if (longTimeToNextEventMillis > 0) {
                         Binder.flushPendingCommands();
-                        timeToNextEventMillis = mMessages.when - now;
+                        timeToNextEventMillis = (int) Math.min(longTimeToNextEventMillis,
+                                Integer.MAX_VALUE);
+                    } else {
+                        timeToNextEventMillis = 0;
                     }
                 } else {
                     Binder.flushPendingCommands();
@@ -230,7 +224,7 @@
                 msg.next = prev.next;
                 prev.next = msg;
             }
-            nativeSignal();
+            nativeWake();
         }
         return true;
     }
@@ -351,7 +345,7 @@
     void poke()
     {
         synchronized (this) {
-            nativeSignal();
+            nativeWake();
         }
     }
 }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 2ade44e..3f5d6ca 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -18,6 +18,7 @@
 
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.view.BaseIWindow;
+import com.android.internal.view.BaseInputHandler;
 import com.android.internal.view.BaseSurfaceHolder;
 
 import android.annotation.SdkConstant;
@@ -39,6 +40,10 @@
 import android.util.LogPrinter;
 import android.view.Gravity;
 import android.view.IWindowSession;
+import android.view.InputChannel;
+import android.view.InputHandler;
+import android.view.InputQueue;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.SurfaceHolder;
 import android.view.View;
@@ -46,6 +51,7 @@
 import android.view.ViewRoot;
 import android.view.WindowManager;
 import android.view.WindowManagerImpl;
+import android.view.WindowManagerPolicy;
 
 import java.util.ArrayList;
 
@@ -146,6 +152,7 @@
         final WindowManager.LayoutParams mLayout
                 = new WindowManager.LayoutParams();
         IWindowSession mSession;
+        InputChannel mInputChannel;
 
         final Object mLock = new Object();
         boolean mOffsetMessageEnqueued;
@@ -205,6 +212,30 @@
             
         };
         
+        final InputHandler mInputHandler = new BaseInputHandler() {
+            @Override
+            public void handleTouch(MotionEvent event, Runnable finishedCallback) {
+                try {
+                    synchronized (mLock) {
+                        if (event.getAction() == MotionEvent.ACTION_MOVE) {
+                            if (mPendingMove != null) {
+                                mCaller.removeMessages(MSG_TOUCH_EVENT, mPendingMove);
+                                mPendingMove.recycle();
+                            }
+                            mPendingMove = event;
+                        } else {
+                            mPendingMove = null;
+                        }
+                        Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT,
+                                event);
+                        mCaller.sendMessage(msg);
+                    }
+                } finally {
+                    finishedCallback.run();
+                }
+            }
+        };
+        
         final BaseIWindow mWindow = new BaseIWindow() {
             @Override
             public boolean onDispatchPointer(MotionEvent event, long eventTime,
@@ -487,8 +518,15 @@
                         mLayout.setTitle(WallpaperService.this.getClass().getName());
                         mLayout.windowAnimations =
                                 com.android.internal.R.style.Animation_Wallpaper;
-                        mSession.add(mWindow, mLayout, View.VISIBLE, mContentInsets);
+                        mInputChannel = new InputChannel();
+                        mSession.add(mWindow, mLayout, View.VISIBLE, mContentInsets,
+                                mInputChannel);
                         mCreated = true;
+
+                        if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) {
+                            InputQueue.registerInputChannel(mInputChannel, mInputHandler,
+                                    Looper.myQueue());
+                        }
                     }
                     
                     mSurfaceHolder.mSurfaceLock.lock();
@@ -587,6 +625,7 @@
             mSurfaceHolder.setSizeFromLayout();
             mInitializing = true;
             mSession = ViewRoot.getWindowSession(getMainLooper());
+            
             mWindow.setSession(mSession);
             
             IntentFilter filter = new IntentFilter();
@@ -730,6 +769,15 @@
                 try {
                     if (DEBUG) Log.v(TAG, "Removing window and destroying surface "
                             + mSurfaceHolder.getSurface() + " of: " + this);
+                    
+                    if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) {
+                        if (mInputChannel != null) {
+                            InputQueue.unregisterInputChannel(mInputChannel);
+                            mInputChannel.dispose();
+                            mInputChannel = null;
+                        }
+                    }
+                    
                     mSession.remove(mWindow);
                 } catch (RemoteException e) {
                 }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 01f07d6..4647fb4 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -21,6 +21,7 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Bundle;
+import android.view.InputChannel;
 import android.view.IWindow;
 import android.view.MotionEvent;
 import android.view.WindowManager;
@@ -33,6 +34,9 @@
  */
 interface IWindowSession {
     int add(IWindow window, in WindowManager.LayoutParams attrs,
+            in int viewVisibility, out Rect outContentInsets,
+            out InputChannel outInputChannel);
+    int addWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs,
             in int viewVisibility, out Rect outContentInsets);
     void remove(IWindow window);
     
diff --git a/core/java/android/view/InputChannel.aidl b/core/java/android/view/InputChannel.aidl
new file mode 100644
index 0000000..74c0aa4
--- /dev/null
+++ b/core/java/android/view/InputChannel.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android/view/InputChannel.aidl
+**
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+package android.view;
+
+parcelable InputChannel;
diff --git a/core/java/android/view/InputChannel.java b/core/java/android/view/InputChannel.java
new file mode 100644
index 0000000..66a83b8
--- /dev/null
+++ b/core/java/android/view/InputChannel.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Slog;
+
+/**
+ * An input channel specifies the file descriptors used to send input events to
+ * a window in another process.  It is Parcelable so that it can be transmitted
+ * to the ViewRoot through a Binder transaction as part of registering the Window.
+ * @hide
+ */
+public class InputChannel implements Parcelable {
+    private static final String TAG = "InputChannel";
+    
+    public static final Parcelable.Creator<InputChannel> CREATOR
+            = new Parcelable.Creator<InputChannel>() {
+        public InputChannel createFromParcel(Parcel source) {
+            InputChannel result = new InputChannel();
+            result.readFromParcel(source);
+            return result;
+        }
+        
+        public InputChannel[] newArray(int size) {
+            return new InputChannel[size];
+        }
+    };
+    
+    @SuppressWarnings("unused")
+    private int mPtr; // used by native code
+    
+    private boolean mDisposeAfterWriteToParcel;
+    
+    private static native InputChannel[] nativeOpenInputChannelPair(String name);
+    
+    private native void nativeDispose(boolean finalized);
+    private native void nativeTransferTo(InputChannel other);
+    private native void nativeReadFromParcel(Parcel parcel);
+    private native void nativeWriteToParcel(Parcel parcel);
+    
+    private native String nativeGetName();
+
+    /**
+     * Creates an uninitialized input channel.
+     * It can be initialized by reading from a Parcel or by transferring the state of
+     * another input channel into this one.
+     */
+    public InputChannel() {
+    }
+    
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            nativeDispose(true);
+        } finally {
+            super.finalize();
+        }
+    }
+    
+    /**
+     * Creates a new input channel pair.  One channel should be provided to the input
+     * dispatcher and the other to the application's input queue.
+     * @param name The descriptive (non-unique) name of the channel pair.
+     * @return A pair of input channels.  They are symmetric and indistinguishable.
+     */
+    public static InputChannel[] openInputChannelPair(String name) {
+        if (name == null) {
+            throw new IllegalArgumentException("name must not be null");
+        }
+        
+        Slog.d(TAG, "Opening input channel pair '" + name + "'");
+        return nativeOpenInputChannelPair(name);
+    }
+    
+    /**
+     * Gets the name of the input channel.
+     * @return The input channel name.
+     */
+    public String getName() {
+        String name = nativeGetName();
+        return name != null ? name : "uninitialized";
+    }
+
+    /**
+     * Disposes the input channel.
+     * Explicitly releases the reference this object is holding on the input channel.
+     * When all references are released, the input channel will be closed.
+     */
+    public void dispose() {
+        nativeDispose(false);
+    }
+    
+    /**
+     * Transfers ownership of the internal state of the input channel to another
+     * instance and invalidates this instance.  This is used to pass an input channel
+     * as an out parameter in a binder call.
+     * @param other The other input channel instance.
+     */
+    public void transferToBinderOutParameter(InputChannel outParameter) {
+        if (outParameter == null) {
+            throw new IllegalArgumentException("outParameter must not be null");
+        }
+        
+        nativeTransferTo(outParameter);
+        outParameter.mDisposeAfterWriteToParcel = true;
+    }
+
+    public int describeContents() {
+        return Parcelable.CONTENTS_FILE_DESCRIPTOR;
+    }
+    
+    public void readFromParcel(Parcel in) {
+        if (in == null) {
+            throw new IllegalArgumentException("in must not be null");
+        }
+        
+        nativeReadFromParcel(in);
+    }
+    
+    public void writeToParcel(Parcel out, int flags) {
+        if (out == null) {
+            throw new IllegalArgumentException("out must not be null");
+        }
+        
+        nativeWriteToParcel(out);
+        
+        if (mDisposeAfterWriteToParcel) {
+            dispose();
+        }
+    }
+    
+    @Override
+    public String toString() {
+        return getName();
+    }
+}
diff --git a/core/java/android/view/InputHandler.java b/core/java/android/view/InputHandler.java
new file mode 100644
index 0000000..816f622
--- /dev/null
+++ b/core/java/android/view/InputHandler.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+/**
+ * Handles input messages that arrive on an input channel.
+ * @hide
+ */
+public interface InputHandler {
+    /**
+     * Handle a key event.
+     * It is the responsibility of the callee to ensure that the finished callback is
+     * eventually invoked when the event processing is finished and the input system
+     * can send the next event.
+     * @param event The key event data.
+     * @param finishedCallback The callback to invoke when event processing is finished.
+     */
+    public void handleKey(KeyEvent event, Runnable finishedCallback);
+    
+    /**
+     * Handle a touch event.
+     * It is the responsibility of the callee to ensure that the finished callback is
+     * eventually invoked when the event processing is finished and the input system
+     * can send the next event.
+     * @param event The motion event data.
+     * @param finishedCallback The callback to invoke when event processing is finished.
+     */
+    public void handleTouch(MotionEvent event, Runnable finishedCallback);
+    
+    /**
+     * Handle a trackball event.
+     * It is the responsibility of the callee to ensure that the finished callback is
+     * eventually invoked when the event processing is finished and the input system
+     * can send the next event.
+     * @param event The motion event data.
+     * @param finishedCallback The callback to invoke when event processing is finished.
+     */
+    public void handleTrackball(MotionEvent event, Runnable finishedCallback);
+}
diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java
new file mode 100644
index 0000000..b38f7d5
--- /dev/null
+++ b/core/java/android/view/InputQueue.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.os.MessageQueue;
+import android.util.Slog;
+
+/**
+ * An input queue provides a mechanism for an application to receive incoming
+ * input events sent over an input channel.  Signalling is implemented by MessageQueue.
+ * @hide
+ */
+public final class InputQueue {
+    private static final String TAG = "InputQueue";
+    
+    // Describes the interpretation of an event.
+    // XXX This concept is tentative.  See comments in android/input.h.
+    public static final int INPUT_EVENT_NATURE_KEY = 1;
+    public static final int INPUT_EVENT_NATURE_TOUCH = 2;
+    public static final int INPUT_EVENT_NATURE_TRACKBALL = 3;
+    
+    private static Object sLock = new Object();
+    
+    private static native void nativeRegisterInputChannel(InputChannel inputChannel,
+            InputHandler inputHandler, MessageQueue messageQueue);
+    private static native void nativeUnregisterInputChannel(InputChannel inputChannel);
+    private static native void nativeFinished(long finishedToken);
+    
+    private InputQueue() {
+    }
+    
+    /**
+     * Registers an input channel and handler.
+     * @param inputChannel The input channel to register.
+     * @param inputHandler The input handler to input events send to the target.
+     * @param messageQueue The message queue on whose thread the handler should be invoked.
+     */
+    public static void registerInputChannel(InputChannel inputChannel, InputHandler inputHandler,
+            MessageQueue messageQueue) {
+        if (inputChannel == null) {
+            throw new IllegalArgumentException("inputChannel must not be null");
+        }
+        if (inputHandler == null) {
+            throw new IllegalArgumentException("inputHandler must not be null");
+        }
+        if (messageQueue == null) {
+            throw new IllegalArgumentException("messageQueue must not be null");
+        }
+        
+        synchronized (sLock) {
+            Slog.d(TAG, "Registering input channel '" + inputChannel + "'");
+            nativeRegisterInputChannel(inputChannel, inputHandler, messageQueue);
+        }
+    }
+    
+    /**
+     * Unregisters an input channel.
+     * Does nothing if the channel is not currently registered.
+     * @param inputChannel The input channel to unregister.
+     */
+    public static void unregisterInputChannel(InputChannel inputChannel) {
+        if (inputChannel == null) {
+            throw new IllegalArgumentException("inputChannel must not be null");
+        }
+
+        synchronized (sLock) {
+            Slog.d(TAG, "Unregistering input channel '" + inputChannel + "'");
+            nativeUnregisterInputChannel(inputChannel);
+        }
+    }
+    
+    @SuppressWarnings("unused")
+    private static void dispatchKeyEvent(InputHandler inputHandler,
+            KeyEvent event, int nature, long finishedToken) {
+        Runnable finishedCallback = new FinishedCallback(finishedToken);
+        
+        if (nature == INPUT_EVENT_NATURE_KEY) {
+            inputHandler.handleKey(event, finishedCallback);
+        } else {
+            Slog.d(TAG, "Unsupported nature for key event: " + nature);
+        }
+    }
+
+    @SuppressWarnings("unused")
+    private static void dispatchMotionEvent(InputHandler inputHandler,
+            MotionEvent event, int nature, long finishedToken) {
+        Runnable finishedCallback = new FinishedCallback(finishedToken);
+        
+        if (nature == INPUT_EVENT_NATURE_TOUCH) {
+            inputHandler.handleTouch(event, finishedCallback);
+        } else if (nature == INPUT_EVENT_NATURE_TRACKBALL) {
+            inputHandler.handleTrackball(event, finishedCallback);
+        } else {
+            Slog.d(TAG, "Unsupported nature for motion event: " + nature);
+        }
+    }
+    
+    // TODO consider recycling finished callbacks when done
+    private static class FinishedCallback implements Runnable {
+        private long mFinishedToken;
+        
+        public FinishedCallback(long finishedToken) {
+            mFinishedToken = finishedToken;
+        }
+        
+        public void run() {
+            synchronized (sLock) {
+                nativeFinished(mFinishedToken);
+            }
+        }
+    }
+}
diff --git a/core/java/android/view/InputTarget.java b/core/java/android/view/InputTarget.java
new file mode 100644
index 0000000..e56e03c
--- /dev/null
+++ b/core/java/android/view/InputTarget.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+/**
+ * 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
+ * window area.
+ * 
+ * These parameters are used by the native input dispatching code.
+ * @hide
+ */
+public class InputTarget {
+    public InputChannel mInputChannel;
+    public int mFlags;
+    public long mTimeoutNanos;
+    public float mXOffset;
+    public float mYOffset;
+    
+    /**
+     * This flag indicates that subsequent event delivery should be held until the
+     * current event is delivered to this target or a timeout occurs.
+     */
+    public static int FLAG_SYNC = 0x01;
+    
+    /**
+     * This flag indicates that a MotionEvent with ACTION_DOWN falls outside of the area of
+     * this target and so should instead be delivered as an ACTION_OUTSIDE to this target.
+     */
+    public static int FLAG_OUTSIDE = 0x02;
+    
+    /*
+     * This flag indicates that a KeyEvent or MotionEvent is being canceled.
+     * In the case of a key event, it should be delivered with KeyEvent.FLAG_CANCELED set.
+     * In the case of a motion event, it should be delivered as MotionEvent.ACTION_CANCEL.
+     */
+    public static int FLAG_CANCEL = 0x04;
+    
+    public void recycle() {
+        mInputChannel = null;
+    }
+}
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 9aa16b5..ae9746e 100755
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -127,6 +127,7 @@
 
     // NOTE: If you add a new keycode here you must also add it to:
     //  isSystem()
+    //  native/include/android/keycodes.h
     //  frameworks/base/include/ui/KeycodeLabels.h
     //  tools/puppet_master/PuppetMaster/nav_keys.py
     //  frameworks/base/core/res/res/values/attrs.xml
@@ -162,7 +163,7 @@
      * key code is not {#link {@link #KEYCODE_UNKNOWN} then the
      * {#link {@link #getRepeatCount()} method returns the number of times
      * the given key code should be executed.
-     * Otherwise, if the key code {@link #KEYCODE_UNKNOWN}, then
+     * Otherwise, if the key code is {@link #KEYCODE_UNKNOWN}, then
      * this is a sequence of characters as returned by {@link #getCharacters}.
      */
     public static final int ACTION_MULTIPLE         = 2;
@@ -330,7 +331,7 @@
     private int mMetaState;
     private int mAction;
     private int mKeyCode;
-    private int mScancode;
+    private int mScanCode;
     private int mRepeatCount;
     private int mDeviceId;
     private int mFlags;
@@ -480,7 +481,7 @@
         mRepeatCount = repeat;
         mMetaState = metaState;
         mDeviceId = device;
-        mScancode = scancode;
+        mScanCode = scancode;
     }
 
     /**
@@ -510,7 +511,7 @@
         mRepeatCount = repeat;
         mMetaState = metaState;
         mDeviceId = device;
-        mScancode = scancode;
+        mScanCode = scancode;
         mFlags = flags;
     }
 
@@ -547,7 +548,7 @@
         mRepeatCount = origEvent.mRepeatCount;
         mMetaState = origEvent.mMetaState;
         mDeviceId = origEvent.mDeviceId;
-        mScancode = origEvent.mScancode;
+        mScanCode = origEvent.mScanCode;
         mFlags = origEvent.mFlags;
         mCharacters = origEvent.mCharacters;
     }
@@ -572,7 +573,7 @@
         mRepeatCount = newRepeat;
         mMetaState = origEvent.mMetaState;
         mDeviceId = origEvent.mDeviceId;
-        mScancode = origEvent.mScancode;
+        mScanCode = origEvent.mScanCode;
         mFlags = origEvent.mFlags;
         mCharacters = origEvent.mCharacters;
     }
@@ -625,7 +626,7 @@
         mRepeatCount = origEvent.mRepeatCount;
         mMetaState = origEvent.mMetaState;
         mDeviceId = origEvent.mDeviceId;
-        mScancode = origEvent.mScancode;
+        mScanCode = origEvent.mScanCode;
         mFlags = origEvent.mFlags;
         // Don't copy mCharacters, since one way or the other we'll lose it
         // when changing the action.
@@ -859,7 +860,7 @@
      * Mostly this is here for debugging purposes.
      */
     public final int getScanCode() {
-        return mScancode;
+        return mScanCode;
     }
 
     /**
@@ -1183,7 +1184,7 @@
     public String toString() {
         return "KeyEvent{action=" + mAction + " code=" + mKeyCode
             + " repeat=" + mRepeatCount
-            + " meta=" + mMetaState + " scancode=" + mScancode
+            + " meta=" + mMetaState + " scancode=" + mScanCode
             + " mFlags=" + mFlags + "}";
     }
 
@@ -1208,7 +1209,7 @@
         out.writeInt(mRepeatCount);
         out.writeInt(mMetaState);
         out.writeInt(mDeviceId);
-        out.writeInt(mScancode);
+        out.writeInt(mScanCode);
         out.writeInt(mFlags);
         out.writeLong(mDownTime);
         out.writeLong(mEventTime);
@@ -1220,7 +1221,7 @@
         mRepeatCount = in.readInt();
         mMetaState = in.readInt();
         mDeviceId = in.readInt();
-        mScancode = in.readInt();
+        mScanCode = in.readInt();
         mFlags = in.readInt();
         mDownTime = in.readLong();
         mEventTime = in.readLong();
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index eefbf7a..1f06191 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -248,17 +248,17 @@
     private RuntimeException mRecycledLocation;
     private boolean mRecycled;
 
-    private MotionEvent() {
-        mPointerIdentifiers = new int[BASE_AVAIL_POINTERS];
-        mDataSamples = new float[BASE_AVAIL_POINTERS*BASE_AVAIL_SAMPLES*NUM_SAMPLE_DATA];
-        mTimeSamples = new long[BASE_AVAIL_SAMPLES];
+    private MotionEvent(int pointerCount, int sampleCount) {
+        mPointerIdentifiers = new int[pointerCount];
+        mDataSamples = new float[pointerCount * sampleCount * NUM_SAMPLE_DATA];
+        mTimeSamples = new long[sampleCount];
     }
 
     static private MotionEvent obtain() {
         final MotionEvent ev;
         synchronized (gRecyclerLock) {
             if (gRecyclerTop == null) {
-                return new MotionEvent();
+                return new MotionEvent(BASE_AVAIL_POINTERS, BASE_AVAIL_SAMPLES);
             }
             ev = gRecyclerTop;
             gRecyclerTop = ev.mNext;
@@ -269,6 +269,45 @@
         ev.mNext = null;
         return ev;
     }
+    
+    @SuppressWarnings("unused") // used by native code
+    static private MotionEvent obtain(int pointerCount, int sampleCount) {
+        final MotionEvent ev;
+        synchronized (gRecyclerLock) {
+            if (gRecyclerTop == null) {
+                if (pointerCount < BASE_AVAIL_POINTERS) {
+                    pointerCount = BASE_AVAIL_POINTERS;
+                }
+                if (sampleCount < BASE_AVAIL_SAMPLES) {
+                    sampleCount = BASE_AVAIL_SAMPLES;
+                }
+                return new MotionEvent(pointerCount, sampleCount);
+            }
+            ev = gRecyclerTop;
+            gRecyclerTop = ev.mNext;
+            gRecyclerUsed--;
+        }
+        ev.mRecycledLocation = null;
+        ev.mRecycled = false;
+        ev.mNext = null;
+        
+        if (ev.mPointerIdentifiers.length < pointerCount) {
+            ev.mPointerIdentifiers = new int[pointerCount];
+        }
+        
+        final int timeSamplesLength = ev.mTimeSamples.length;
+        if (timeSamplesLength < sampleCount) {
+            ev.mTimeSamples = new long[sampleCount];
+        }
+        
+        final int dataSamplesLength = ev.mDataSamples.length;
+        final int neededDataSamplesLength = pointerCount * sampleCount * NUM_SAMPLE_DATA;
+        if (dataSamplesLength < neededDataSamplesLength) {
+            ev.mDataSamples = new float[neededDataSamplesLength];
+        }
+        
+        return ev;
+    }
 
     /**
      * Create a new MotionEvent, filling in all of the basic values that
@@ -1022,7 +1061,7 @@
     }
 
     /**
-     * Returns a bitfield indicating which edges, if any, where touched by this
+     * Returns a bitfield indicating which edges, if any, were touched by this
      * MotionEvent. For touch events, clients can use this to determine if the
      * user's finger was touching the edge of the display.
      *
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 0f0cf60..8beceec 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -471,7 +471,7 @@
                     mWindow = new MyWindow(this);
                     mLayout.type = mWindowType;
                     mLayout.gravity = Gravity.LEFT|Gravity.TOP;
-                    mSession.add(mWindow, mLayout,
+                    mSession.addWithoutInputChannel(mWindow, mLayout,
                             mVisible ? VISIBLE : GONE, mContentInsets);
                 }
                 
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index aa124e6..a41c690d 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -153,6 +153,7 @@
     CompatibilityInfo.Translator mTranslator;
 
     final View.AttachInfo mAttachInfo;
+    InputChannel mInputChannel;
 
     final Rect mTempRect; // used in the transaction to not thrash the heap.
     final Rect mVisRect; // used to retrieve visible rect of focused view.
@@ -219,7 +220,7 @@
     AudioManager mAudioManager;
 
     private final int mDensity;
-
+    
     public static IWindowSession getWindowSession(Looper mainLooper) {
         synchronized (mStaticInit) {
             if (!mInitialized) {
@@ -434,9 +435,6 @@
         }
     }
 
-    // fd [0] is the receiver, [1] is the sender
-    private native int[] makeInputChannel();
-
     /**
      * We have one child
      */
@@ -488,25 +486,20 @@
                 mAdded = true;
                 int res; /* = WindowManagerImpl.ADD_OKAY; */
 
-                // Set up the input event channel
-                if (false) {
-                int[] fds = makeInputChannel();
-                if (DEBUG_INPUT) {
-                    Log.v(TAG, "makeInputChannel() returned " + fds);
-                }
-                }
-
                 // Schedule the first layout -before- adding to the window
                 // manager, to make sure we do the relayout before receiving
                 // any other events from the system.
                 requestLayout();
+                mInputChannel = new InputChannel();
                 try {
                     res = sWindowSession.add(mWindow, mWindowAttributes,
-                            getHostVisibility(), mAttachInfo.mContentInsets);
+                            getHostVisibility(), mAttachInfo.mContentInsets,
+                            mInputChannel);
                 } catch (RemoteException e) {
                     mAdded = false;
                     mView = null;
                     mAttachInfo.mRootView = null;
+                    mInputChannel = null;
                     unscheduleTraversals();
                     throw new RuntimeException("Adding window failed", e);
                 } finally {
@@ -514,7 +507,7 @@
                         attrs.restore();
                     }
                 }
-
+                
                 if (mTranslator != null) {
                     mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets);
                 }
@@ -560,6 +553,12 @@
                     throw new RuntimeException(
                         "Unable to add window -- unknown error code " + res);
                 }
+
+                if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) {
+                    InputQueue.registerInputChannel(mInputChannel, mInputHandler,
+                            Looper.myQueue());
+                }
+                
                 view.assignParent(this);
                 mAddedTouchMode = (res&WindowManagerImpl.ADD_FLAG_IN_TOUCH_MODE) != 0;
                 mAppVisible = (res&WindowManagerImpl.ADD_FLAG_APP_VISIBLE) != 0;
@@ -1735,6 +1734,14 @@
         }
         mSurface.release();
 
+        if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) {
+            if (mInputChannel != null) {
+                InputQueue.unregisterInputChannel(mInputChannel);
+                mInputChannel.dispose();
+                mInputChannel = null;
+            }
+        }
+        
         try {
             sWindowSession.remove(mWindow);
         } catch (RemoteException e) {
@@ -1841,19 +1848,16 @@
             boolean callWhenDone = msg.arg1 != 0;
             
             if (event == null) {
-                try {
-                    long timeBeforeGettingEvents;
-                    if (MEASURE_LATENCY) {
-                        timeBeforeGettingEvents = System.nanoTime();
-                    }
+                long timeBeforeGettingEvents;
+                if (MEASURE_LATENCY) {
+                    timeBeforeGettingEvents = System.nanoTime();
+                }
 
-                    event = sWindowSession.getPendingPointerMove(mWindow);
+                event = getPendingPointerMotionEvent();
 
-                    if (MEASURE_LATENCY && event != null) {
-                        lt.sample("9 Client got events      ", System.nanoTime() - event.getEventTimeNano());
-                        lt.sample("8 Client getting events  ", timeBeforeGettingEvents - event.getEventTimeNano());
-                    }
-                } catch (RemoteException e) {
+                if (MEASURE_LATENCY && event != null) {
+                    lt.sample("9 Client got events      ", System.nanoTime() - event.getEventTimeNano());
+                    lt.sample("8 Client getting events  ", timeBeforeGettingEvents - event.getEventTimeNano());
                 }
                 callWhenDone = false;
             }
@@ -1928,14 +1932,9 @@
                 }
             } finally {
                 if (callWhenDone) {
-                    try {
-                        sWindowSession.finishKey(mWindow);
-                    } catch (RemoteException e) {
-                    }
+                    finishMotionEvent();
                 }
-                if (event != null) {
-                    event.recycle();
-                }
+                recycleMotionEvent(event);
                 if (LOCAL_LOGV || WATCH_POINTER) Log.i(TAG, "Done dispatching!");
                 // Let the exception fall through -- the looper will catch
                 // it and take care of the bad app for us.
@@ -2075,7 +2074,63 @@
         } break;
         }
     }
+    
+    private void finishKeyEvent(KeyEvent event) {
+        if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) {
+            if (mFinishedCallback != null) {
+                mFinishedCallback.run();
+                mFinishedCallback = null;
+            }
+        } else {
+            try {
+                sWindowSession.finishKey(mWindow);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+    
+    private void finishMotionEvent() {
+        if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) {
+            throw new IllegalStateException("Should not be reachable with native input dispatch.");
+        }
+        
+        try {
+            sWindowSession.finishKey(mWindow);
+        } catch (RemoteException e) {
+        }
+    }
 
+    private void recycleMotionEvent(MotionEvent event) {
+        if (event != null) {
+            event.recycle();
+        }
+    }
+    
+    private MotionEvent getPendingPointerMotionEvent() {
+        if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) {
+            throw new IllegalStateException("Should not be reachable with native input dispatch.");
+        }
+        
+        try {
+            return sWindowSession.getPendingPointerMove(mWindow);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    private MotionEvent getPendingTrackballMotionEvent() {
+        if (WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH) {
+            throw new IllegalStateException("Should not be reachable with native input dispatch.");
+        }
+        
+        try {
+            return sWindowSession.getPendingTrackballMove(mWindow);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+    
+    
     /**
      * Something in the current window tells us we need to change the touch mode.  For
      * example, we are not in touch mode, and the user touches the screen.
@@ -2200,10 +2255,7 @@
 
     private void deliverTrackballEvent(MotionEvent event, boolean callWhenDone) {
         if (event == null) {
-            try {
-                event = sWindowSession.getPendingTrackballMove(mWindow);
-            } catch (RemoteException e) {
-            }
+            event = getPendingTrackballMotionEvent();
             callWhenDone = false;
         }
 
@@ -2223,14 +2275,9 @@
         } finally {
             if (handled) {
                 if (callWhenDone) {
-                    try {
-                        sWindowSession.finishKey(mWindow);
-                    } catch (RemoteException e) {
-                    }
+                    finishMotionEvent();
                 }
-                if (event != null) {
-                    event.recycle();
-                }
+                recycleMotionEvent(event);
                 // If we reach this, we delivered a trackball event to mView and
                 // mView consumed it. Because we will not translate the trackball
                 // event into a key event, touch mode will not exit, so we exit
@@ -2339,13 +2386,8 @@
             }
         } finally {
             if (callWhenDone) {
-                try {
-                    sWindowSession.finishKey(mWindow);
-                } catch (RemoteException e) {
-                }
-                if (event != null) {
-                    event.recycle();
-                }
+                finishMotionEvent();
+                recycleMotionEvent(event);
             }
             // Let the exception fall through -- the looper will catch
             // it and take care of the bad app for us.
@@ -2503,10 +2545,7 @@
             if (sendDone) {
                 if (LOCAL_LOGV) Log.v(
                     "ViewRoot", "Telling window manager key is finished");
-                try {
-                    sWindowSession.finishKey(mWindow);
-                } catch (RemoteException e) {
-                }
+                finishKeyEvent(event);
             }
             return;
         }
@@ -2539,10 +2578,7 @@
             } else if (sendDone) {
                 if (LOCAL_LOGV) Log.v(
                         "ViewRoot", "Telling window manager key is finished");
-                try {
-                    sWindowSession.finishKey(mWindow);
-                } catch (RemoteException e) {
-                }
+                finishKeyEvent(event);
             } else {
                 Log.w("ViewRoot", "handleFinishedEvent(seq=" + seq
                         + " handled=" + handled + " ev=" + event
@@ -2617,10 +2653,7 @@
             if (sendDone) {
                 if (LOCAL_LOGV) Log.v(
                     "ViewRoot", "Telling window manager key is finished");
-                try {
-                    sWindowSession.finishKey(mWindow);
-                } catch (RemoteException e) {
-                }
+                finishKeyEvent(event);
             }
             // Let the exception fall through -- the looper will catch
             // it and take care of the bad app for us.
@@ -2798,6 +2831,53 @@
         msg.obj = ri;
         sendMessage(msg);
     }
+    
+    private Runnable mFinishedCallback;
+    
+    private final InputHandler mInputHandler = new InputHandler() {
+        public void handleKey(KeyEvent event, Runnable finishedCallback) {
+            mFinishedCallback = finishedCallback;
+            
+            if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                //noinspection ConstantConditions
+                if (false && event.getKeyCode() == KeyEvent.KEYCODE_CAMERA) {
+                    if (Config.LOGD) Log.d("keydisp",
+                            "===================================================");
+                    if (Config.LOGD) Log.d("keydisp", "Focused view Hierarchy is:");
+                    debug();
+
+                    if (Config.LOGD) Log.d("keydisp",
+                            "===================================================");
+                }
+            }
+
+            Message msg = obtainMessage(DISPATCH_KEY);
+            msg.obj = event;
+
+            if (LOCAL_LOGV) Log.v(
+                "ViewRoot", "sending key " + event + " to " + mView);
+
+            sendMessageAtTime(msg, event.getEventTime());
+        }
+
+        public void handleTouch(MotionEvent event, Runnable finishedCallback) {
+            finishedCallback.run();
+            
+            Message msg = obtainMessage(DISPATCH_POINTER);
+            msg.obj = event;
+            msg.arg1 = 0;
+            sendMessageAtTime(msg, event.getEventTime());
+        }
+
+        public void handleTrackball(MotionEvent event, Runnable finishedCallback) {
+            finishedCallback.run();
+            
+            Message msg = obtainMessage(DISPATCH_TRACKBALL);
+            msg.obj = event;
+            msg.arg1 = 0;
+            sendMessageAtTime(msg, event.getEventTime());
+        }
+    };
 
     public void dispatchKey(KeyEvent event) {
         if (event.getAction() == KeyEvent.ACTION_DOWN) {
@@ -2968,7 +3048,7 @@
         }
     }
 
-    static class EventCompletion extends Handler {
+    class EventCompletion extends Handler {
         final IWindow mWindow;
         final KeyEvent mKeyEvent;
         final boolean mIsPointer;
@@ -2987,40 +3067,25 @@
         @Override
         public void handleMessage(Message msg) {
             if (mKeyEvent != null) {
-                try {
-                    sWindowSession.finishKey(mWindow);
-                 } catch (RemoteException e) {
-                 }
+                finishKeyEvent(mKeyEvent);
            } else if (mIsPointer) {
                 boolean didFinish;
                 MotionEvent event = mMotionEvent;
                 if (event == null) {
-                    try {
-                        event = sWindowSession.getPendingPointerMove(mWindow);
-                    } catch (RemoteException e) {
-                    }
+                    event = getPendingPointerMotionEvent();
                     didFinish = true;
                 } else {
                     didFinish = event.getAction() == MotionEvent.ACTION_OUTSIDE;
                 }
                 if (!didFinish) {
-                    try {
-                        sWindowSession.finishKey(mWindow);
-                     } catch (RemoteException e) {
-                     }
+                    finishMotionEvent();
                 }
             } else {
                 MotionEvent event = mMotionEvent;
                 if (event == null) {
-                    try {
-                        event = sWindowSession.getPendingTrackballMove(mWindow);
-                    } catch (RemoteException e) {
-                    }
+                    event = getPendingTrackballMotionEvent();
                 } else {
-                    try {
-                        sWindowSession.finishKey(mWindow);
-                     } catch (RemoteException e) {
-                     }
+                    finishMotionEvent();
                 }
             }
         }
@@ -3050,7 +3115,7 @@
                 viewRoot.dispatchKey(event);
             } else {
                 Log.w("ViewRoot.W", "Key event " + event + " but no ViewRoot available!");
-                new EventCompletion(mMainLooper, this, event, false, null);
+                viewRoot.new EventCompletion(mMainLooper, this, event, false, null);
             }
         }
 
@@ -3064,7 +3129,7 @@
                 }
                 viewRoot.dispatchPointer(event, eventTime, callWhenDone);
             } else {
-                new EventCompletion(mMainLooper, this, null, true, event);
+                viewRoot.new EventCompletion(mMainLooper, this, null, true, event);
             }
         }
 
@@ -3074,7 +3139,7 @@
             if (viewRoot != null) {
                 viewRoot.dispatchTrackball(event, eventTime, callWhenDone);
             } else {
-                new EventCompletion(mMainLooper, this, null, false, event);
+                viewRoot.new EventCompletion(mMainLooper, this, null, false, event);
             }
         }
 
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index b39cb9d..431b786 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -78,6 +78,12 @@
     public final static int FLAG_BRIGHT_HERE = 0x20000000;
 
     public final static boolean WATCH_POINTER = false;
+    
+    /**
+     * Temporary flag added during the transition to the new native input dispatcher.
+     * This will be removed when the old input dispatch code is deleted.
+     */
+    public final static boolean ENABLE_NATIVE_INPUT_DISPATCH = false;
 
     // flags for interceptKeyTq
     /**
@@ -708,6 +714,8 @@
      */
     public boolean preprocessInputEventTq(RawInputEvent event);
     
+    public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen);
+    
     /**
      * Determine whether a given key code is used to cause an app switch
      * to occur (most often the HOME key, also often ENDCALL).  If you return
diff --git a/core/java/com/android/internal/view/BaseInputHandler.java b/core/java/com/android/internal/view/BaseInputHandler.java
new file mode 100644
index 0000000..6fe5063
--- /dev/null
+++ b/core/java/com/android/internal/view/BaseInputHandler.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view;
+
+import android.view.InputHandler;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+/**
+ * Base do-nothing implementation of an input handler.
+ * @hide
+ */
+public abstract class BaseInputHandler implements InputHandler {
+    public void handleKey(KeyEvent event, Runnable finishedCallback) {
+        finishedCallback.run();
+    }
+    
+    public void handleTouch(MotionEvent event, Runnable finishedCallback) {
+        finishedCallback.run();
+    }
+    
+    public void handleTrackball(MotionEvent event, Runnable finishedCallback) {
+        finishedCallback.run();
+    }
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index d4545d7..d854e87 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -45,6 +45,11 @@
 	android_view_Display.cpp \
 	android_view_Surface.cpp \
 	android_view_ViewRoot.cpp \
+	android_view_InputChannel.cpp \
+	android_view_InputQueue.cpp \
+	android_view_InputTarget.cpp \
+	android_view_KeyEvent.cpp \
+	android_view_MotionEvent.cpp \
 	android_text_AndroidCharacter.cpp \
 	android_text_AndroidBidi.cpp \
 	android_text_KeyCharacterMap.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index f66ed83..466642a 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -162,6 +162,11 @@
 extern int register_android_backup_FileBackupHelperBase(JNIEnv *env);
 extern int register_android_backup_BackupHelperDispatcher(JNIEnv *env);
 extern int register_android_app_NativeActivity(JNIEnv *env);
+extern int register_android_view_InputChannel(JNIEnv* env);
+extern int register_android_view_InputQueue(JNIEnv* env);
+extern int register_android_view_InputTarget(JNIEnv* env);
+extern int register_android_view_KeyEvent(JNIEnv* env);
+extern int register_android_view_MotionEvent(JNIEnv* env);
 
 static AndroidRuntime* gCurRuntime = NULL;
 
@@ -1288,6 +1293,11 @@
     REG_JNI(register_android_backup_BackupHelperDispatcher),
     
     REG_JNI(register_android_app_NativeActivity),
+    REG_JNI(register_android_view_InputChannel),
+    REG_JNI(register_android_view_InputQueue),
+    REG_JNI(register_android_view_InputTarget),
+    REG_JNI(register_android_view_KeyEvent),
+    REG_JNI(register_android_view_MotionEvent),
 };
 
 /*
diff --git a/core/jni/android_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp
index 8984057..030d6c7 100644
--- a/core/jni/android_os_MessageQueue.cpp
+++ b/core/jni/android_os_MessageQueue.cpp
@@ -14,325 +14,151 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "MQNative"
+#define LOG_TAG "MessageQueue-JNI"
 
 #include "JNIHelp.h"
 
-#include <sys/socket.h>
-#include <sys/select.h>
-#include <sys/time.h>
-#include <fcntl.h>
-
-#include <android_runtime/AndroidRuntime.h>
-#include <utils/SystemClock.h>
-#include <utils/Vector.h>
+#include <utils/PollLoop.h>
 #include <utils/Log.h>
-
-using namespace android;
-
-// ----------------------------------------------------------------------------
-
-static struct {
-    jclass mClass;
-
-    jfieldID mObject;   // native object attached to the DVM MessageQueue
-} gMessageQueueOffsets;
-
-static struct {
-    jclass mClass;
-    jmethodID mConstructor;
-} gKeyEventOffsets;
-
-// TODO: also MotionEvent offsets etc. a la gKeyEventOffsets
-
-static struct {
-    jclass mClass;
-    jmethodID mObtain;      // obtain(Handler h, int what, Object obj)
-} gMessageOffsets;
-
-// ----------------------------------------------------------------------------
-
-static void doThrow(JNIEnv* env, const char* exc, const char* msg = NULL)
-{
-    if (jniThrowException(env, exc, msg) != 0)
-        assert(false);
-}
-
-// ----------------------------------------------------------------------------
-
-class MessageQueueNative {
-public:
-    MessageQueueNative(int readSocket, int writeSocket);
-    ~MessageQueueNative();
-
-    // select on all FDs until the designated time; forever if wakeupTime is < 0
-    int waitForSignal(jobject mqueue, jlong wakeupTime);
-
-    // signal the queue-ready pipe
-    void signalQueuePipe();
-
-    // Specify a new input pipe, passing in responsibility for the socket fd and
-    // ashmem region
-    int registerInputPipe(JNIEnv* env, int socketFd, int memRegionFd, jobject handler);
-
-    // Forget about this input pipe, closing the socket and ashmem region as well
-    int unregisterInputPipe(JNIEnv* env, int socketFd);
-
-    size_t numRegisteredPipes() const { return mInputPipes.size(); }
-
-private:
-    struct InputPipe {
-        int fd;
-        int region;
-        jobject handler;
-
-        InputPipe() {}
-        InputPipe(int _fd, int _r, jobject _h) : fd(_fd), region(_r), handler(_h) {}
-    };
-
-    // consume an event from a socket, put it on the DVM MessageQueue indicated,
-    // and notify the other end of the pipe that we've consumed it.
-    void queueEventFromPipe(const InputPipe& pipe, jobject mqueue);
-
-    int mQueueReadFd, mQueueWriteFd;
-    Vector<InputPipe> mInputPipes;
-};
-
-MessageQueueNative::MessageQueueNative(int readSocket, int writeSocket) 
-        : mQueueReadFd(readSocket), mQueueWriteFd(writeSocket) {
-}
-
-MessageQueueNative::~MessageQueueNative() {
-}
-
-int MessageQueueNative::waitForSignal(jobject mqueue, jlong timeoutMillis) {
-    struct timeval tv, *timeout;
-    fd_set fdset;
-
-    if (timeoutMillis < 0) {
-        timeout = NULL;
-    } else {
-        if (timeoutMillis == 0) {
-            tv.tv_sec = 0;
-            tv.tv_usec = 0;
-        } else {
-            tv.tv_sec = (timeoutMillis / 1000);
-            tv.tv_usec = (timeoutMillis - (1000 * tv.tv_sec)) * 1000;
-        }
-        timeout = &tv;
-    }
-
-    // always rebuild the fd set from scratch
-    FD_ZERO(&fdset);
-
-    // the queue signalling pipe
-    FD_SET(mQueueReadFd, &fdset);
-    int maxFd = mQueueReadFd;
-
-    // and the input sockets themselves
-    for (size_t i = 0; i < mInputPipes.size(); i++) {
-        FD_SET(mInputPipes[i].fd, &fdset);
-        if (maxFd < mInputPipes[i].fd) {
-            maxFd = mInputPipes[i].fd;
-        }
-    }
-
-    // now wait
-    int res = select(maxFd + 1, &fdset, NULL, NULL, timeout);
-
-    // Error?  Just return it and bail
-    if (res < 0) return res;
-
-    // What happened -- timeout or actual data arrived?
-    if (res == 0) {
-        // select() returned zero, which means we timed out, which means that it's time
-        // to deliver the head element that was already on the queue.  Just fall through
-        // without doing anything else.
-    } else {
-        // Data (or a queue signal) arrived!
-        //
-        // If it's data, pull the data off the pipe, build a new Message with it, put it on
-        // the DVM-side MessageQueue (pointed to by the 'mqueue' parameter), then proceed
-        // into the queue-signal case.
-        //
-        // If a queue signal arrived, just consume any data pending in that pipe and
-        // fall out.
-        bool queue_signalled = (FD_ISSET(mQueueReadFd, &fdset) != 0);
-
-        for (size_t i = 0; i < mInputPipes.size(); i++) {
-            if (FD_ISSET(mInputPipes[i].fd, &fdset)) {
-                queueEventFromPipe(mInputPipes[i], mqueue);
-                queue_signalled = true;     // we know a priori that queueing the event does this
-            }
-        }
-
-        // Okay, stuff went on the queue.  Consume the contents of the signal pipe
-        // now that we're awake and about to start dispatching messages again.
-        if (queue_signalled) {
-            uint8_t buf[16];
-            ssize_t nRead;
-            do {
-                nRead = read(mQueueReadFd, buf, sizeof(buf));
-            } while (nRead > 0); // in nonblocking mode we'll get -1 when it's drained
-        }
-    }
-
-    return 0;
-}
-
-// signals to the queue pipe are one undefined byte.  it's just a "data has arrived" token
-// and the pipe is drained on receipt of at least one signal
-void MessageQueueNative::signalQueuePipe() {
-    int dummy[1];
-    write(mQueueWriteFd, dummy, 1);
-}
-
-void MessageQueueNative::queueEventFromPipe(const InputPipe& inPipe, jobject mqueue) {
-    // !!! TODO: read the event data from the InputPipe's ashmem region, convert it to a DVM
-    // event object of the proper type [MotionEvent or KeyEvent], create a Message holding
-    // it as appropriate, point the Message to the Handler associated with this InputPipe,
-    // and call up to the DVM MessageQueue implementation to enqueue it for delivery.
-}
-
-// the number of registered pipes on success; < 0 on error
-int MessageQueueNative::registerInputPipe(JNIEnv* env,
-        int socketFd, int memRegionFd, jobject handler) {
-    // make sure this fd is not already known to us
-    for (size_t i = 0; i < mInputPipes.size(); i++) {
-        if (mInputPipes[i].fd == socketFd) {
-            LOGE("Attempt to re-register input fd %d", socketFd);
-            return -1;
-        }
-    }
-
-    mInputPipes.push( InputPipe(socketFd, memRegionFd, env->NewGlobalRef(handler)) );
-    return mInputPipes.size();
-}
-
-// Remove an input pipe from our bookkeeping.  Also closes the socket and ashmem
-// region file descriptor!
-//
-// returns the number of remaining input pipes on success; < 0 on error
-int MessageQueueNative::unregisterInputPipe(JNIEnv* env, int socketFd) {
-    for (size_t i = 0; i < mInputPipes.size(); i++) {
-        if (mInputPipes[i].fd == socketFd) {
-            close(mInputPipes[i].fd);
-            close(mInputPipes[i].region);
-            env->DeleteGlobalRef(mInputPipes[i].handler);
-            mInputPipes.removeAt(i);
-            return mInputPipes.size();
-        }
-    }
-    LOGW("Tried to unregister input pipe %d but not found!", socketFd);
-    return -1;
-}
-
-// ----------------------------------------------------------------------------
+#include "android_os_MessageQueue.h"
 
 namespace android {
-    
-static void android_os_MessageQueue_init(JNIEnv* env, jobject obj) {
-    // Create the pipe
-    int fds[2];
-    int err = socketpair(AF_LOCAL, SOCK_STREAM, 0, fds);
-    if (err != 0) {
-        doThrow(env, "java/lang/RuntimeException", "Unable to create socket pair");
-    }
 
-    MessageQueueNative *mqn = new MessageQueueNative(fds[0], fds[1]);
-    if (mqn == NULL) {
-        close(fds[0]);
-        close(fds[1]);
-        doThrow(env, "java/lang/RuntimeException", "Unable to allocate native queue");
-    }
+// ----------------------------------------------------------------------------
 
-    int flags = fcntl(fds[0], F_GETFL);
-    fcntl(fds[0], F_SETFL, flags | O_NONBLOCK);
-    flags = fcntl(fds[1], F_GETFL);
-    fcntl(fds[1], F_SETFL, flags | O_NONBLOCK);
+static struct {
+    jclass clazz;
 
-    env->SetIntField(obj, gMessageQueueOffsets.mObject, (jint)mqn);
+    jfieldID mPtr;   // native object attached to the DVM MessageQueue
+} gMessageQueueClassInfo;
+
+// ----------------------------------------------------------------------------
+
+class NativeMessageQueue {
+public:
+    NativeMessageQueue();
+    ~NativeMessageQueue();
+
+    inline sp<PollLoop> getPollLoop() { return mPollLoop; }
+
+    bool pollOnce(int timeoutMillis);
+    void wake();
+
+private:
+    sp<PollLoop> mPollLoop;
+};
+
+// ----------------------------------------------------------------------------
+
+NativeMessageQueue::NativeMessageQueue() {
+    mPollLoop = new PollLoop();
 }
 
-static void android_os_MessageQueue_signal(JNIEnv* env, jobject obj) {
-    MessageQueueNative *mqn = (MessageQueueNative*) env->GetIntField(obj, gMessageQueueOffsets.mObject);
-    if (mqn != NULL) {
-        mqn->signalQueuePipe();
-    } else {
-        doThrow(env, "java/lang/IllegalStateException", "Queue not initialized");
-    }
+NativeMessageQueue::~NativeMessageQueue() {
 }
 
-static int android_os_MessageQueue_waitForNext(JNIEnv* env, jobject obj, jlong when) {
-    MessageQueueNative *mqn = (MessageQueueNative*) env->GetIntField(obj, gMessageQueueOffsets.mObject);
-    if (mqn != NULL) {
-        int res = mqn->waitForSignal(obj, when);
-        return res; // the DVM event, if any, has been constructed and queued now
-    }
-
-    return -1;
+bool NativeMessageQueue::pollOnce(int timeoutMillis) {
+    return mPollLoop->pollOnce(timeoutMillis);
 }
 
-static void android_os_MessageQueue_registerInputStream(JNIEnv* env, jobject obj,
-        jint socketFd, jint regionFd, jobject handler) {
-    MessageQueueNative *mqn = (MessageQueueNative*) env->GetIntField(obj, gMessageQueueOffsets.mObject);
-    if (mqn != NULL) {
-        mqn->registerInputPipe(env, socketFd, regionFd, handler);
-    } else {
-        doThrow(env, "java/lang/IllegalStateException", "Queue not initialized");
-    }
-}
-
-static void android_os_MessageQueue_unregisterInputStream(JNIEnv* env, jobject obj,
-        jint socketFd) {
-    MessageQueueNative *mqn = (MessageQueueNative*) env->GetIntField(obj, gMessageQueueOffsets.mObject);
-    if (mqn != NULL) {
-        mqn->unregisterInputPipe(env, socketFd);
-    } else {
-        doThrow(env, "java/lang/IllegalStateException", "Queue not initialized");
-    }
+void NativeMessageQueue::wake() {
+    mPollLoop->wake();
 }
 
 // ----------------------------------------------------------------------------
 
-const char* const kKeyEventPathName = "android/view/KeyEvent";
-const char* const kMessagePathName = "android/os/Message";
-const char* const kMessageQueuePathName = "android/os/MessageQueue";
+static NativeMessageQueue* android_os_MessageQueue_getNativeMessageQueue(JNIEnv* env,
+        jobject messageQueueObj) {
+    jint intPtr = env->GetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr);
+    return reinterpret_cast<NativeMessageQueue*>(intPtr);
+}
+
+static void android_os_MessageQueue_setNativeMessageQueue(JNIEnv* env, jobject messageQueueObj,
+        NativeMessageQueue* nativeMessageQueue) {
+    env->SetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr,
+             reinterpret_cast<jint>(nativeMessageQueue));
+}
+
+sp<PollLoop> android_os_MessageQueue_getPollLoop(JNIEnv* env, jobject messageQueueObj) {
+    NativeMessageQueue* nativeMessageQueue =
+            android_os_MessageQueue_getNativeMessageQueue(env, messageQueueObj);
+    return nativeMessageQueue != NULL ? nativeMessageQueue->getPollLoop() : NULL;
+}
+
+static void android_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) {
+    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
+    if (! nativeMessageQueue) {
+        jniThrowRuntimeException(env, "Unable to allocate native queue");
+        return;
+    }
+
+    android_os_MessageQueue_setNativeMessageQueue(env, obj, nativeMessageQueue);
+}
+
+static void android_os_MessageQueue_nativeDestroy(JNIEnv* env, jobject obj) {
+    NativeMessageQueue* nativeMessageQueue =
+            android_os_MessageQueue_getNativeMessageQueue(env, obj);
+    if (nativeMessageQueue) {
+        android_os_MessageQueue_setNativeMessageQueue(env, obj, NULL);
+        delete nativeMessageQueue;
+    }
+}
+
+static void throwQueueNotInitialized(JNIEnv* env) {
+    jniThrowException(env, "java/lang/IllegalStateException", "Message queue not initialized");
+}
+
+static jboolean android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
+        jint timeoutMillis) {
+    NativeMessageQueue* nativeMessageQueue =
+            android_os_MessageQueue_getNativeMessageQueue(env, obj);
+    if (! nativeMessageQueue) {
+        throwQueueNotInitialized(env);
+        return false;
+    }
+    return nativeMessageQueue->pollOnce(timeoutMillis);
+}
+
+static void android_os_MessageQueue_nativeWake(JNIEnv* env, jobject obj) {
+    NativeMessageQueue* nativeMessageQueue =
+            android_os_MessageQueue_getNativeMessageQueue(env, obj);
+    if (! nativeMessageQueue) {
+        throwQueueNotInitialized(env);
+        return;
+    }
+    return nativeMessageQueue->wake();
+}
+
+// ----------------------------------------------------------------------------
 
 static JNINativeMethod gMessageQueueMethods[] = {
     /* name, signature, funcPtr */
-    { "nativeInit", "()V", (void*)android_os_MessageQueue_init },
-    { "nativeSignal", "()V", (void*)android_os_MessageQueue_signal },
-    { "nativeWaitForNext", "(J)I", (void*)android_os_MessageQueue_waitForNext },
-    { "nativeRegisterInputStream", "(IILandroid/os/Handler;)V", (void*)android_os_MessageQueue_registerInputStream },
-    { "nativeUnregisterInputStream", "(I)V", (void*)android_os_MessageQueue_unregisterInputStream },
+    { "nativeInit", "()V", (void*)android_os_MessageQueue_nativeInit },
+    { "nativeDestroy", "()V", (void*)android_os_MessageQueue_nativeDestroy },
+    { "nativePollOnce", "(I)Z", (void*)android_os_MessageQueue_nativePollOnce },
+    { "nativeWake", "()V", (void*)android_os_MessageQueue_nativeWake }
 };
 
+#define FIND_CLASS(var, className) \
+        var = env->FindClass(className); \
+        LOG_FATAL_IF(! var, "Unable to find class " className); \
+        var = jclass(env->NewGlobalRef(var));
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+        var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
 int register_android_os_MessageQueue(JNIEnv* env) {
-    jclass clazz;
-
-    clazz = env->FindClass(kMessageQueuePathName);
-    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.MessageQueue");
-    gMessageQueueOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
-    gMessageQueueOffsets.mObject = env->GetFieldID(clazz, "mObject", "I");
-    assert(gMessageQueueOffsets.mObject);
-
-    clazz = env->FindClass(kMessagePathName);
-    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.Message");
-    gMessageOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
-    gMessageOffsets.mObtain = env->GetStaticMethodID(clazz, "obtain",
-            "(Landroid/os/Handler;ILjava/lang/Object;)Landroid/os/Message;");
-    assert(gMessageOffsets.mObtain);
-
-    clazz = env->FindClass(kKeyEventPathName);
-    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.view.KeyEvent");
-    gKeyEventOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
-    gKeyEventOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "(JJIIIIIII)V");
-    assert(gKeyEventOffsets.mConstructor);
-    
-    return AndroidRuntime::registerNativeMethods(env, kMessageQueuePathName,
+    int res = jniRegisterNativeMethods(env, "android/os/MessageQueue",
             gMessageQueueMethods, NELEM(gMessageQueueMethods));
+    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+
+    FIND_CLASS(gMessageQueueClassInfo.clazz, "android/os/MessageQueue");
+
+    GET_FIELD_ID(gMessageQueueClassInfo.mPtr, gMessageQueueClassInfo.clazz,
+            "mPtr", "I");
+    
+    return 0;
 }
 
-
-}; // end of namespace android
+} // namespace android
diff --git a/core/jni/android_os_MessageQueue.h b/core/jni/android_os_MessageQueue.h
new file mode 100644
index 0000000..5c742e2
--- /dev/null
+++ b/core/jni/android_os_MessageQueue.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_OS_MESSAGEQUEUE_H
+#define _ANDROID_OS_MESSAGEQUEUE_H
+
+#include "jni.h"
+
+namespace android {
+
+class PollLoop;
+
+extern sp<PollLoop> android_os_MessageQueue_getPollLoop(JNIEnv* env, jobject messageQueueObj);
+
+} // namespace android
+
+#endif // _ANDROID_OS_MESSAGEQUEUE_H
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
new file mode 100644
index 0000000..47bb073
--- /dev/null
+++ b/core/jni/android_view_InputChannel.cpp
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputChannel-JNI"
+
+#include "JNIHelp.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <binder/Parcel.h>
+#include <utils/Log.h>
+#include <ui/InputTransport.h>
+#include "android_view_InputChannel.h"
+#include "android_util_Binder.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+static struct {
+    jclass clazz;
+
+    jfieldID mPtr;   // native object attached to the DVM InputChannel
+    jmethodID ctor;
+} gInputChannelClassInfo;
+
+// ----------------------------------------------------------------------------
+
+class NativeInputChannel {
+public:
+    NativeInputChannel(const sp<InputChannel>& inputChannel);
+    ~NativeInputChannel();
+
+    inline sp<InputChannel> getInputChannel() { return mInputChannel; }
+
+    void setDisposeCallback(InputChannelObjDisposeCallback callback, void* data);
+    void invokeAndRemoveDisposeCallback(JNIEnv* env, jobject obj);
+
+private:
+    sp<InputChannel> mInputChannel;
+    InputChannelObjDisposeCallback mDisposeCallback;
+    void* mDisposeData;
+};
+
+// ----------------------------------------------------------------------------
+
+NativeInputChannel::NativeInputChannel(const sp<InputChannel>& inputChannel) :
+    mInputChannel(inputChannel), mDisposeCallback(NULL) {
+}
+
+NativeInputChannel::~NativeInputChannel() {
+}
+
+void NativeInputChannel::setDisposeCallback(InputChannelObjDisposeCallback callback, void* data) {
+    mDisposeCallback = callback;
+    mDisposeData = data;
+}
+
+void NativeInputChannel::invokeAndRemoveDisposeCallback(JNIEnv* env, jobject obj) {
+    if (mDisposeCallback) {
+        mDisposeCallback(env, obj, mInputChannel, mDisposeData);
+        mDisposeCallback = NULL;
+        mDisposeData = NULL;
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+static NativeInputChannel* android_view_InputChannel_getNativeInputChannel(JNIEnv* env,
+        jobject inputChannelObj) {
+    jint intPtr = env->GetIntField(inputChannelObj, gInputChannelClassInfo.mPtr);
+    return reinterpret_cast<NativeInputChannel*>(intPtr);
+}
+
+static void android_view_InputChannel_setNativeInputChannel(JNIEnv* env, jobject inputChannelObj,
+        NativeInputChannel* nativeInputChannel) {
+    env->SetIntField(inputChannelObj, gInputChannelClassInfo.mPtr,
+             reinterpret_cast<jint>(nativeInputChannel));
+}
+
+sp<InputChannel> android_view_InputChannel_getInputChannel(JNIEnv* env, jobject inputChannelObj) {
+    NativeInputChannel* nativeInputChannel =
+            android_view_InputChannel_getNativeInputChannel(env, inputChannelObj);
+    return nativeInputChannel != NULL ? nativeInputChannel->getInputChannel() : NULL;
+}
+
+void android_view_InputChannel_setDisposeCallback(JNIEnv* env, jobject inputChannelObj,
+        InputChannelObjDisposeCallback callback, void* data) {
+    NativeInputChannel* nativeInputChannel =
+            android_view_InputChannel_getNativeInputChannel(env, inputChannelObj);
+    if (nativeInputChannel == NULL) {
+        LOGW("Cannot set dispose callback because input channel object has not been initialized.");
+    } else {
+        nativeInputChannel->setDisposeCallback(callback, data);
+    }
+}
+
+static jobject android_view_InputChannel_createInputChannel(JNIEnv* env,
+        NativeInputChannel* nativeInputChannel) {
+    jobject inputChannelObj = env->NewObject(gInputChannelClassInfo.clazz,
+            gInputChannelClassInfo.ctor);
+    android_view_InputChannel_setNativeInputChannel(env, inputChannelObj, nativeInputChannel);
+    return inputChannelObj;
+}
+
+static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env,
+        jclass clazz, jstring nameObj) {
+    const char* nameChars = env->GetStringUTFChars(nameObj, NULL);
+    String8 name(nameChars);
+    env->ReleaseStringUTFChars(nameObj, nameChars);
+
+    InputChannel* serverChannel;
+    InputChannel* clientChannel;
+    status_t result = InputChannel::openInputChannelPair(name, & serverChannel, & clientChannel);
+
+    if (result) {
+        LOGE("Could not open input channel pair.  status=%d", result);
+        jniThrowRuntimeException(env, "Could not open input channel pair.");
+        return NULL;
+    }
+
+    // TODO more robust error checking
+    jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
+            new NativeInputChannel(serverChannel));
+    jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
+            new NativeInputChannel(clientChannel));
+
+    jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
+    env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
+    env->SetObjectArrayElement(channelPair, 1, clientChannelObj);
+    return channelPair;
+}
+
+static void android_view_InputChannel_nativeDispose(JNIEnv* env, jobject obj, jboolean finalized) {
+    NativeInputChannel* nativeInputChannel =
+            android_view_InputChannel_getNativeInputChannel(env, obj);
+    if (nativeInputChannel) {
+        if (finalized) {
+            LOGW("Input channel object '%s' was finalized without being disposed!",
+                    nativeInputChannel->getInputChannel()->getName().string());
+        }
+
+        nativeInputChannel->invokeAndRemoveDisposeCallback(env, obj);
+
+        android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
+        delete nativeInputChannel;
+    }
+}
+
+static void android_view_InputChannel_nativeTransferTo(JNIEnv* env, jobject obj,
+        jobject otherObj) {
+    if (android_view_InputChannel_getInputChannel(env, otherObj) != NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+                "Other object already has a native input channel.");
+        return;
+    }
+
+    NativeInputChannel* nativeInputChannel =
+            android_view_InputChannel_getNativeInputChannel(env, obj);
+    android_view_InputChannel_setNativeInputChannel(env, otherObj, nativeInputChannel);
+    android_view_InputChannel_setNativeInputChannel(env, obj, NULL);
+}
+
+static void android_view_InputChannel_nativeReadFromParcel(JNIEnv* env, jobject obj,
+        jobject parcelObj) {
+    if (android_view_InputChannel_getInputChannel(env, obj) != NULL) {
+        jniThrowException(env, "java/lang/IllegalStateException",
+                "This object already has a native input channel.");
+        return;
+    }
+
+    Parcel* parcel = parcelForJavaObject(env, parcelObj);
+    if (parcel) {
+        bool isInitialized = parcel->readInt32();
+        if (isInitialized) {
+            String8 name = parcel->readString8();
+            int32_t ashmemFd = dup(parcel->readFileDescriptor());
+            int32_t receivePipeFd = dup(parcel->readFileDescriptor());
+            int32_t sendPipeFd = dup(parcel->readFileDescriptor());
+            if (ashmemFd < 0 || receivePipeFd < 0 || sendPipeFd < 0) {
+                if (ashmemFd >= 0) ::close(ashmemFd);
+                if (receivePipeFd >= 0) ::close(receivePipeFd);
+                if (sendPipeFd >= 0) ::close(sendPipeFd);
+                jniThrowRuntimeException(env,
+                        "Could not read input channel file descriptors from parcel.");
+                return;
+            }
+
+            InputChannel* inputChannel = new InputChannel(name, ashmemFd,
+                    receivePipeFd, sendPipeFd);
+            NativeInputChannel* nativeInputChannel = new NativeInputChannel(inputChannel);
+
+            android_view_InputChannel_setNativeInputChannel(env, obj, nativeInputChannel);
+        }
+    }
+}
+
+static void android_view_InputChannel_nativeWriteToParcel(JNIEnv* env, jobject obj,
+        jobject parcelObj) {
+    Parcel* parcel = parcelForJavaObject(env, parcelObj);
+    if (parcel) {
+        NativeInputChannel* nativeInputChannel =
+                android_view_InputChannel_getNativeInputChannel(env, obj);
+        if (nativeInputChannel) {
+            sp<InputChannel> inputChannel = nativeInputChannel->getInputChannel();
+
+            parcel->writeInt32(1);
+            parcel->writeString8(inputChannel->getName());
+            parcel->writeDupFileDescriptor(inputChannel->getAshmemFd());
+            parcel->writeDupFileDescriptor(inputChannel->getReceivePipeFd());
+            parcel->writeDupFileDescriptor(inputChannel->getSendPipeFd());
+        } else {
+            parcel->writeInt32(0);
+        }
+    }
+}
+
+static jstring android_view_InputChannel_nativeGetName(JNIEnv* env, jobject obj) {
+    NativeInputChannel* nativeInputChannel =
+            android_view_InputChannel_getNativeInputChannel(env, obj);
+    if (! nativeInputChannel) {
+        return NULL;
+    }
+
+    jstring name = env->NewStringUTF(nativeInputChannel->getInputChannel()->getName().string());
+    return name;
+}
+
+// ----------------------------------------------------------------------------
+
+static JNINativeMethod gInputChannelMethods[] = {
+    /* name, signature, funcPtr */
+    { "nativeOpenInputChannelPair", "(Ljava/lang/String;)[Landroid/view/InputChannel;",
+            (void*)android_view_InputChannel_nativeOpenInputChannelPair },
+    { "nativeDispose", "(Z)V",
+            (void*)android_view_InputChannel_nativeDispose },
+    { "nativeTransferTo", "(Landroid/view/InputChannel;)V",
+            (void*)android_view_InputChannel_nativeTransferTo },
+    { "nativeReadFromParcel", "(Landroid/os/Parcel;)V",
+            (void*)android_view_InputChannel_nativeReadFromParcel },
+    { "nativeWriteToParcel", "(Landroid/os/Parcel;)V",
+            (void*)android_view_InputChannel_nativeWriteToParcel },
+    { "nativeGetName", "()Ljava/lang/String;",
+            (void*)android_view_InputChannel_nativeGetName },
+};
+
+#define FIND_CLASS(var, className) \
+        var = env->FindClass(className); \
+        LOG_FATAL_IF(! var, "Unable to find class " className); \
+        var = jclass(env->NewGlobalRef(var));
+
+#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+        var = env->GetMethodID(clazz, methodName, methodDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find method " methodName);
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+        var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+int register_android_view_InputChannel(JNIEnv* env) {
+    int res = jniRegisterNativeMethods(env, "android/view/InputChannel",
+            gInputChannelMethods, NELEM(gInputChannelMethods));
+    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+
+    FIND_CLASS(gInputChannelClassInfo.clazz, "android/view/InputChannel");
+
+    GET_FIELD_ID(gInputChannelClassInfo.mPtr, gInputChannelClassInfo.clazz,
+            "mPtr", "I");
+    
+    GET_METHOD_ID(gInputChannelClassInfo.ctor, gInputChannelClassInfo.clazz,
+            "<init>", "()V");
+
+    return 0;
+}
+
+} // namespace android
diff --git a/core/jni/android_view_InputChannel.h b/core/jni/android_view_InputChannel.h
new file mode 100644
index 0000000..ac1defb
--- /dev/null
+++ b/core/jni/android_view_InputChannel.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_VIEW_INPUTCHANNEL_H
+#define _ANDROID_VIEW_INPUTCHANNEL_H
+
+#include "jni.h"
+
+namespace android {
+
+class InputChannel;
+
+typedef void (*InputChannelObjDisposeCallback)(JNIEnv* env, jobject inputChannelObj,
+        const sp<InputChannel>& inputChannel, void* data);
+
+extern sp<InputChannel> android_view_InputChannel_getInputChannel(JNIEnv* env,
+        jobject inputChannelObj);
+
+/* Sets a callback that is invoked when the InputChannel DVM object is disposed (or finalized).
+ * This is used to automatically dispose of other native objects in the input dispatcher
+ * and input queue to prevent memory leaks. */
+extern void android_view_InputChannel_setDisposeCallback(JNIEnv* env, jobject inputChannelObj,
+        InputChannelObjDisposeCallback callback, void* data = NULL);
+
+} // namespace android
+
+#endif // _ANDROID_OS_INPUTCHANNEL_H
diff --git a/core/jni/android_view_InputQueue.cpp b/core/jni/android_view_InputQueue.cpp
new file mode 100644
index 0000000..9cbde25
--- /dev/null
+++ b/core/jni/android_view_InputQueue.cpp
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputQueue-JNI"
+
+//#define LOG_NDEBUG 0
+
+// Log debug messages about the dispatch cycle.
+#define DEBUG_DISPATCH_CYCLE 1
+
+
+#include "JNIHelp.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/Log.h>
+#include <utils/PollLoop.h>
+#include <utils/KeyedVector.h>
+#include <utils/threads.h>
+#include <ui/InputTransport.h>
+#include "android_os_MessageQueue.h"
+#include "android_view_InputChannel.h"
+#include "android_view_KeyEvent.h"
+#include "android_view_MotionEvent.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+static struct {
+    jclass clazz;
+
+    jmethodID dispatchKeyEvent;
+    jmethodID dispatchMotionEvent;
+} gInputQueueClassInfo;
+
+// ----------------------------------------------------------------------------
+
+class NativeInputQueue {
+public:
+    NativeInputQueue();
+    virtual ~NativeInputQueue();
+
+    status_t registerInputChannel(JNIEnv* env, jobject inputChannelObj,
+            jobject inputHandlerObj, jobject messageQueueObj);
+
+    status_t unregisterInputChannel(JNIEnv* env, jobject inputChannelObj);
+
+    status_t finished(JNIEnv* env, jlong finishedToken, bool ignoreSpuriousFinish);
+
+private:
+    class Connection : public RefBase {
+    protected:
+        virtual ~Connection();
+
+    public:
+        enum Status {
+            // Everything is peachy.
+            STATUS_NORMAL,
+            // The input channel has been unregistered.
+            STATUS_ZOMBIE
+        };
+
+        Connection(const sp<InputChannel>& inputChannel, const sp<PollLoop>& pollLoop);
+
+        inline const char* getInputChannelName() { return inputChannel->getName().string(); }
+
+        Status status;
+
+        sp<InputChannel> inputChannel;
+        InputConsumer inputConsumer;
+        sp<PollLoop> pollLoop;
+        jobject inputHandlerObjGlobal;
+        PreallocatedInputEventFactory inputEventFactory;
+
+        // The sequence number of the current event being dispatched.
+        // This is used as part of the finished token as a way to determine whether the finished
+        // token is still valid before sending a finished signal back to the publisher.
+        uint32_t messageSeqNum;
+
+        // True if a message has been received from the publisher but not yet finished.
+        bool messageInProgress;
+    };
+
+    Mutex mLock;
+    KeyedVector<int32_t, sp<Connection> > mConnectionsByReceiveFd;
+
+    static void handleInputChannelDisposed(JNIEnv* env,
+            jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data);
+
+    static bool handleReceiveCallback(int receiveFd, int events, void* data);
+
+    static jlong generateFinishedToken(int32_t receiveFd, int32_t messageSeqNum);
+
+    static void parseFinishedToken(jlong finishedToken,
+            int32_t* outReceiveFd, uint32_t* outMessageIndex);
+};
+
+// ----------------------------------------------------------------------------
+
+NativeInputQueue::NativeInputQueue() {
+}
+
+NativeInputQueue::~NativeInputQueue() {
+}
+
+status_t NativeInputQueue::registerInputChannel(JNIEnv* env, jobject inputChannelObj,
+        jobject inputHandlerObj, jobject messageQueueObj) {
+    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
+            inputChannelObj);
+    if (inputChannel == NULL) {
+        LOGW("Input channel is not initialized.");
+        return BAD_VALUE;
+    }
+
+    sp<PollLoop> pollLoop = android_os_MessageQueue_getPollLoop(env, messageQueueObj);
+
+    int receiveFd;
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        receiveFd = inputChannel->getReceivePipeFd();
+        if (mConnectionsByReceiveFd.indexOfKey(receiveFd) >= 0) {
+            LOGW("Attempted to register already registered input channel '%s'",
+                    inputChannel->getName().string());
+            return BAD_VALUE;
+        }
+
+        sp<Connection> connection = new Connection(inputChannel, pollLoop);
+        status_t result = connection->inputConsumer.initialize();
+        if (result) {
+            LOGW("Failed to initialize input consumer for input channel '%s', status=%d",
+                    inputChannel->getName().string(), result);
+            return result;
+        }
+
+        connection->inputHandlerObjGlobal = env->NewGlobalRef(inputHandlerObj);
+
+        mConnectionsByReceiveFd.add(receiveFd, connection);
+    } // release lock
+
+    android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
+            handleInputChannelDisposed, this);
+
+    pollLoop->setCallback(receiveFd, POLLIN, handleReceiveCallback, NULL);
+    return OK;
+}
+
+status_t NativeInputQueue::unregisterInputChannel(JNIEnv* env, jobject inputChannelObj) {
+    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
+            inputChannelObj);
+    if (inputChannel == NULL) {
+        LOGW("Input channel is not initialized.");
+        return BAD_VALUE;
+    }
+
+    int32_t receiveFd;
+    sp<Connection> connection;
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        receiveFd = inputChannel->getReceivePipeFd();
+        ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd);
+        if (connectionIndex < 0) {
+            LOGW("Attempted to unregister already unregistered input channel '%s'",
+                    inputChannel->getName().string());
+            return BAD_VALUE;
+        }
+
+        connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+        mConnectionsByReceiveFd.removeItemsAt(connectionIndex);
+
+        connection->status = Connection::STATUS_ZOMBIE;
+
+        env->DeleteGlobalRef(connection->inputHandlerObjGlobal);
+        connection->inputHandlerObjGlobal = NULL;
+    } // release lock
+
+    android_view_InputChannel_setDisposeCallback(env, inputChannelObj, NULL, NULL);
+
+    connection->pollLoop->removeCallback(receiveFd);
+    return OK;
+}
+
+status_t NativeInputQueue::finished(JNIEnv* env, jlong finishedToken, bool ignoreSpuriousFinish) {
+    int32_t receiveFd;
+    uint32_t messageSeqNum;
+    parseFinishedToken(finishedToken, &receiveFd, &messageSeqNum);
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd);
+        if (connectionIndex < 0) {
+            if (! ignoreSpuriousFinish) {
+                LOGW("Attempted to finish input on channel that is no longer registered.");
+            }
+            return DEAD_OBJECT;
+        }
+
+        sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+        if (messageSeqNum != connection->messageSeqNum || ! connection->messageInProgress) {
+            if (! ignoreSpuriousFinish) {
+                LOGW("Attempted to finish input twice on channel '%s'.",
+                        connection->getInputChannelName());
+            }
+            return INVALID_OPERATION;
+        }
+
+        connection->messageInProgress = false;
+
+        status_t status = connection->inputConsumer.sendFinishedSignal();
+        if (status) {
+            LOGW("Failed to send finished signal on channel '%s'.  status=%d",
+                    connection->getInputChannelName(), status);
+            return status;
+        }
+
+#if DEBUG_DISPATCH_CYCLE
+        LOGD("channel '%s' ~ Finished event.",
+                connection->getInputChannelName());
+#endif
+    } // release lock
+
+    return OK;
+}
+
+void NativeInputQueue::handleInputChannelDisposed(JNIEnv* env,
+        jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data) {
+    LOGW("Input channel object '%s' was disposed without first being unregistered with "
+            "the input queue!", inputChannel->getName().string());
+
+    NativeInputQueue* q = static_cast<NativeInputQueue*>(data);
+    q->unregisterInputChannel(env, inputChannelObj);
+}
+
+bool NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* data) {
+    NativeInputQueue* q = static_cast<NativeInputQueue*>(data);
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+
+    sp<Connection> connection;
+    InputEvent* inputEvent;
+    jobject inputHandlerObjLocal;
+    jlong finishedToken;
+    { // acquire lock
+        AutoMutex _l(q->mLock);
+
+        ssize_t connectionIndex = q->mConnectionsByReceiveFd.indexOfKey(receiveFd);
+        if (connectionIndex < 0) {
+            LOGE("Received spurious receive callback for unknown input channel.  "
+                    "fd=%d, events=0x%x", receiveFd, events);
+            return false; // remove the callback
+        }
+
+        connection = q->mConnectionsByReceiveFd.valueAt(connectionIndex);
+        if (events & (POLLERR | POLLHUP | POLLNVAL)) {
+            LOGE("channel '%s' ~ Publisher closed input channel or an error occurred.  "
+                    "events=0x%x", connection->getInputChannelName(), events);
+            return false; // remove the callback
+        }
+
+        if (! (events & POLLIN)) {
+            LOGW("channel '%s' ~ Received spurious callback for unhandled poll event.  "
+                    "events=0x%x", connection->getInputChannelName(), events);
+            return true;
+        }
+
+        status_t status = connection->inputConsumer.receiveDispatchSignal();
+        if (status) {
+            LOGE("channel '%s' ~ Failed to receive dispatch signal.  status=%d",
+                    connection->getInputChannelName(), status);
+            return false; // remove the callback
+        }
+
+        if (connection->messageInProgress) {
+            LOGW("channel '%s' ~ Publisher sent spurious dispatch signal.",
+                    connection->getInputChannelName());
+            return true;
+        }
+
+        status = connection->inputConsumer.consume(& connection->inputEventFactory, & inputEvent);
+        if (status) {
+            LOGW("channel '%s' ~ Failed to consume input event.  status=%d",
+                    connection->getInputChannelName(), status);
+            connection->inputConsumer.sendFinishedSignal();
+            return true;
+        }
+
+        connection->messageInProgress = true;
+        connection->messageSeqNum += 1;
+
+        finishedToken = generateFinishedToken(receiveFd, connection->messageSeqNum);
+
+        inputHandlerObjLocal = env->NewLocalRef(connection->inputHandlerObjGlobal);
+    } // release lock
+
+    // Invoke the handler outside of the lock.
+    //
+    // Note: inputEvent is stored in a field of the connection object which could potentially
+    //       become disposed due to the input channel being unregistered concurrently.
+    //       For this reason, we explicitly keep the connection object alive by holding
+    //       a strong pointer to it within this scope.  We also grabbed a local reference to
+    //       the input handler object itself for the same reason.
+
+    int32_t inputEventType = inputEvent->getType();
+    int32_t inputEventNature = inputEvent->getNature();
+
+    jobject inputEventObj;
+    jmethodID dispatchMethodId;
+    switch (inputEventType) {
+    case INPUT_EVENT_TYPE_KEY:
+#if DEBUG_DISPATCH_CYCLE
+        LOGD("channel '%s' ~ Received key event.", connection->getInputChannelName());
+#endif
+        inputEventObj = android_view_KeyEvent_fromNative(env,
+                static_cast<KeyEvent*>(inputEvent));
+        dispatchMethodId = gInputQueueClassInfo.dispatchKeyEvent;
+        break;
+
+    case INPUT_EVENT_TYPE_MOTION:
+#if DEBUG_DISPATCH_CYCLE
+        LOGD("channel '%s' ~ Received motion event.", connection->getInputChannelName());
+#endif
+        inputEventObj = android_view_MotionEvent_fromNative(env,
+                static_cast<MotionEvent*>(inputEvent));
+        dispatchMethodId = gInputQueueClassInfo.dispatchMotionEvent;
+        break;
+
+    default:
+        assert(false); // InputConsumer should prevent this from ever happening
+        inputEventObj = NULL;
+    }
+
+    if (! inputEventObj) {
+        LOGW("channel '%s' ~ Failed to obtain DVM event object.",
+                connection->getInputChannelName());
+        env->DeleteLocalRef(inputHandlerObjLocal);
+        q->finished(env, finishedToken, false);
+        return true;
+    }
+
+#if DEBUG_DISPATCH_CYCLE
+    LOGD("Invoking input handler.");
+#endif
+    env->CallStaticVoidMethod(gInputQueueClassInfo.clazz,
+            dispatchMethodId, inputHandlerObjLocal, inputEventObj,
+            jint(inputEventNature), jlong(finishedToken));
+#if DEBUG_DISPATCH_CYCLE
+    LOGD("Returned from input handler.");
+#endif
+
+    if (env->ExceptionCheck()) {
+        LOGE("An exception occurred while invoking the input handler for an event.");
+        LOGE_EX(env);
+        env->ExceptionClear();
+
+        q->finished(env, finishedToken, true /*ignoreSpuriousFinish*/);
+    }
+
+    env->DeleteLocalRef(inputEventObj);
+    env->DeleteLocalRef(inputHandlerObjLocal);
+    return true;
+}
+
+jlong NativeInputQueue::generateFinishedToken(int32_t receiveFd, int32_t messageSeqNum) {
+    return (jlong(receiveFd) << 32) | jlong(messageSeqNum);
+}
+
+void NativeInputQueue::parseFinishedToken(jlong finishedToken,
+        int32_t* outReceiveFd, uint32_t* outMessageIndex) {
+    *outReceiveFd = int32_t(finishedToken >> 32);
+    *outMessageIndex = uint32_t(finishedToken & 0xffffffff);
+}
+
+// ----------------------------------------------------------------------------
+
+NativeInputQueue::Connection::Connection(const sp<InputChannel>& inputChannel, const sp<PollLoop>& pollLoop) :
+    status(STATUS_NORMAL), inputChannel(inputChannel), inputConsumer(inputChannel),
+    pollLoop(pollLoop), inputHandlerObjGlobal(NULL),
+    messageSeqNum(0), messageInProgress(false) {
+}
+
+NativeInputQueue::Connection::~Connection() {
+}
+
+// ----------------------------------------------------------------------------
+
+static NativeInputQueue gNativeInputQueue;
+
+static void android_view_InputQueue_nativeRegisterInputChannel(JNIEnv* env, jclass clazz,
+        jobject inputChannelObj, jobject inputHandlerObj, jobject messageQueueObj) {
+    status_t status = gNativeInputQueue.registerInputChannel(
+            env, inputChannelObj, inputHandlerObj, messageQueueObj);
+    if (status) {
+        jniThrowRuntimeException(env, "Failed to register input channel.  "
+                "Check logs for details.");
+    }
+}
+
+static void android_view_InputQueue_nativeUnregisterInputChannel(JNIEnv* env, jclass clazz,
+        jobject inputChannelObj) {
+    status_t status = gNativeInputQueue.unregisterInputChannel(env, inputChannelObj);
+    if (status) {
+        jniThrowRuntimeException(env, "Failed to unregister input channel.  "
+                "Check logs for details.");
+    }
+}
+
+static void android_view_InputQueue_nativeFinished(JNIEnv* env, jclass clazz,
+        jlong finishedToken) {
+    status_t status = gNativeInputQueue.finished(
+            env, finishedToken, false /*ignoreSpuriousFinish*/);
+    if (status) {
+        jniThrowRuntimeException(env, "Failed to finish input event.  "
+                "Check logs for details.");
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+static JNINativeMethod gInputQueueMethods[] = {
+    /* name, signature, funcPtr */
+    { "nativeRegisterInputChannel",
+            "(Landroid/view/InputChannel;Landroid/view/InputHandler;Landroid/os/MessageQueue;)V",
+            (void*)android_view_InputQueue_nativeRegisterInputChannel },
+    { "nativeUnregisterInputChannel",
+            "(Landroid/view/InputChannel;)V",
+            (void*)android_view_InputQueue_nativeUnregisterInputChannel },
+    { "nativeFinished", "(J)V",
+            (void*)android_view_InputQueue_nativeFinished }
+};
+
+#define FIND_CLASS(var, className) \
+        var = env->FindClass(className); \
+        LOG_FATAL_IF(! var, "Unable to find class " className); \
+        var = jclass(env->NewGlobalRef(var));
+
+#define GET_STATIC_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+        var = env->GetStaticMethodID(clazz, methodName, methodDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find static method " methodName);
+
+int register_android_view_InputQueue(JNIEnv* env) {
+    int res = jniRegisterNativeMethods(env, "android/view/InputQueue",
+            gInputQueueMethods, NELEM(gInputQueueMethods));
+    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+
+    FIND_CLASS(gInputQueueClassInfo.clazz, "android/view/InputQueue");
+
+    GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchKeyEvent, gInputQueueClassInfo.clazz,
+            "dispatchKeyEvent",
+            "(Landroid/view/InputHandler;Landroid/view/KeyEvent;IJ)V");
+
+    GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchMotionEvent, gInputQueueClassInfo.clazz,
+            "dispatchMotionEvent",
+            "(Landroid/view/InputHandler;Landroid/view/MotionEvent;IJ)V");
+    return 0;
+}
+
+} // namespace android
diff --git a/core/jni/android_view_InputTarget.cpp b/core/jni/android_view_InputTarget.cpp
new file mode 100644
index 0000000..e2a1f23
--- /dev/null
+++ b/core/jni/android_view_InputTarget.cpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputTarget-JNI"
+
+#include "JNIHelp.h"
+
+#include <utils/Log.h>
+#include <ui/InputDispatchPolicy.h>
+#include <ui/InputTransport.h>
+#include "android_view_InputTarget.h"
+#include "android_view_InputChannel.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+static struct {
+    jclass clazz;
+
+    jfieldID mInputChannel;
+    jfieldID mFlags;
+    jfieldID mTimeoutNanos;
+    jfieldID mXOffset;
+    jfieldID mYOffset;
+} gInputTargetClassInfo;
+
+// ----------------------------------------------------------------------------
+
+void android_view_InputTarget_toNative(JNIEnv* env, jobject inputTargetObj,
+        InputTarget* outInputTarget) {
+    jobject inputChannelObj = env->GetObjectField(inputTargetObj,
+            gInputTargetClassInfo.mInputChannel);
+    jint flags = env->GetIntField(inputTargetObj,
+            gInputTargetClassInfo.mFlags);
+    jlong timeoutNanos = env->GetLongField(inputTargetObj,
+            gInputTargetClassInfo.mTimeoutNanos);
+    jfloat xOffset = env->GetFloatField(inputTargetObj,
+            gInputTargetClassInfo.mXOffset);
+    jfloat yOffset = env->GetFloatField(inputTargetObj,
+            gInputTargetClassInfo.mYOffset);
+
+    outInputTarget->inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj);
+    outInputTarget->flags = flags;
+    outInputTarget->timeout = timeoutNanos;
+    outInputTarget->xOffset = xOffset;
+    outInputTarget->yOffset = yOffset;
+
+    env->DeleteLocalRef(inputChannelObj);
+}
+
+// ----------------------------------------------------------------------------
+
+#define FIND_CLASS(var, className) \
+        var = env->FindClass(className); \
+        LOG_FATAL_IF(! var, "Unable to find class " className); \
+        var = jclass(env->NewGlobalRef(var));
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+        var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+int register_android_view_InputTarget(JNIEnv* env) {
+    FIND_CLASS(gInputTargetClassInfo.clazz, "android/view/InputTarget");
+
+    GET_FIELD_ID(gInputTargetClassInfo.mInputChannel, gInputTargetClassInfo.clazz,
+            "mInputChannel", "Landroid/view/InputChannel;");
+
+    GET_FIELD_ID(gInputTargetClassInfo.mFlags, gInputTargetClassInfo.clazz,
+            "mFlags", "I");
+
+    GET_FIELD_ID(gInputTargetClassInfo.mTimeoutNanos, gInputTargetClassInfo.clazz,
+            "mTimeoutNanos", "J");
+
+    GET_FIELD_ID(gInputTargetClassInfo.mXOffset, gInputTargetClassInfo.clazz,
+            "mXOffset", "F");
+
+    GET_FIELD_ID(gInputTargetClassInfo.mYOffset, gInputTargetClassInfo.clazz,
+            "mYOffset", "F");
+    
+    return 0;
+}
+
+} // namespace android
diff --git a/core/jni/android_view_InputTarget.h b/core/jni/android_view_InputTarget.h
new file mode 100644
index 0000000..9230b1b
--- /dev/null
+++ b/core/jni/android_view_InputTarget.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_VIEW_INPUTTARGET_H
+#define _ANDROID_VIEW_INPUTTARGET_H
+
+#include "jni.h"
+
+namespace android {
+
+class InputTarget;
+
+extern void android_view_InputTarget_toNative(JNIEnv* env, jobject inputTargetObj,
+        InputTarget* outInputTarget);
+
+} // namespace android
+
+#endif // _ANDROID_OS_INPUTTARGET_H
diff --git a/core/jni/android_view_KeyEvent.cpp b/core/jni/android_view_KeyEvent.cpp
new file mode 100644
index 0000000..df3b952
--- /dev/null
+++ b/core/jni/android_view_KeyEvent.cpp
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "KeyEvent-JNI"
+
+#include "JNIHelp.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/Log.h>
+#include <ui/Input.h>
+#include "android_view_KeyEvent.h"
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+static struct {
+    jclass clazz;
+
+    jmethodID ctor;
+
+    jfieldID mMetaState;
+    jfieldID mAction;
+    jfieldID mKeyCode;
+    jfieldID mScanCode;
+    jfieldID mRepeatCount;
+    jfieldID mDeviceId;
+    jfieldID mFlags;
+    jfieldID mDownTime;
+    jfieldID mEventTime;
+    jfieldID mCharacters;
+} gKeyEventClassInfo;
+
+// ----------------------------------------------------------------------------
+
+jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent* event) {
+    return env->NewObject(gKeyEventClassInfo.clazz, gKeyEventClassInfo.ctor,
+            nanoseconds_to_milliseconds(event->getDownTime()),
+            nanoseconds_to_milliseconds(event->getEventTime()),
+            event->getAction(),
+            event->getKeyCode(),
+            event->getRepeatCount(),
+            event->getMetaState(),
+            event->getDeviceId(),
+            event->getScanCode(),
+            event->getFlags());
+}
+
+void android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj, int32_t nature,
+        KeyEvent* event) {
+    jint metaState = env->GetIntField(eventObj, gKeyEventClassInfo.mMetaState);
+    jint action = env->GetIntField(eventObj, gKeyEventClassInfo.mAction);
+    jint keyCode = env->GetIntField(eventObj, gKeyEventClassInfo.mKeyCode);
+    jint scanCode = env->GetIntField(eventObj, gKeyEventClassInfo.mScanCode);
+    jint repeatCount = env->GetIntField(eventObj, gKeyEventClassInfo.mRepeatCount);
+    jint deviceId = env->GetIntField(eventObj, gKeyEventClassInfo.mDeviceId);
+    jint flags = env->GetIntField(eventObj, gKeyEventClassInfo.mFlags);
+    jlong downTime = env->GetLongField(eventObj, gKeyEventClassInfo.mDownTime);
+    jlong eventTime = env->GetLongField(eventObj, gKeyEventClassInfo.mEventTime);
+
+    event->initialize(deviceId, nature, action, flags, keyCode, scanCode, metaState, repeatCount,
+            milliseconds_to_nanoseconds(downTime),
+            milliseconds_to_nanoseconds(eventTime));
+}
+
+// ----------------------------------------------------------------------------
+
+#define FIND_CLASS(var, className) \
+        var = env->FindClass(className); \
+        LOG_FATAL_IF(! var, "Unable to find class " className); \
+        var = jclass(env->NewGlobalRef(var));
+
+#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \
+        var = env->GetMethodID(clazz, methodName, fieldDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find method" methodName);
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+        var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+int register_android_view_KeyEvent(JNIEnv* env) {
+    FIND_CLASS(gKeyEventClassInfo.clazz, "android/view/KeyEvent");
+
+    GET_METHOD_ID(gKeyEventClassInfo.ctor, gKeyEventClassInfo.clazz,
+            "<init>", "(JJIIIIIII)V");
+
+    GET_FIELD_ID(gKeyEventClassInfo.mMetaState, gKeyEventClassInfo.clazz,
+            "mMetaState", "I");
+    GET_FIELD_ID(gKeyEventClassInfo.mAction, gKeyEventClassInfo.clazz,
+            "mAction", "I");
+    GET_FIELD_ID(gKeyEventClassInfo.mKeyCode, gKeyEventClassInfo.clazz,
+            "mKeyCode", "I");
+    GET_FIELD_ID(gKeyEventClassInfo.mScanCode, gKeyEventClassInfo.clazz,
+            "mScanCode", "I");
+    GET_FIELD_ID(gKeyEventClassInfo.mRepeatCount, gKeyEventClassInfo.clazz,
+            "mRepeatCount", "I");
+    GET_FIELD_ID(gKeyEventClassInfo.mDeviceId, gKeyEventClassInfo.clazz,
+            "mDeviceId", "I");
+    GET_FIELD_ID(gKeyEventClassInfo.mFlags, gKeyEventClassInfo.clazz,
+            "mFlags", "I");
+    GET_FIELD_ID(gKeyEventClassInfo.mDownTime, gKeyEventClassInfo.clazz,
+            "mDownTime", "J");
+    GET_FIELD_ID(gKeyEventClassInfo.mEventTime, gKeyEventClassInfo.clazz,
+            "mEventTime", "J");
+    GET_FIELD_ID(gKeyEventClassInfo.mCharacters, gKeyEventClassInfo.clazz,
+            "mCharacters", "Ljava/lang/String;");
+
+    return 0;
+}
+
+} // namespace android
diff --git a/core/jni/android_view_KeyEvent.h b/core/jni/android_view_KeyEvent.h
new file mode 100644
index 0000000..3c71b1a
--- /dev/null
+++ b/core/jni/android_view_KeyEvent.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_VIEW_KEYEVENT_H
+#define _ANDROID_VIEW_KEYEVENT_H
+
+#include "jni.h"
+
+namespace android {
+
+class KeyEvent;
+
+/* Obtains an instance of a DVM KeyEvent object as a copy of a native KeyEvent instance. */
+extern jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent* event);
+
+/* Copies the contents of a DVM KeyEvent object to a native KeyEvent instance. */
+extern void android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj, int32_t nature,
+        KeyEvent* event);
+
+} // namespace android
+
+#endif // _ANDROID_OS_KEYEVENT_H
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
new file mode 100644
index 0000000..629c8fe
--- /dev/null
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MotionEvent-JNI"
+
+#include "JNIHelp.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/Log.h>
+#include <ui/Input.h>
+#include "android_view_MotionEvent.h"
+
+// Number of float items per entry in a DVM sample data array
+#define NUM_SAMPLE_DATA 4
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+static struct {
+    jclass clazz;
+
+    jmethodID obtain;
+    jmethodID recycle;
+
+    jfieldID mDownTime;
+    jfieldID mEventTimeNano;
+    jfieldID mAction;
+    jfieldID mRawX;
+    jfieldID mRawY;
+    jfieldID mXPrecision;
+    jfieldID mYPrecision;
+    jfieldID mDeviceId;
+    jfieldID mEdgeFlags;
+    jfieldID mMetaState;
+    jfieldID mNumPointers;
+    jfieldID mNumSamples;
+    jfieldID mPointerIdentifiers;
+    jfieldID mDataSamples;
+    jfieldID mTimeSamples;
+} gMotionEventClassInfo;
+
+// ----------------------------------------------------------------------------
+
+jobject android_view_MotionEvent_fromNative(JNIEnv* env, const MotionEvent* event) {
+    jint numPointers = jint(event->getPointerCount());
+    jint numHistoricalSamples = jint(event->getHistorySize());
+    jint numSamples = numHistoricalSamples + 1;
+
+    jobject eventObj = env->CallStaticObjectMethod(gMotionEventClassInfo.clazz,
+            gMotionEventClassInfo.obtain, numPointers, numSamples);
+    if (env->ExceptionCheck()) {
+        LOGE("An exception occurred while obtaining a motion event.");
+        LOGE_EX(env);
+        env->ExceptionClear();
+        return NULL;
+    }
+
+    // MotionEvent.mEventTimeNano is the time of the oldest sample because
+    // MotionEvent.addBatch does not update it as successive samples are added.
+    jlong eventTimeNano = numHistoricalSamples != 0
+            ? event->getHistoricalEventTime(0)
+            : event->getEventTime();
+
+    env->SetLongField(eventObj, gMotionEventClassInfo.mDownTime,
+            nanoseconds_to_milliseconds(event->getDownTime()));
+    env->SetLongField(eventObj, gMotionEventClassInfo.mEventTimeNano,
+            eventTimeNano);
+    env->SetIntField(eventObj, gMotionEventClassInfo.mAction,
+            event->getAction());
+    env->SetFloatField(eventObj, gMotionEventClassInfo.mRawX,
+            event->getRawX());
+    env->SetFloatField(eventObj, gMotionEventClassInfo.mRawY,
+            event->getRawY());
+    env->SetFloatField(eventObj, gMotionEventClassInfo.mXPrecision,
+            event->getXPrecision());
+    env->SetFloatField(eventObj, gMotionEventClassInfo.mYPrecision,
+            event->getYPrecision());
+    env->SetIntField(eventObj, gMotionEventClassInfo.mDeviceId,
+            event->getDeviceId());
+    env->SetIntField(eventObj, gMotionEventClassInfo.mEdgeFlags,
+            event->getEdgeFlags());
+    env->SetIntField(eventObj, gMotionEventClassInfo.mMetaState,
+            event->getMetaState());
+    env->SetIntField(eventObj, gMotionEventClassInfo.mNumPointers,
+            numPointers);
+    env->SetIntField(eventObj, gMotionEventClassInfo.mNumSamples,
+            numSamples);
+
+    jintArray pointerIdentifierArray = jintArray(env->GetObjectField(eventObj,
+            gMotionEventClassInfo.mPointerIdentifiers));
+    jfloatArray dataSampleArray = jfloatArray(env->GetObjectField(eventObj,
+            gMotionEventClassInfo.mDataSamples));
+    jlongArray timeSampleArray = jlongArray(env->GetObjectField(eventObj,
+            gMotionEventClassInfo.mTimeSamples));
+
+    jint* pointerIdentifiers = (jint*)env->GetPrimitiveArrayCritical(pointerIdentifierArray, NULL);
+    jfloat* dataSamples = (jfloat*)env->GetPrimitiveArrayCritical(dataSampleArray, NULL);
+    jlong* timeSamples = (jlong*)env->GetPrimitiveArrayCritical(timeSampleArray, NULL);
+
+    for (jint i = 0; i < numPointers; i++) {
+        pointerIdentifiers[i] = event->getPointerId(i);
+    }
+
+    // Most recent data is in first slot of the DVM array, followed by the oldest,
+    // and then all others are in order.
+
+    jfloat* currentDataSample = dataSamples;
+    jlong* currentTimeSample = timeSamples;
+
+    *(currentTimeSample++) = nanoseconds_to_milliseconds(event->getEventTime());
+    for (jint j = 0; j < numPointers; j++) {
+        *(currentDataSample++) = event->getX(j);
+        *(currentDataSample++) = event->getY(j);
+        *(currentDataSample++) = event->getPressure(j);
+        *(currentDataSample++) = event->getSize(j);
+    }
+
+    for (jint i = 0; i < numHistoricalSamples; i++) {
+        *(currentTimeSample++) = nanoseconds_to_milliseconds(event->getHistoricalEventTime(i));
+        for (jint j = 0; j < numPointers; j++) {
+            *(currentDataSample++) = event->getHistoricalX(j, i);
+            *(currentDataSample++) = event->getHistoricalY(j, i);
+            *(currentDataSample++) = event->getHistoricalPressure(j, i);
+            *(currentDataSample++) = event->getHistoricalSize(j, i);
+        }
+    }
+
+    env->ReleasePrimitiveArrayCritical(pointerIdentifierArray, pointerIdentifiers, 0);
+    env->ReleasePrimitiveArrayCritical(dataSampleArray, dataSamples, 0);
+    env->ReleasePrimitiveArrayCritical(timeSampleArray, timeSamples, 0);
+
+    env->DeleteLocalRef(pointerIdentifierArray);
+    env->DeleteLocalRef(dataSampleArray);
+    env->DeleteLocalRef(timeSampleArray);
+    return eventObj;
+}
+
+void android_view_MotionEvent_toNative(JNIEnv* env, jobject eventObj, int32_t nature,
+        MotionEvent* event) {
+    // MotionEvent.mEventTimeNano is the time of the oldest sample because
+    // MotionEvent.addBatch does not update it as successive samples are added.
+    jlong downTime = env->GetLongField(eventObj, gMotionEventClassInfo.mDownTime);
+    jlong eventTimeNano = env->GetLongField(eventObj, gMotionEventClassInfo.mEventTimeNano);
+    jint action = env->GetIntField(eventObj, gMotionEventClassInfo.mAction);
+    jfloat rawX = env->GetFloatField(eventObj, gMotionEventClassInfo.mRawX);
+    jfloat rawY = env->GetFloatField(eventObj, gMotionEventClassInfo.mRawY);
+    jfloat xPrecision = env->GetFloatField(eventObj, gMotionEventClassInfo.mXPrecision);
+    jfloat yPrecision = env->GetFloatField(eventObj, gMotionEventClassInfo.mYPrecision);
+    jint deviceId = env->GetIntField(eventObj, gMotionEventClassInfo.mDeviceId);
+    jint edgeFlags = env->GetIntField(eventObj, gMotionEventClassInfo.mEdgeFlags);
+    jint metaState = env->GetIntField(eventObj, gMotionEventClassInfo.mMetaState);
+    jint numPointers = env->GetIntField(eventObj, gMotionEventClassInfo.mNumPointers);
+    jint numSamples = env->GetIntField(eventObj, gMotionEventClassInfo.mNumSamples);
+    jintArray pointerIdentifierArray = jintArray(env->GetObjectField(eventObj,
+            gMotionEventClassInfo.mPointerIdentifiers));
+    jfloatArray dataSampleArray = jfloatArray(env->GetObjectField(eventObj,
+            gMotionEventClassInfo.mDataSamples));
+    jlongArray timeSampleArray = jlongArray(env->GetObjectField(eventObj,
+            gMotionEventClassInfo.mTimeSamples));
+
+    LOG_FATAL_IF(numPointers == 0, "numPointers was zero");
+    LOG_FATAL_IF(numSamples == 0, "numSamples was zero");
+
+    jint* pointerIdentifiers = (jint*)env->GetPrimitiveArrayCritical(pointerIdentifierArray, NULL);
+    jfloat* dataSamples = (jfloat*)env->GetPrimitiveArrayCritical(dataSampleArray, NULL);
+    jlong* timeSamples = (jlong*)env->GetPrimitiveArrayCritical(timeSampleArray, NULL);
+
+    // Most recent data is in first slot of the DVM array, followed by the oldest,
+    // and then all others are in order.  eventTimeNano is the time of the oldest sample
+    // since MotionEvent.addBatch does not update it.
+
+    jint numHistoricalSamples = numSamples - 1;
+    jint dataSampleStride = numPointers * NUM_SAMPLE_DATA;
+
+    const jfloat* currentDataSample;
+    const jlong* currentTimeSample;
+    if (numHistoricalSamples == 0) {
+        currentDataSample = dataSamples;
+        currentTimeSample = timeSamples;
+    } else {
+        currentDataSample = dataSamples + dataSampleStride;
+        currentTimeSample = timeSamples + 1;
+    }
+
+    PointerCoords pointerCoords[MAX_POINTERS];
+    for (jint j = 0; j < numPointers; j++) {
+        pointerCoords[j].x = *(currentDataSample++);
+        pointerCoords[j].y = *(currentDataSample++);
+        pointerCoords[j].pressure = *(currentDataSample++);
+        pointerCoords[j].size = *(currentDataSample++);
+    }
+
+    event->initialize(deviceId, nature, action, edgeFlags, metaState,
+            rawX, rawY, xPrecision, yPrecision,
+            milliseconds_to_nanoseconds(downTime), eventTimeNano,
+            numPointers, pointerIdentifiers, pointerCoords);
+
+    while (numHistoricalSamples > 0) {
+        numHistoricalSamples -= 1;
+        if (numHistoricalSamples == 0) {
+            currentDataSample = dataSamples;
+            currentTimeSample = timeSamples;
+        }
+
+        nsecs_t sampleEventTime = milliseconds_to_nanoseconds(*(currentTimeSample++));
+
+        for (jint j = 0; j < numPointers; j++) {
+            pointerCoords[j].x = *(currentDataSample++);
+            pointerCoords[j].y = *(currentDataSample++);
+            pointerCoords[j].pressure = *(currentDataSample++);
+            pointerCoords[j].size = *(currentDataSample++);
+        }
+
+        event->addSample(sampleEventTime, pointerCoords);
+    }
+
+    env->ReleasePrimitiveArrayCritical(pointerIdentifierArray, pointerIdentifiers, JNI_ABORT);
+    env->ReleasePrimitiveArrayCritical(dataSampleArray, dataSamples, JNI_ABORT);
+    env->ReleasePrimitiveArrayCritical(timeSampleArray, timeSamples, JNI_ABORT);
+
+    env->DeleteLocalRef(pointerIdentifierArray);
+    env->DeleteLocalRef(dataSampleArray);
+    env->DeleteLocalRef(timeSampleArray);
+}
+
+void android_view_MotionEvent_recycle(JNIEnv* env, jobject eventObj) {
+    env->CallVoidMethod(eventObj, gMotionEventClassInfo.recycle);
+    if (env->ExceptionCheck()) {
+        LOGW("An exception occurred while recycling a motion event.");
+        LOGW_EX(env);
+        env->ExceptionClear();
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+#define FIND_CLASS(var, className) \
+        var = env->FindClass(className); \
+        LOG_FATAL_IF(! var, "Unable to find class " className); \
+        var = jclass(env->NewGlobalRef(var));
+
+#define GET_STATIC_METHOD_ID(var, clazz, methodName, fieldDescriptor) \
+        var = env->GetStaticMethodID(clazz, methodName, fieldDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find static method" methodName);
+
+#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \
+        var = env->GetMethodID(clazz, methodName, fieldDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find method" methodName);
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+        var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+int register_android_view_MotionEvent(JNIEnv* env) {
+    FIND_CLASS(gMotionEventClassInfo.clazz, "android/view/MotionEvent");
+
+    GET_STATIC_METHOD_ID(gMotionEventClassInfo.obtain, gMotionEventClassInfo.clazz,
+            "obtain", "(II)Landroid/view/MotionEvent;");
+    GET_METHOD_ID(gMotionEventClassInfo.recycle, gMotionEventClassInfo.clazz,
+            "recycle", "()V");
+
+    GET_FIELD_ID(gMotionEventClassInfo.mDownTime, gMotionEventClassInfo.clazz,
+            "mDownTime", "J");
+    GET_FIELD_ID(gMotionEventClassInfo.mEventTimeNano, gMotionEventClassInfo.clazz,
+            "mEventTimeNano", "J");
+    GET_FIELD_ID(gMotionEventClassInfo.mAction, gMotionEventClassInfo.clazz,
+            "mAction", "I");
+    GET_FIELD_ID(gMotionEventClassInfo.mRawX, gMotionEventClassInfo.clazz,
+            "mRawX", "F");
+    GET_FIELD_ID(gMotionEventClassInfo.mRawY, gMotionEventClassInfo.clazz,
+            "mRawY", "F");
+    GET_FIELD_ID(gMotionEventClassInfo.mXPrecision, gMotionEventClassInfo.clazz,
+            "mXPrecision", "F");
+    GET_FIELD_ID(gMotionEventClassInfo.mYPrecision, gMotionEventClassInfo.clazz,
+            "mYPrecision", "F");
+    GET_FIELD_ID(gMotionEventClassInfo.mDeviceId, gMotionEventClassInfo.clazz,
+            "mDeviceId", "I");
+    GET_FIELD_ID(gMotionEventClassInfo.mEdgeFlags, gMotionEventClassInfo.clazz,
+            "mEdgeFlags", "I");
+    GET_FIELD_ID(gMotionEventClassInfo.mMetaState, gMotionEventClassInfo.clazz,
+            "mMetaState", "I");
+    GET_FIELD_ID(gMotionEventClassInfo.mNumPointers, gMotionEventClassInfo.clazz,
+            "mNumPointers", "I");
+    GET_FIELD_ID(gMotionEventClassInfo.mNumSamples, gMotionEventClassInfo.clazz,
+            "mNumSamples", "I");
+    GET_FIELD_ID(gMotionEventClassInfo.mPointerIdentifiers, gMotionEventClassInfo.clazz,
+            "mPointerIdentifiers", "[I");
+    GET_FIELD_ID(gMotionEventClassInfo.mDataSamples, gMotionEventClassInfo.clazz,
+            "mDataSamples", "[F");
+    GET_FIELD_ID(gMotionEventClassInfo.mTimeSamples, gMotionEventClassInfo.clazz,
+            "mTimeSamples", "[J");
+
+    return 0;
+}
+
+} // namespace android
diff --git a/core/jni/android_view_MotionEvent.h b/core/jni/android_view_MotionEvent.h
new file mode 100644
index 0000000..03ee32f
--- /dev/null
+++ b/core/jni/android_view_MotionEvent.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_VIEW_MOTIONEVENT_H
+#define _ANDROID_VIEW_MOTIONEVENT_H
+
+#include "jni.h"
+
+namespace android {
+
+class MotionEvent;
+
+/* Obtains an instance of a DVM MotionEvent object as a copy of a native MotionEvent instance. */
+extern jobject android_view_MotionEvent_fromNative(JNIEnv* env, const MotionEvent* event);
+
+/* Copies the contents of a DVM MotionEvent object to a native MotionEvent instance. */
+extern void android_view_MotionEvent_toNative(JNIEnv* env, jobject eventObj, int32_t nature,
+        MotionEvent* event);
+
+/* Recycles a DVM MotionEvent object. */
+extern void android_view_MotionEvent_recycle(JNIEnv* env, jobject eventObj);
+
+} // namespace android
+
+#endif // _ANDROID_OS_KEYEVENT_H
diff --git a/core/jni/android_view_ViewRoot.cpp b/core/jni/android_view_ViewRoot.cpp
index 70ad8c5..5173bb8 100644
--- a/core/jni/android_view_ViewRoot.cpp
+++ b/core/jni/android_view_ViewRoot.cpp
@@ -80,38 +80,6 @@
     SkGLCanvas::AbandonAllTextures();
 }
 
-static jintArray android_view_ViewRoot_makeInputChannel(JNIEnv* env, jobject) {
-    int fd[2];
-    jint* arrayData = NULL;
-
-    // Create the pipe
-    int err = socketpair(AF_LOCAL, SOCK_STREAM, 0, fd);
-    if (err != 0) {
-        fprintf(stderr, "socketpair() failed: %d\n", errno);
-        doThrow(env, "java/lang/RuntimeException", "Unable to create pipe");
-        return NULL;
-    }
-
-    // Set up the return array
-    jintArray array = env->NewIntArray(2);
-    if (env->ExceptionCheck()) {
-        fprintf(stderr, "Exception allocating fd array");
-        goto bail;
-    }
-
-    arrayData = env->GetIntArrayElements(array, 0);
-    arrayData[0] = fd[0];
-    arrayData[1] = fd[1];
-    env->ReleaseIntArrayElements(array, arrayData, 0);
-
-    return array;
-
-bail:
-    env->DeleteLocalRef(array);
-    close(fd[0]);
-    close(fd[1]);
-    return NULL;
-}
 
 // ----------------------------------------------------------------------------
 
@@ -121,9 +89,7 @@
     {   "nativeShowFPS", "(Landroid/graphics/Canvas;I)V",
                                         (void*)android_view_ViewRoot_showFPS },
     {   "nativeAbandonGlCaches", "()V", 
-                                (void*)android_view_ViewRoot_abandonGlCaches },
-    {   "makeInputChannel", "()[I",
-                                        (void*)android_view_ViewRoot_makeInputChannel }
+                                (void*)android_view_ViewRoot_abandonGlCaches }
 };
 
 int register_android_view_ViewRoot(JNIEnv* env) {
diff --git a/include/private/README b/include/private/README
new file mode 100644
index 0000000..ee41492
--- /dev/null
+++ b/include/private/README
@@ -0,0 +1,4 @@
+This folder contains private include files.
+
+These include files are part of the private implementation details of
+various framework components.  Use at your peril.
diff --git a/include/ui/EventHub.h b/include/ui/EventHub.h
index 3b18c77..d322a34 100644
--- a/include/ui/EventHub.h
+++ b/include/ui/EventHub.h
@@ -18,6 +18,7 @@
 #ifndef _RUNTIME_EVENT_HUB_H
 #define _RUNTIME_EVENT_HUB_H
 
+#include <android/input.h>
 #include <utils/String8.h>
 #include <utils/threads.h>
 #include <utils/Log.h>
@@ -27,6 +28,31 @@
 
 #include <linux/input.h>
 
+/* These constants are not defined in linux/input.h but they are part of the multitouch
+ * input protocol. */
+
+#define ABS_MT_TOUCH_MAJOR 0x30  /* Major axis of touching ellipse */
+#define ABS_MT_TOUCH_MINOR 0x31  /* Minor axis (omit if circular) */
+#define ABS_MT_WIDTH_MAJOR 0x32  /* Major axis of approaching ellipse */
+#define ABS_MT_WIDTH_MINOR 0x33  /* Minor axis (omit if circular) */
+#define ABS_MT_ORIENTATION 0x34  /* Ellipse orientation */
+#define ABS_MT_POSITION_X 0x35   /* Center X ellipse position */
+#define ABS_MT_POSITION_Y 0x36   /* Center Y ellipse position */
+#define ABS_MT_TOOL_TYPE 0x37    /* Type of touching device (finger, pen, ...) */
+#define ABS_MT_BLOB_ID 0x38      /* Group a set of packets as a blob */
+#define ABS_MT_TRACKING_ID 0x39  /* Unique ID of initiated contact */
+#define ABS_MT_PRESSURE 0x3a     /* Pressure on contact area */
+
+#define MT_TOOL_FINGER 0 /* Identifies a finger */
+#define MT_TOOL_PEN 1    /* Identifies a pen */
+
+#define SYN_MT_REPORT 2
+
+/* Convenience constants. */
+
+#define BTN_FIRST 0x100  // first button scancode
+#define BTN_LAST 0x15f   // last button scancode
+
 struct pollfd;
 
 namespace android {
@@ -34,62 +60,101 @@
 class KeyLayoutMap;
 
 /*
- * Grand Central Station for events.  With a single call to waitEvent()
- * you can wait for:
- *  - input events from the keypad of a real device
- *  - input events and meta-events (e.g. "quit") from the simulator
- *  - synthetic events from the runtime (e.g. "URL fetch completed")
- *  - real or forged "vsync" events
+ * Grand Central Station for events.
  *
- * Do not instantiate this class.  Instead, call startUp().
+ * The event hub aggregates input events received across all known input
+ * devices on the system, including devices that may be emulated by the simulator
+ * environment.  In addition, the event hub generates fake input events to indicate
+ * when devices are added or removed.
+ *
+ * The event hub provies a stream of input events (via the getEvent function).
+ * It also supports querying the current actual state of input devices such as identifying
+ * which keys are currently down.  Finally, the event hub keeps track of the capabilities of
+ * individual input devices, such as their class and the set of key codes that they support.
  */
-class EventHub : public RefBase
-{
+class EventHubInterface : public virtual RefBase {
+protected:
+    EventHubInterface() { }
+    virtual ~EventHubInterface() { }
+
 public:
-    EventHub();
-    
-    status_t errorCheck() const;
-    
-    // bit fields for classes of devices.
-    enum {
-        CLASS_KEYBOARD      = 0x00000001,
-        CLASS_ALPHAKEY      = 0x00000002,
-        CLASS_TOUCHSCREEN   = 0x00000004,
-        CLASS_TRACKBALL     = 0x00000008,
-        CLASS_TOUCHSCREEN_MT= 0x00000010,
-        CLASS_DPAD          = 0x00000020
-    };
-    uint32_t getDeviceClasses(int32_t deviceId) const;
-    
-    String8 getDeviceName(int32_t deviceId) const;
-    
-    int getAbsoluteInfo(int32_t deviceId, int axis, int *outMinValue,
-            int* outMaxValue, int* outFlat, int* outFuzz) const;
-        
-    int getSwitchState(int sw) const;
-    int getSwitchState(int32_t deviceId, int sw) const;
-    
-    int getScancodeState(int key) const;
-    int getScancodeState(int32_t deviceId, int key) const;
-    
-    int getKeycodeState(int key) const;
-    int getKeycodeState(int32_t deviceId, int key) const;
-    
-    status_t scancodeToKeycode(int32_t deviceId, int scancode,
-            int32_t* outKeycode, uint32_t* outFlags) const;
-
-    // exclude a particular device from opening
-    // this can be used to ignore input devices for sensors
-    void addExcludedDevice(const char* deviceName);
-
-    // special type codes when devices are added/removed.
+    // Synthetic raw event type codes produced when devices are added or removed.
     enum {
         DEVICE_ADDED = 0x10000000,
         DEVICE_REMOVED = 0x20000000
     };
+
+    virtual uint32_t getDeviceClasses(int32_t deviceId) const = 0;
+
+    virtual String8 getDeviceName(int32_t deviceId) const = 0;
+
+    virtual int getAbsoluteInfo(int32_t deviceId, int axis, int *outMinValue,
+            int* outMaxValue, int* outFlat, int* outFuzz) const = 0;
+
+    virtual status_t scancodeToKeycode(int32_t deviceId, int scancode,
+            int32_t* outKeycode, uint32_t* outFlags) const = 0;
+
+    // exclude a particular device from opening
+    // this can be used to ignore input devices for sensors
+    virtual void addExcludedDevice(const char* deviceName) = 0;
+
+    /*
+     * Wait for the next event to become available and return it.
+     * After returning, the EventHub holds onto a wake lock until the next call to getEvent.
+     * This ensures that the device will not go to sleep while the event is being processed.
+     * If the device needs to remain awake longer than that, then the caller is responsible
+     * for taking care of it (say, by poking the power manager user activity timer).
+     */
+    virtual bool getEvent(int32_t* outDeviceId, int32_t* outType,
+            int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags,
+            int32_t* outValue, nsecs_t* outWhen) = 0;
+
+    /*
+     * Query current input state.
+     *   deviceId may be -1 to search for the device automatically, filtered by class.
+     *   deviceClasses may be -1 to ignore device class while searching.
+     */
+    virtual int32_t getScanCodeState(int32_t deviceId, int32_t deviceClasses,
+            int32_t scanCode) const = 0;
+    virtual int32_t getKeyCodeState(int32_t deviceId, int32_t deviceClasses,
+            int32_t keyCode) const = 0;
+    virtual int32_t getSwitchState(int32_t deviceId, int32_t deviceClasses,
+            int32_t sw) const = 0;
+
+    /*
+     * Examine key input devices for specific framework keycode support
+     */
+    virtual bool hasKeys(size_t numCodes, const int32_t* keyCodes,
+            uint8_t* outFlags) const = 0;
+};
+
+class EventHub : public EventHubInterface
+{
+public:
+    EventHub();
+
+    status_t errorCheck() const;
+
+    virtual uint32_t getDeviceClasses(int32_t deviceId) const;
     
-    // examine key input devices for specific framework keycode support
-    bool hasKeys(size_t numCodes, int32_t* keyCodes, uint8_t* outFlags);
+    virtual String8 getDeviceName(int32_t deviceId) const;
+    
+    virtual int getAbsoluteInfo(int32_t deviceId, int axis, int *outMinValue,
+            int* outMaxValue, int* outFlat, int* outFuzz) const;
+        
+    virtual status_t scancodeToKeycode(int32_t deviceId, int scancode,
+            int32_t* outKeycode, uint32_t* outFlags) const;
+
+    virtual void addExcludedDevice(const char* deviceName);
+
+    virtual int32_t getScanCodeState(int32_t deviceId, int32_t deviceClasses,
+            int32_t scanCode) const;
+    virtual int32_t getKeyCodeState(int32_t deviceId, int32_t deviceClasses,
+            int32_t keyCode) const;
+    virtual int32_t getSwitchState(int32_t deviceId, int32_t deviceClasses,
+            int32_t sw) const;
+
+    virtual bool hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const;
 
     virtual bool getEvent(int32_t* outDeviceId, int32_t* outType,
             int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags,
@@ -126,6 +191,10 @@
     device_t* getDevice(int32_t deviceId) const;
     bool hasKeycode(device_t* device, int keycode) const;
     
+    int32_t getScanCodeStateLocked(device_t* device, int32_t scanCode) const;
+    int32_t getKeyCodeStateLocked(device_t* device, int32_t keyCode) const;
+    int32_t getSwitchStateLocked(device_t* device, int32_t sw) const;
+
     // Protect all internal state.
     mutable Mutex   mLock;
     
@@ -151,7 +220,7 @@
 
     // device ids that report particular switches.
 #ifdef EV_SW
-    int32_t         mSwitches[SW_MAX+1];
+    int32_t         mSwitches[SW_MAX + 1];
 #endif
 };
 
diff --git a/include/ui/Input.h b/include/ui/Input.h
new file mode 100644
index 0000000..6a5c8a8
--- /dev/null
+++ b/include/ui/Input.h
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_H
+#define _UI_INPUT_H
+
+/**
+ * Native input event structures.
+ */
+
+#include <android/input.h>
+#include <utils/Vector.h>
+#include <utils/Timers.h>
+
+/*
+ * Additional private constants not defined in ndk/ui/input.h.
+ */
+enum {
+    /*
+     * Private control to determine when an app is tracking a key sequence.
+     */
+    KEY_EVENT_FLAG_START_TRACKING = 0x40000000
+};
+
+/*
+ * Maximum number of pointers supported per motion event.
+ */
+#define MAX_POINTERS 10
+
+namespace android {
+
+/*
+ * A raw event as retrieved from the EventHub.
+ */
+struct RawEvent {
+    nsecs_t when;
+    int32_t deviceId;
+    int32_t type;
+    int32_t scanCode;
+    int32_t keyCode;
+    int32_t value;
+    uint32_t flags;
+};
+
+/*
+ * 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. */
+
+    POLICY_FLAG_WAKE = 0x00000001,
+    POLICY_FLAG_WAKE_DROPPED = 0x00000002,
+    POLICY_FLAG_SHIFT = 0x00000004,
+    POLICY_FLAG_CAPS_LOCK = 0x00000008,
+    POLICY_FLAG_ALT = 0x00000010,
+    POLICY_FLAG_ALT_GR = 0x00000020,
+    POLICY_FLAG_MENU = 0x00000040,
+    POLICY_FLAG_LAUNCHER = 0x00000080,
+
+    /* These flags are set by the input dispatch policy as it intercepts each event. */
+
+    // Indicates that the screen was off when the event was received and the event
+    // should wake the device.
+    POLICY_FLAG_WOKE_HERE = 0x10000000,
+
+    // Indicates that the screen was dim when the event was received and the event
+    // should brighten the device.
+    POLICY_FLAG_BRIGHT_HERE = 0x20000000,
+};
+
+/*
+ * Pointer coordinate data.
+ */
+struct PointerCoords {
+    float x;
+    float y;
+    float pressure;
+    float size;
+};
+
+/*
+ * Input events.
+ */
+struct input_event_t { };
+
+class InputEvent : public input_event_t {
+public:
+    virtual ~InputEvent() { }
+
+    virtual int32_t getType() const = 0;
+
+    inline int32_t getDeviceId() const { return mDeviceId; }
+
+    inline int32_t getNature() const { return mNature; }
+
+protected:
+    void initialize(int32_t deviceId, int32_t nature);
+
+private:
+    int32_t mDeviceId;
+    int32_t mNature;
+};
+
+class KeyEvent : public InputEvent {
+public:
+    virtual ~KeyEvent() { }
+
+    virtual int32_t getType() const { return INPUT_EVENT_TYPE_KEY; }
+
+    inline int32_t getAction() const { return mAction; }
+
+    inline int32_t getFlags() const { return mFlags; }
+
+    inline int32_t getKeyCode() const { return mKeyCode; }
+
+    inline int32_t getScanCode() const { return mScanCode; }
+
+    inline int32_t getMetaState() const { return mMetaState; }
+
+    inline int32_t getRepeatCount() const { return mRepeatCount; }
+
+    inline nsecs_t getDownTime() const { return mDownTime; }
+
+    inline nsecs_t getEventTime() const { return mEventTime; }
+
+    void initialize(
+            int32_t deviceId,
+            int32_t nature,
+            int32_t action,
+            int32_t flags,
+            int32_t keyCode,
+            int32_t scanCode,
+            int32_t metaState,
+            int32_t repeatCount,
+            nsecs_t downTime,
+            nsecs_t eventTime);
+
+private:
+    int32_t mAction;
+    int32_t mFlags;
+    int32_t mKeyCode;
+    int32_t mScanCode;
+    int32_t mMetaState;
+    int32_t mRepeatCount;
+    nsecs_t mDownTime;
+    nsecs_t mEventTime;
+};
+
+class MotionEvent : public InputEvent {
+public:
+    virtual ~MotionEvent() { }
+
+    virtual int32_t getType() const { return INPUT_EVENT_TYPE_MOTION; }
+
+    inline int32_t getAction() const { return mAction; }
+
+    inline int32_t getEdgeFlags() const { return mEdgeFlags; }
+
+    inline int32_t getMetaState() const { return mMetaState; }
+
+    inline float getXPrecision() const { return mXPrecision; }
+
+    inline float getYPrecision() const { return mYPrecision; }
+
+    inline nsecs_t getDownTime() const { return mDownTime; }
+
+    inline size_t getPointerCount() const { return mPointerIds.size(); }
+
+    inline int32_t getPointerId(size_t pointerIndex) const { return mPointerIds[pointerIndex]; }
+
+    inline nsecs_t getEventTime() const { return mSampleEventTimes[getHistorySize()]; }
+
+    inline float getRawX() const { return mRawX; }
+
+    inline float getRawY() const { return mRawY; }
+
+    inline float getX(size_t pointerIndex) const {
+        return getCurrentPointerCoords(pointerIndex).x;
+    }
+
+    inline float getY(size_t pointerIndex) const {
+        return getCurrentPointerCoords(pointerIndex).y;
+    }
+
+    inline float getPressure(size_t pointerIndex) const {
+        return getCurrentPointerCoords(pointerIndex).pressure;
+    }
+
+    inline float getSize(size_t pointerIndex) const {
+        return getCurrentPointerCoords(pointerIndex).size;
+    }
+
+    inline size_t getHistorySize() const { return mSampleEventTimes.size() - 1; }
+
+    inline nsecs_t getHistoricalEventTime(size_t historicalIndex) const {
+        return mSampleEventTimes[historicalIndex];
+    }
+
+    inline float getHistoricalX(size_t pointerIndex, size_t historicalIndex) const {
+        return getHistoricalPointerCoords(pointerIndex, historicalIndex).x;
+    }
+
+    inline float getHistoricalY(size_t pointerIndex, size_t historicalIndex) const {
+        return getHistoricalPointerCoords(pointerIndex, historicalIndex).y;
+    }
+
+    inline float getHistoricalPressure(size_t pointerIndex, size_t historicalIndex) const {
+        return getHistoricalPointerCoords(pointerIndex, historicalIndex).pressure;
+    }
+
+    inline float getHistoricalSize(size_t pointerIndex, size_t historicalIndex) const {
+        return getHistoricalPointerCoords(pointerIndex, historicalIndex).size;
+    }
+
+    void initialize(
+            int32_t deviceId,
+            int32_t nature,
+            int32_t action,
+            int32_t edgeFlags,
+            int32_t metaState,
+            float rawX,
+            float rawY,
+            float xPrecision,
+            float yPrecision,
+            nsecs_t downTime,
+            nsecs_t eventTime,
+            size_t pointerCount,
+            const int32_t* pointerIds,
+            const PointerCoords* pointerCoords);
+
+    void addSample(
+            nsecs_t eventTime,
+            const PointerCoords* pointerCoords);
+
+    void offsetLocation(float xOffset, float yOffset);
+
+private:
+    int32_t mAction;
+    int32_t mEdgeFlags;
+    int32_t mMetaState;
+    float mRawX;
+    float mRawY;
+    float mXPrecision;
+    float mYPrecision;
+    nsecs_t mDownTime;
+    Vector<int32_t> mPointerIds;
+    Vector<nsecs_t> mSampleEventTimes;
+    Vector<PointerCoords> mSamplePointerCoords;
+
+    inline const PointerCoords& getCurrentPointerCoords(size_t pointerIndex) const {
+        return mSamplePointerCoords[getHistorySize() * getPointerCount() + pointerIndex];
+    }
+
+    inline const PointerCoords& getHistoricalPointerCoords(
+            size_t pointerIndex, size_t historicalIndex) const {
+        return mSamplePointerCoords[historicalIndex * getPointerCount() + pointerIndex];
+    }
+};
+
+/*
+ * Input event factory.
+ */
+class InputEventFactoryInterface {
+protected:
+    virtual ~InputEventFactoryInterface() { }
+
+public:
+    InputEventFactoryInterface() { }
+
+    virtual KeyEvent* createKeyEvent() = 0;
+    virtual MotionEvent* createMotionEvent() = 0;
+};
+
+/*
+ * A simple input event factory implementation that uses a single preallocated instance
+ * of each type of input event that are reused for each request.
+ */
+class PreallocatedInputEventFactory : public InputEventFactoryInterface {
+public:
+    PreallocatedInputEventFactory() { }
+    virtual ~PreallocatedInputEventFactory() { }
+
+    virtual KeyEvent* createKeyEvent() { return & mKeyEvent; }
+    virtual MotionEvent* createMotionEvent() { return & mMotionEvent; }
+
+private:
+    KeyEvent mKeyEvent;
+    MotionEvent mMotionEvent;
+};
+
+
+} // namespace android
+
+#endif // _UI_INPUT_H
diff --git a/include/ui/InputDispatchPolicy.h b/include/ui/InputDispatchPolicy.h
new file mode 100644
index 0000000..3546813
--- /dev/null
+++ b/include/ui/InputDispatchPolicy.h
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_DISPATCH_POLICY_H
+#define _UI_INPUT_DISPATCH_POLICY_H
+
+/**
+ * Native input dispatch policy.
+ */
+
+#include <ui/Input.h>
+#include <utils/Errors.h>
+#include <utils/Vector.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+
+namespace android {
+
+class InputChannel;
+
+/*
+ * 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
+ * window area.
+ */
+struct InputTarget {
+    enum {
+        /* This flag indicates that subsequent event delivery should be held until the
+         * current event is delivered to this target or a timeout occurs. */
+        FLAG_SYNC = 0x01,
+
+        /* This flag indicates that a MotionEvent with ACTION_DOWN falls outside of the area of
+         * this target and so should instead be delivered as an ACTION_OUTSIDE to this target. */
+        FLAG_OUTSIDE = 0x02,
+
+        /* This flag indicates that a KeyEvent or MotionEvent is being canceled.
+         * In the case of a key event, it should be delivered with KeyEvent.FLAG_CANCELED set.
+         * In the case of a motion event, it should be delivered as MotionEvent.ACTION_CANCEL. */
+        FLAG_CANCEL = 0x04
+    };
+
+    // The input channel to be targeted.
+    sp<InputChannel> inputChannel;
+
+    // Flags for the input target.
+    int32_t flags;
+
+    // The timeout for event delivery to this target in nanoseconds.  Or -1 if none.
+    nsecs_t timeout;
+
+    // The x and y offset to add to a MotionEvent as it is delivered.
+    // (ignored for KeyEvents)
+    float xOffset, yOffset;
+};
+
+/*
+ * Input dispatch policy interface.
+ *
+ * The input dispatch policy is used by the input dispatcher to interact with the
+ * Window Manager and other system components.  This separation of concerns keeps
+ * the input dispatcher relatively free of special case logic such as is required
+ * to determine the target of iput events, when to wake the device, how to interact
+ * with key guard, and when to transition to the home screen.
+ *
+ * The actual implementation is partially supported by callbacks into the DVM
+ * via JNI.  This class is also mocked in the input dispatcher unit tests since
+ * it is an ideal test seam.
+ */
+class InputDispatchPolicyInterface : public virtual RefBase {
+protected:
+    InputDispatchPolicyInterface() { }
+    virtual ~InputDispatchPolicyInterface() { }
+
+public:
+    enum {
+        ROTATION_0 = 0,
+        ROTATION_90 = 1,
+        ROTATION_180 = 2,
+        ROTATION_270 = 3
+    };
+
+    enum {
+        // The input dispatcher should do nothing and discard the input unless other
+        // flags are set.
+        ACTION_NONE = 0,
+
+        // The input dispatcher should dispatch the input to the application.
+        ACTION_DISPATCH = 0x00000001,
+
+        // The input dispatcher should perform special filtering in preparation for
+        // a pending app switch.
+        ACTION_APP_SWITCH_COMING = 0x00000002,
+
+        // The input dispatcher should add POLICY_FLAG_WOKE_HERE to the policy flags it
+        // passes through the dispatch pipeline.
+        ACTION_WOKE_HERE = 0x00000004,
+
+        // The input dispatcher should add POLICY_FLAG_BRIGHT_HERE to the policy flags it
+        // passes through the dispatch pipeline.
+        ACTION_BRIGHT_HERE = 0x00000008
+    };
+
+    enum {
+        TOUCHSCREEN_UNDEFINED = 0,
+        TOUCHSCREEN_NOTOUCH = 1,
+        TOUCHSCREEN_STYLUS = 2,
+        TOUCHSCREEN_FINGER = 3
+    };
+
+    enum {
+        KEYBOARD_UNDEFINED = 0,
+        KEYBOARD_NOKEYS = 1,
+        KEYBOARD_QWERTY = 2,
+        KEYBOARD_12KEY = 3
+    };
+
+    enum {
+        NAVIGATION_UNDEFINED = 0,
+        NAVIGATION_NONAV = 1,
+        NAVIGATION_DPAD = 2,
+        NAVIGATION_TRACKBALL = 3,
+        NAVIGATION_WHEEL = 4
+    };
+
+    struct VirtualKeyDefinition {
+        int32_t scanCode;
+
+        // configured position data, specified in display coords
+        int32_t centerX;
+        int32_t centerY;
+        int32_t width;
+        int32_t height;
+    };
+
+    /* Gets information about the display with the specified id.
+     * Returns true if the display info is available, false otherwise.
+     */
+    virtual bool getDisplayInfo(int32_t displayId,
+            int32_t* width, int32_t* height, int32_t* orientation) = 0;
+
+    virtual void notifyConfigurationChanged(nsecs_t when,
+            int32_t touchScreenConfig, int32_t keyboardConfig, int32_t navigationConfig) = 0;
+
+    virtual void notifyLidSwitchChanged(nsecs_t when, bool lidOpen) = 0;
+
+    virtual void virtualKeyFeedback(nsecs_t when, int32_t deviceId,
+            int32_t action, int32_t flags, int32_t keyCode,
+            int32_t scanCode, int32_t metaState, nsecs_t downTime) = 0;
+
+    virtual int32_t interceptKey(nsecs_t when, int32_t deviceId,
+            bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags) = 0;
+
+    virtual int32_t interceptTrackball(nsecs_t when, bool buttonChanged, bool buttonDown,
+            bool rolled) = 0;
+
+    virtual int32_t interceptTouch(nsecs_t when) = 0;
+
+    virtual bool allowKeyRepeat() = 0;
+    virtual nsecs_t getKeyRepeatTimeout() = 0;
+
+    virtual void getKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags,
+            Vector<InputTarget>& outTargets) = 0;
+    virtual void getMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
+            Vector<InputTarget>& outTargets) = 0;
+
+    /* Determine whether to turn on some hacks we have to improve the touch interaction with a
+     * certain device whose screen currently is not all that good.
+     */
+    virtual bool filterTouchEvents() = 0;
+
+    /* Determine whether to turn on some hacks to improve touch interaction with another device
+     * where touch coordinate data can get corrupted.
+     */
+    virtual bool filterJumpyTouchEvents() = 0;
+
+    virtual void getVirtualKeyDefinitions(const String8& deviceName,
+            Vector<VirtualKeyDefinition>& outVirtualKeyDefinitions) = 0;
+    virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames) = 0;
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_DISPATCH_POLICY_H
diff --git a/include/ui/InputDispatcher.h b/include/ui/InputDispatcher.h
new file mode 100644
index 0000000..bde07f2
--- /dev/null
+++ b/include/ui/InputDispatcher.h
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_DISPATCHER_H
+#define _UI_INPUT_DISPATCHER_H
+
+#include <ui/Input.h>
+#include <ui/InputDispatchPolicy.h>
+#include <ui/InputTransport.h>
+#include <utils/KeyedVector.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/PollLoop.h>
+#include <utils/Pool.h>
+
+#include <stddef.h>
+#include <unistd.h>
+
+
+namespace android {
+
+/* Notifies the system about input events generated by the input reader.
+ * The dispatcher is expected to be mostly asynchronous. */
+class InputDispatcherInterface : public virtual RefBase {
+protected:
+    InputDispatcherInterface() { }
+    virtual ~InputDispatcherInterface() { }
+
+public:
+    /* Runs a single iteration of the dispatch loop.
+     * Nominally processes one queued event, a timeout, or a response from an input consumer.
+     *
+     * This method should only be called on the input dispatcher thread.
+     */
+    virtual void dispatchOnce() = 0;
+
+    /* Notifies the dispatcher about new events.
+     * The dispatcher will process most of these events asynchronously although some
+     * policy processing may occur synchronously.
+     *
+     * These methods should only be called on the input reader thread.
+     */
+    virtual void notifyConfigurationChanged(nsecs_t eventTime,
+            int32_t touchScreenConfig, int32_t keyboardConfig, int32_t navigationConfig) = 0;
+    virtual void notifyLidSwitchChanged(nsecs_t eventTime, bool lidOpen) = 0;
+    virtual void notifyAppSwitchComing(nsecs_t eventTime) = 0;
+    virtual void notifyKey(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, nsecs_t downTime) = 0;
+    virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t nature,
+            uint32_t policyFlags, int32_t action, int32_t metaState, int32_t edgeFlags,
+            uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
+            float xPrecision, float yPrecision, nsecs_t downTime) = 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).
+     */
+    virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel) = 0;
+    virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) = 0;
+};
+
+/* Dispatches events. */
+class InputDispatcher : public InputDispatcherInterface {
+protected:
+    virtual ~InputDispatcher();
+
+public:
+    explicit InputDispatcher(const sp<InputDispatchPolicyInterface>& policy);
+
+    virtual void dispatchOnce();
+
+    virtual void notifyConfigurationChanged(nsecs_t eventTime,
+            int32_t touchScreenConfig, int32_t keyboardConfig, int32_t navigationConfig);
+    virtual void notifyLidSwitchChanged(nsecs_t eventTime, bool lidOpen);
+    virtual void notifyAppSwitchComing(nsecs_t eventTime);
+    virtual void notifyKey(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, nsecs_t downTime);
+    virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t nature,
+            uint32_t policyFlags, int32_t action, int32_t metaState, int32_t edgeFlags,
+            uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
+            float xPrecision, float yPrecision, nsecs_t downTime);
+
+    virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel);
+    virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel);
+
+private:
+    template <typename T>
+    struct Link {
+        T* next;
+        T* prev;
+    };
+
+    struct EventEntry : Link<EventEntry> {
+        enum {
+            TYPE_SENTINEL,
+            TYPE_CONFIGURATION_CHANGED,
+            TYPE_KEY,
+            TYPE_MOTION
+        };
+
+        int32_t refCount;
+        int32_t type;
+        nsecs_t eventTime;
+    };
+
+    struct ConfigurationChangedEntry : EventEntry {
+        int32_t touchScreenConfig;
+        int32_t keyboardConfig;
+        int32_t navigationConfig;
+    };
+
+    struct KeyEntry : EventEntry {
+        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;
+    };
+
+    struct MotionSample {
+        MotionSample* next;
+
+        nsecs_t eventTime;
+        PointerCoords pointerCoords[MAX_POINTERS];
+    };
+
+    struct MotionEntry : EventEntry {
+        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;
+        int32_t pointerIds[MAX_POINTERS];
+
+        // Linked list of motion samples associated with this motion event.
+        MotionSample firstSample;
+        MotionSample* lastSample;
+    };
+
+    struct DispatchEntry : Link<DispatchEntry> {
+        EventEntry* eventEntry; // the event to dispatch
+        int32_t targetFlags;
+        float xOffset;
+        float yOffset;
+        nsecs_t timeout;
+
+        // True if dispatch has started.
+        bool inProgress;
+
+        // For motion events:
+        //   Pointer to the first motion sample to dispatch in this cycle.
+        //   Usually NULL to indicate that the list of motion samples begins at
+        //   MotionEntry::firstSample.  Otherwise, some samples were dispatched in a previous
+        //   cycle and this pointer indicates the location of the first remainining sample
+        //   to dispatch during the current cycle.
+        MotionSample* headMotionSample;
+        //   Pointer to a motion sample to dispatch in the next cycle if the dispatcher was
+        //   unable to send all motion samples during this cycle.  On the next cycle,
+        //   headMotionSample will be initialized to tailMotionSample and tailMotionSample
+        //   will be set to NULL.
+        MotionSample* tailMotionSample;
+    };
+
+    template <typename T>
+    struct Queue {
+        T head;
+        T tail;
+
+        inline Queue() {
+            head.prev = NULL;
+            head.next = & tail;
+            tail.prev = & head;
+            tail.next = NULL;
+        }
+
+        inline bool isEmpty() {
+            return head.next == & tail;
+        }
+
+        inline void enqueueAtTail(T* entry) {
+            T* last = tail.prev;
+            last->next = entry;
+            entry->prev = last;
+            entry->next = & tail;
+            tail.prev = entry;
+        }
+
+        inline void enqueueAtHead(T* entry) {
+            T* first = head.next;
+            head.next = entry;
+            entry->prev = & head;
+            entry->next = first;
+            first->prev = entry;
+        }
+
+        inline void dequeue(T* entry) {
+            entry->prev->next = entry->next;
+            entry->next->prev = entry->prev;
+        }
+
+        inline T* dequeueAtHead() {
+            T* first = head.next;
+            dequeue(first);
+            return first;
+        }
+    };
+
+    /* Allocates queue entries and performs reference counting as needed. */
+    class Allocator {
+    public:
+        Allocator();
+
+        ConfigurationChangedEntry* obtainConfigurationChangedEntry();
+        KeyEntry* obtainKeyEntry();
+        MotionEntry* obtainMotionEntry();
+        DispatchEntry* obtainDispatchEntry(EventEntry* eventEntry);
+
+        void releaseEventEntry(EventEntry* entry);
+        void releaseConfigurationChangedEntry(ConfigurationChangedEntry* entry);
+        void releaseKeyEntry(KeyEntry* entry);
+        void releaseMotionEntry(MotionEntry* entry);
+        void releaseDispatchEntry(DispatchEntry* entry);
+
+        void appendMotionSample(MotionEntry* motionEntry,
+                nsecs_t eventTime, int32_t pointerCount, const PointerCoords* pointerCoords);
+        void freeMotionSample(MotionSample* sample);
+        void freeMotionSampleList(MotionSample* head);
+
+    private:
+        Pool<ConfigurationChangedEntry> mConfigurationChangeEntryPool;
+        Pool<KeyEntry> mKeyEntryPool;
+        Pool<MotionEntry> mMotionEntryPool;
+        Pool<MotionSample> mMotionSamplePool;
+        Pool<DispatchEntry> mDispatchEntryPool;
+    };
+
+    /* Manages the dispatch state associated with a single input channel. */
+    class Connection : public RefBase {
+    protected:
+        virtual ~Connection();
+
+    public:
+        enum Status {
+            // Everything is peachy.
+            STATUS_NORMAL,
+            // An unrecoverable communication error has occurred.
+            STATUS_BROKEN,
+            // The client is not responding.
+            STATUS_NOT_RESPONDING,
+            // The input channel has been unregistered.
+            STATUS_ZOMBIE
+        };
+
+        Status status;
+        sp<InputChannel> inputChannel;
+        InputPublisher inputPublisher;
+        Queue<DispatchEntry> outboundQueue;
+        nsecs_t nextTimeoutTime; // next timeout time (LONG_LONG_MAX if none)
+
+        nsecs_t lastEventTime; // the time when the event was originally captured
+        nsecs_t lastDispatchTime; // the time when the last event was dispatched
+        nsecs_t lastANRTime; // the time when the last ANR was recorded
+
+        explicit Connection(const sp<InputChannel>& inputChannel);
+
+        inline const char* getInputChannelName() { return inputChannel->getName().string(); }
+
+        // Finds a DispatchEntry in the outbound queue associated with the specified event.
+        // Returns NULL if not found.
+        DispatchEntry* findQueuedDispatchEntryForEvent(const EventEntry* eventEntry) const;
+
+        // Determine whether this connection has a pending synchronous dispatch target.
+        // Since there can only ever be at most one such target at a time, if there is one,
+        // it must be at the tail because nothing else can be enqueued after it.
+        inline bool hasPendingSyncTarget() {
+            return ! outboundQueue.isEmpty()
+                    && (outboundQueue.tail.prev->targetFlags & InputTarget::FLAG_SYNC);
+        }
+
+        // Gets the time since the current event was originally obtained from the input driver.
+        inline double getEventLatencyMillis(nsecs_t currentTime) {
+            return (currentTime - lastEventTime) / 1000000.0;
+        }
+
+        // Gets the time since the current event entered the outbound dispatch queue.
+        inline double getDispatchLatencyMillis(nsecs_t currentTime) {
+            return (currentTime - lastDispatchTime) / 1000000.0;
+        }
+
+        // Gets the time since the current event ANR was declared, if applicable.
+        inline double getANRLatencyMillis(nsecs_t currentTime) {
+            return (currentTime - lastANRTime) / 1000000.0;
+        }
+
+        status_t initialize();
+    };
+
+    sp<InputDispatchPolicyInterface> mPolicy;
+
+    Mutex mLock;
+
+    Queue<EventEntry> mInboundQueue;
+    Allocator mAllocator;
+
+    sp<PollLoop> mPollLoop;
+
+    // All registered connections mapped by receive pipe file descriptor.
+    KeyedVector<int, sp<Connection> > mConnectionsByReceiveFd;
+
+    // Active connections are connections that have a non-empty outbound queue.
+    Vector<Connection*> mActiveConnections;
+
+    // Pool of key and motion event objects used only to ask the input dispatch policy
+    // for the targets of an event that is to be dispatched.
+    KeyEvent mReusableKeyEvent;
+    MotionEvent mReusableMotionEvent;
+
+    // The input targets that were most recently identified for dispatch.
+    // If there is a synchronous event dispatch in progress, the current input targets will
+    // remain unchanged until the dispatch has completed or been aborted.
+    Vector<InputTarget> mCurrentInputTargets;
+
+    // Key repeat tracking.
+    // XXX Move this up to the input reader instead.
+    struct KeyRepeatState {
+        KeyEntry* lastKeyEntry; // or null if no repeat
+        nsecs_t nextRepeatTime;
+    } mKeyRepeatState;
+
+    void resetKeyRepeatLocked();
+
+    // Process events that have just been dequeued from the head of the input queue.
+    void processConfigurationChangedLocked(nsecs_t currentTime, ConfigurationChangedEntry* entry);
+    void processKeyLocked(nsecs_t currentTime, KeyEntry* entry);
+    void processKeyRepeatLocked(nsecs_t currentTime);
+    void processMotionLocked(nsecs_t currentTime, MotionEntry* entry);
+
+    // Identify input targets for an event and dispatch to them.
+    void identifyInputTargetsAndDispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry);
+    void identifyInputTargetsAndDispatchMotionLocked(nsecs_t currentTime, MotionEntry* entry);
+    void dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime, EventEntry* entry,
+            bool resumeWithAppendedMotionSample);
+
+    // Manage the dispatch cycle for a single connection.
+    void prepareDispatchCycleLocked(nsecs_t currentTime, 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,
+            bool broken);
+    static bool handleReceiveCallback(int receiveFd, int events, void* data);
+
+    // Add or remove a connection to the mActiveConnections vector.
+    void activateConnectionLocked(Connection* connection);
+    void deactivateConnectionLocked(Connection* connection);
+
+    // Interesting events that we might like to log or tell the framework about.
+    void onDispatchCycleStartedLocked(nsecs_t currentTime, Connection* connection);
+    void onDispatchCycleFinishedLocked(nsecs_t currentTime, Connection* connection,
+            bool recoveredFromANR);
+    void onDispatchCycleANRLocked(nsecs_t currentTime, Connection* connection);
+    void onDispatchCycleBrokenLocked(nsecs_t currentTime, Connection* connection);
+};
+
+/* Enqueues and dispatches input events, endlessly. */
+class InputDispatcherThread : public Thread {
+public:
+    explicit InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher);
+    ~InputDispatcherThread();
+
+private:
+    virtual bool threadLoop();
+
+    sp<InputDispatcherInterface> mDispatcher;
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_DISPATCHER_PRIV_H
diff --git a/include/ui/InputManager.h b/include/ui/InputManager.h
new file mode 100644
index 0000000..eb27513
--- /dev/null
+++ b/include/ui/InputManager.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_MANAGER_H
+#define _UI_INPUT_MANAGER_H
+
+/**
+ * Native input manager.
+ */
+
+#include <ui/EventHub.h>
+#include <ui/Input.h>
+#include <ui/InputDispatchPolicy.h>
+#include <utils/Errors.h>
+#include <utils/Vector.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+
+namespace android {
+
+class InputReader;
+class InputDispatcher;
+class InputReaderThread;
+class InputDispatcherThread;
+
+/*
+ * The input manager is the core of the system event processing.
+ *
+ * The input manager uses two threads.
+ *
+ * 1. The InputReaderThread (called "InputReader") reads and preprocesses raw input events,
+ *    applies policy, and posts messages to a queue managed by the DispatcherThread.
+ * 2. The InputDispatcherThread (called "InputDispatcher") thread waits for new events on the
+ *    queue and asynchronously dispatches them to applications.
+ *
+ * By design, the InputReaderThread class and InputDispatcherThread class do not share any
+ * internal state.  Moreover, all communication is done one way from the InputReaderThread
+ * into the InputDispatcherThread and never the reverse.  Both classes may interact with the
+ * InputDispatchPolicy, however.
+ *
+ * The InputManager class never makes any calls into Java itself.  Instead, the
+ * InputDispatchPolicy is responsible for performing all external interactions with the
+ * system, including calling DVM services.
+ */
+class InputManagerInterface : public virtual RefBase {
+protected:
+    InputManagerInterface() { }
+    virtual ~InputManagerInterface() { }
+
+public:
+    /* Starts the input manager threads. */
+    virtual status_t start() = 0;
+
+    /* Stops the input manager threads and waits for them to exit. */
+    virtual status_t stop() = 0;
+
+    /* Registers an input channel prior to using it as the target of an event. */
+    virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel) = 0;
+
+    /* Unregisters an input channel. */
+    virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) = 0;
+
+    /*
+     * Query current input state.
+     *   deviceId may be -1 to search for the device automatically, filtered by class.
+     *   deviceClasses may be -1 to ignore device class while searching.
+     */
+    virtual int32_t getScanCodeState(int32_t deviceId, int32_t deviceClasses,
+            int32_t scanCode) const = 0;
+    virtual int32_t getKeyCodeState(int32_t deviceId, int32_t deviceClasses,
+            int32_t keyCode) const = 0;
+    virtual int32_t getSwitchState(int32_t deviceId, int32_t deviceClasses,
+            int32_t sw) const = 0;
+
+    /* Determine whether physical keys exist for the given framework-domain key codes. */
+    virtual bool hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const = 0;
+};
+
+class InputManager : public InputManagerInterface {
+protected:
+    virtual ~InputManager();
+
+public:
+    /*
+     * Creates an input manager that reads events from the given
+     * event hub and applies the given input dispatch policy.
+     */
+    InputManager(const sp<EventHubInterface>& eventHub,
+            const sp<InputDispatchPolicyInterface>& policy);
+
+    virtual status_t start();
+    virtual status_t stop();
+
+    virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel);
+    virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel);
+
+    virtual int32_t getScanCodeState(int32_t deviceId, int32_t deviceClasses,
+            int32_t scanCode) const;
+    virtual int32_t getKeyCodeState(int32_t deviceId, int32_t deviceClasses,
+            int32_t keyCode) const;
+    virtual int32_t getSwitchState(int32_t deviceId, int32_t deviceClasses,
+            int32_t sw) const;
+    virtual bool hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const;
+
+private:
+    sp<EventHubInterface> mEventHub;
+    sp<InputDispatchPolicyInterface> mPolicy;
+
+    sp<InputDispatcher> mDispatcher;
+    sp<InputDispatcherThread> mDispatcherThread;
+
+    sp<InputReader> mReader;
+    sp<InputReaderThread> mReaderThread;
+
+    void configureExcludedDevices();
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_MANAGER_H
diff --git a/include/ui/InputReader.h b/include/ui/InputReader.h
new file mode 100644
index 0000000..7e7a64c
--- /dev/null
+++ b/include/ui/InputReader.h
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_READER_H
+#define _UI_INPUT_READER_H
+
+#include <ui/EventHub.h>
+#include <ui/Input.h>
+#include <ui/InputDispatchPolicy.h>
+#include <ui/InputDispatcher.h>
+#include <utils/KeyedVector.h>
+#include <utils/threads.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/BitSet.h>
+
+#include <stddef.h>
+#include <unistd.h>
+
+/* Maximum pointer id value supported.
+ * (This is limited by our use of BitSet32 to track pointer assignments.) */
+#define MAX_POINTER_ID 32
+
+/** Amount that trackball needs to move in order to generate a key event. */
+#define TRACKBALL_MOVEMENT_THRESHOLD 6
+
+/* Slop distance for jumpy pointer detection.
+ * The vertical range of the screen divided by this is our epsilon value. */
+#define JUMPY_EPSILON_DIVISOR 212
+
+/* Number of jumpy points to drop for touchscreens that need it. */
+#define JUMPY_TRANSITION_DROPS 3
+#define JUMPY_DROP_LIMIT 3
+
+/* Maximum squared distance for averaging.
+ * If moving farther than this, turn of averaging to avoid lag in response. */
+#define AVERAGING_DISTANCE_LIMIT (75 * 75)
+
+/* Maximum number of historical samples to average. */
+#define AVERAGING_HISTORY_SIZE 5
+
+
+namespace android {
+
+extern int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState);
+extern int32_t rotateKeyCode(int32_t keyCode, int32_t orientation);
+
+/*
+ * An input device structure tracks the state of a single input device.
+ *
+ * This structure is only used by ReaderThread and is not intended to be shared with
+ * DispatcherThread (because that would require locking).  This works out fine because
+ * DispatcherThread is only interested in cooked event data anyways and does not need
+ * any of the low-level data from InputDevice.
+ */
+struct InputDevice {
+    struct AbsoluteAxisInfo {
+        int32_t minValue;  // minimum value
+        int32_t maxValue;  // maximum value
+        int32_t range;     // range of values, equal to maxValue - minValue
+        int32_t flat;      // center flat position, eg. flat == 8 means center is between -8 and 8
+        int32_t fuzz;      // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise
+    };
+
+    struct VirtualKey {
+        int32_t keyCode;
+        int32_t scanCode;
+        uint32_t flags;
+
+        // computed hit box, specified in touch screen coords based on known display size
+        int32_t hitLeft;
+        int32_t hitTop;
+        int32_t hitRight;
+        int32_t hitBottom;
+
+        inline bool isHit(int32_t x, int32_t y) const {
+            return x >= hitLeft && x <= hitRight && y >= hitTop && y <= hitBottom;
+        }
+    };
+
+    struct KeyboardState {
+        struct Current {
+            int32_t metaState;
+            nsecs_t downTime; // time of most recent key down
+        } current;
+
+        void reset();
+    };
+
+    struct TrackballState {
+        struct Accumulator {
+            enum {
+                FIELD_BTN_MOUSE = 1,
+                FIELD_REL_X = 2,
+                FIELD_REL_Y = 4
+            };
+
+            uint32_t fields;
+
+            bool btnMouse;
+            int32_t relX;
+            int32_t relY;
+
+            inline void clear() {
+                fields = 0;
+            }
+
+            inline bool isDirty() {
+                return fields != 0;
+            }
+        } accumulator;
+
+        struct Current {
+            bool down;
+            nsecs_t downTime;
+        } current;
+
+        struct Precalculated {
+            float xScale;
+            float yScale;
+            float xPrecision;
+            float yPrecision;
+        } precalculated;
+
+        void reset();
+    };
+
+    struct SingleTouchScreenState {
+        struct Accumulator {
+            enum {
+                FIELD_BTN_TOUCH = 1,
+                FIELD_ABS_X = 2,
+                FIELD_ABS_Y = 4,
+                FIELD_ABS_PRESSURE = 8,
+                FIELD_ABS_TOOL_WIDTH = 16
+            };
+
+            uint32_t fields;
+
+            bool btnTouch;
+            int32_t absX;
+            int32_t absY;
+            int32_t absPressure;
+            int32_t absToolWidth;
+
+            inline void clear() {
+                fields = 0;
+            }
+
+            inline bool isDirty() {
+                return fields != 0;
+            }
+        } accumulator;
+
+        struct Current {
+            bool down;
+            int32_t x;
+            int32_t y;
+            int32_t pressure;
+            int32_t size;
+        } current;
+
+        void reset();
+    };
+
+    struct MultiTouchScreenState {
+        struct Accumulator {
+            enum {
+                FIELD_ABS_MT_POSITION_X = 1,
+                FIELD_ABS_MT_POSITION_Y = 2,
+                FIELD_ABS_MT_TOUCH_MAJOR = 4,
+                FIELD_ABS_MT_WIDTH_MAJOR = 8,
+                FIELD_ABS_MT_TRACKING_ID = 16
+            };
+
+            uint32_t pointerCount;
+            struct Pointer {
+                uint32_t fields;
+
+                int32_t absMTPositionX;
+                int32_t absMTPositionY;
+                int32_t absMTTouchMajor;
+                int32_t absMTWidthMajor;
+                int32_t absMTTrackingId;
+
+                inline void clear() {
+                    fields = 0;
+                }
+            } pointers[MAX_POINTERS + 1]; // + 1 to remove the need for extra range checks
+
+            inline void clear() {
+                pointerCount = 0;
+                pointers[0].clear();
+            }
+
+            inline bool isDirty() {
+                return pointerCount != 0;
+            }
+        } accumulator;
+
+        void reset();
+    };
+
+    struct PointerData {
+        uint32_t id;
+        int32_t x;
+        int32_t y;
+        int32_t pressure;
+        int32_t size;
+    };
+
+    struct TouchData {
+        uint32_t pointerCount;
+        PointerData pointers[MAX_POINTERS];
+        BitSet32 idBits;
+        uint32_t idToIndex[MAX_POINTER_ID];
+
+        void copyFrom(const TouchData& other);
+
+        inline void clear() {
+            pointerCount = 0;
+            idBits.clear();
+        }
+    };
+
+    // common state used for both single-touch and multi-touch screens after the initial
+    // touch decoding has been performed
+    struct TouchScreenState {
+        Vector<VirtualKey> virtualKeys;
+
+        struct Parameters {
+            bool useBadTouchFilter;
+            bool useJumpyTouchFilter;
+            bool useAveragingTouchFilter;
+
+            AbsoluteAxisInfo xAxis;
+            AbsoluteAxisInfo yAxis;
+            AbsoluteAxisInfo pressureAxis;
+            AbsoluteAxisInfo sizeAxis;
+        } parameters;
+
+        // The touch data of the current sample being processed.
+        TouchData currentTouch;
+
+        // The touch data of the previous sample that was processed.  This is updated
+        // incrementally while the current sample is being processed.
+        TouchData lastTouch;
+
+        // The time the primary pointer last went down.
+        nsecs_t downTime;
+
+        struct CurrentVirtualKeyState {
+            bool down;
+            nsecs_t downTime;
+            int32_t keyCode;
+            int32_t scanCode;
+        } currentVirtualKey;
+
+        struct AveragingTouchFilterState {
+            // Individual history tracks are stored by pointer id
+            uint32_t historyStart[MAX_POINTERS];
+            uint32_t historyEnd[MAX_POINTERS];
+            struct {
+                struct {
+                    int32_t x;
+                    int32_t y;
+                    int32_t pressure;
+                } pointers[MAX_POINTERS];
+            } historyData[AVERAGING_HISTORY_SIZE];
+        } averagingTouchFilter;
+
+        struct JumpTouchFilterState {
+            int32_t jumpyPointsDropped;
+        } jumpyTouchFilter;
+
+        struct Precalculated {
+            float xScale;
+            float yScale;
+            float pressureScale;
+            float sizeScale;
+        } precalculated;
+
+        void reset();
+
+        bool applyBadTouchFilter();
+        bool applyJumpyTouchFilter();
+        void applyAveragingTouchFilter();
+        void calculatePointerIds();
+
+        bool isPointInsideDisplay(int32_t x, int32_t y) const;
+    };
+
+    InputDevice(int32_t id, uint32_t classes, String8 name);
+
+    int32_t id;
+    uint32_t classes;
+    String8 name;
+    bool ignored;
+
+    KeyboardState keyboard;
+    TrackballState trackball;
+    TouchScreenState touchScreen;
+    union {
+        SingleTouchScreenState singleTouchScreen;
+        MultiTouchScreenState multiTouchScreen;
+    };
+
+    void reset();
+
+    inline bool isKeyboard() const { return classes & INPUT_DEVICE_CLASS_KEYBOARD; }
+    inline bool isAlphaKey() const { return classes & INPUT_DEVICE_CLASS_ALPHAKEY; }
+    inline bool isTrackball() const { return classes & INPUT_DEVICE_CLASS_TRACKBALL; }
+    inline bool isDPad() const { return classes & INPUT_DEVICE_CLASS_DPAD; }
+    inline bool isSingleTouchScreen() const { return (classes
+            & (INPUT_DEVICE_CLASS_TOUCHSCREEN | INPUT_DEVICE_CLASS_TOUCHSCREEN_MT))
+            == INPUT_DEVICE_CLASS_TOUCHSCREEN; }
+    inline bool isMultiTouchScreen() const { return classes
+            & INPUT_DEVICE_CLASS_TOUCHSCREEN_MT; }
+    inline bool isTouchScreen() const { return classes
+            & (INPUT_DEVICE_CLASS_TOUCHSCREEN | INPUT_DEVICE_CLASS_TOUCHSCREEN_MT); }
+};
+
+
+/* Processes raw input events and sends cooked event data to an input dispatcher
+ * in accordance with the input dispatch policy. */
+class InputReaderInterface : public virtual RefBase {
+protected:
+    InputReaderInterface() { }
+    virtual ~InputReaderInterface() { }
+
+public:
+    /* Runs a single iteration of the processing loop.
+     * Nominally reads and processes one incoming message from the EventHub.
+     *
+     * This method should be called on the input reader thread.
+     */
+    virtual void loopOnce() = 0;
+
+    /* Gets the current virtual key.  Returns false if not down.
+     *
+     * This method may be called on any thread (usually by the input manager).
+     */
+    virtual bool getCurrentVirtualKey(int32_t* outKeyCode, int32_t* outScanCode) const = 0;
+};
+
+/* The input reader reads raw event data from the event hub and processes it into input events
+ * that it sends to the input dispatcher.  Some functions of the input reader are controlled
+ * by the input dispatch policy, such as early event filtering in low power states.
+ */
+class InputReader : public InputReaderInterface {
+public:
+    InputReader(const sp<EventHubInterface>& eventHub,
+            const sp<InputDispatchPolicyInterface>& policy,
+            const sp<InputDispatcherInterface>& dispatcher);
+    virtual ~InputReader();
+
+    virtual void loopOnce();
+
+    virtual bool getCurrentVirtualKey(int32_t* outKeyCode, int32_t* outScanCode) const;
+
+private:
+    // Lock that must be acquired while manipulating state that may be concurrently accessed
+    // from other threads by input state query methods.  It should be held for as short a
+    // time as possible.
+    //
+    // Exported state:
+    //   - global virtual key code and scan code
+    //   - device list and immutable properties of devices such as id, name, and class
+    //     (but not other internal device state)
+    mutable Mutex mExportedStateLock;
+
+    // current virtual key information
+    int32_t mGlobalVirtualKeyCode;
+    int32_t mGlobalVirtualScanCode;
+
+    // combined key meta state
+    int32_t mGlobalMetaState;
+
+    sp<EventHubInterface> mEventHub;
+    sp<InputDispatchPolicyInterface> mPolicy;
+    sp<InputDispatcherInterface> mDispatcher;
+
+    KeyedVector<int32_t, InputDevice*> mDevices;
+
+    // display properties needed to translate touch screen coordinates into display coordinates
+    int32_t mDisplayOrientation;
+    int32_t mDisplayWidth;
+    int32_t mDisplayHeight;
+
+    // low-level input event decoding
+    void process(const RawEvent* rawEvent);
+    void handleDeviceAdded(const RawEvent* rawEvent);
+    void handleDeviceRemoved(const RawEvent* rawEvent);
+    void handleSync(const RawEvent* rawEvent);
+    void handleKey(const RawEvent* rawEvent);
+    void handleRelativeMotion(const RawEvent* rawEvent);
+    void handleAbsoluteMotion(const RawEvent* rawEvent);
+    void handleSwitch(const RawEvent* rawEvent);
+
+    // input policy processing and dispatch
+    void onKey(nsecs_t when, InputDevice* device, bool down,
+            int32_t keyCode, int32_t scanCode, uint32_t policyFlags);
+    void onSwitch(nsecs_t when, InputDevice* device, bool down, int32_t code);
+    void onSingleTouchScreenStateChanged(nsecs_t when, InputDevice* device);
+    void onMultiTouchScreenStateChanged(nsecs_t when, InputDevice* device);
+    void onTouchScreenChanged(nsecs_t when, InputDevice* device, bool havePointerIds);
+    void onTrackballStateChanged(nsecs_t when, InputDevice* device);
+    void onConfigurationChanged(nsecs_t when);
+
+    bool applyStandardInputDispatchPolicyActions(nsecs_t when,
+            int32_t policyActions, uint32_t* policyFlags);
+
+    bool consumeVirtualKeyTouches(nsecs_t when, InputDevice* device, uint32_t policyFlags);
+    void dispatchVirtualKey(nsecs_t when, InputDevice* device, uint32_t policyFlags,
+            int32_t keyEventAction, int32_t keyEventFlags);
+    void dispatchTouches(nsecs_t when, InputDevice* device, uint32_t policyFlags);
+    void dispatchTouch(nsecs_t when, InputDevice* device, uint32_t policyFlags,
+            InputDevice::TouchData* touch, BitSet32 idBits, int32_t motionEventAction);
+
+    // display
+    void resetDisplayProperties();
+    bool refreshDisplayProperties();
+
+    // device management
+    InputDevice* getDevice(int32_t deviceId);
+    InputDevice* getNonIgnoredDevice(int32_t deviceId);
+    void addDevice(nsecs_t when, int32_t deviceId);
+    void removeDevice(nsecs_t when, InputDevice* device);
+    void configureDevice(InputDevice* device);
+    void configureDeviceForCurrentDisplaySize(InputDevice* device);
+    void configureVirtualKeys(InputDevice* device);
+    void configureAbsoluteAxisInfo(InputDevice* device, int axis, const char* name,
+            InputDevice::AbsoluteAxisInfo* out);
+
+    // global meta state management for all devices
+    void resetGlobalMetaState();
+    int32_t globalMetaState();
+
+    // virtual key management
+    void updateGlobalVirtualKeyState();
+};
+
+
+/* Reads raw events from the event hub and processes them, endlessly. */
+class InputReaderThread : public Thread {
+public:
+    InputReaderThread(const sp<InputReaderInterface>& reader);
+    virtual ~InputReaderThread();
+
+private:
+    sp<InputReaderInterface> mReader;
+
+    virtual bool threadLoop();
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_READER_H
diff --git a/include/ui/InputTransport.h b/include/ui/InputTransport.h
new file mode 100644
index 0000000..9537523
--- /dev/null
+++ b/include/ui/InputTransport.h
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_TRANSPORT_H
+#define _UI_INPUT_TRANSPORT_H
+
+/**
+ * Native input transport.
+ *
+ * Uses anonymous shared memory as a whiteboard for sending input events from an
+ * InputPublisher to an InputConsumer and ensuring appropriate synchronization.
+ * One interesting feature is that published events can be updated in place as long as they
+ * have not yet been consumed.
+ *
+ * The InputPublisher and InputConsumer only take care of transferring event data
+ * over an InputChannel and sending synchronization signals.  The InputDispatcher and InputQueue
+ * build on these abstractions to add multiplexing and queueing.
+ */
+
+#include <semaphore.h>
+#include <ui/Input.h>
+#include <utils/Errors.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+
+namespace android {
+
+/*
+ * An input channel consists of a shared memory buffer and a pair of pipes
+ * used to send input messages from an InputPublisher to an InputConsumer
+ * across processes.  Each channel has a descriptive name for debugging purposes.
+ *
+ * Each endpoint has its own InputChannel object that specifies its own file descriptors.
+ *
+ * The input channel is closed when all references to it are released.
+ */
+class InputChannel : public RefBase {
+protected:
+    virtual ~InputChannel();
+
+public:
+    InputChannel(const String8& name, int32_t ashmemFd, int32_t receivePipeFd,
+            int32_t sendPipeFd);
+
+    /* Creates a pair of input channels and their underlying shared memory buffers
+     * and pipes.
+     *
+     * Returns OK on success.
+     */
+    static status_t openInputChannelPair(const String8& name,
+            InputChannel** outServerChannel, InputChannel** outClientChannel);
+
+    inline String8 getName() const { return mName; }
+    inline int32_t getAshmemFd() const { return mAshmemFd; }
+    inline int32_t getReceivePipeFd() const { return mReceivePipeFd; }
+    inline int32_t getSendPipeFd() const { return mSendPipeFd; }
+
+    /* Sends a signal to the other endpoint.
+     *
+     * Returns OK on success.
+     * Errors probably indicate that the channel is broken.
+     */
+    status_t sendSignal(char signal);
+
+    /* Receives a signal send by the other endpoint.
+     * (Should only call this after poll() indicates that the receivePipeFd has available input.)
+     *
+     * Returns OK on success.
+     * Returns WOULD_BLOCK if there is no signal present.
+     * Other errors probably indicate that the channel is broken.
+     */
+    status_t receiveSignal(char* outSignal);
+
+private:
+    String8 mName;
+    int32_t mAshmemFd;
+    int32_t mReceivePipeFd;
+    int32_t mSendPipeFd;
+};
+
+/*
+ * Private intermediate representation of input events as messages written into an
+ * ashmem buffer.
+ */
+struct InputMessage {
+    /* Semaphore count is set to 1 when the message is published.
+     * It becomes 0 transiently while the publisher updates the message.
+     * It becomes 0 permanently when the consumer consumes the message.
+     */
+    sem_t semaphore;
+
+    /* Initialized to false by the publisher.
+     * Set to true by the consumer when it consumes the message.
+     */
+    bool consumed;
+
+    int32_t type;
+
+    struct SampleData {
+        nsecs_t eventTime;
+        PointerCoords coords[0]; // variable length
+    };
+
+    int32_t deviceId;
+    int32_t nature;
+
+    union {
+        struct {
+            int32_t action;
+            int32_t flags;
+            int32_t keyCode;
+            int32_t scanCode;
+            int32_t metaState;
+            int32_t repeatCount;
+            nsecs_t downTime;
+            nsecs_t eventTime;
+        } key;
+
+        struct {
+            int32_t action;
+            int32_t metaState;
+            int32_t edgeFlags;
+            nsecs_t downTime;
+            float xOffset;
+            float yOffset;
+            float xPrecision;
+            float yPrecision;
+            size_t pointerCount;
+            int32_t pointerIds[MAX_POINTERS];
+            size_t sampleCount;
+            SampleData sampleData[0]; // variable length
+        } motion;
+    };
+
+    /* Gets the number of bytes to add to step to the next SampleData object in a motion
+     * event message for a given number of pointers.
+     */
+    static inline size_t sampleDataStride(size_t pointerCount) {
+        return sizeof(InputMessage::SampleData) + pointerCount * sizeof(PointerCoords);
+    }
+
+    /* Adds the SampleData stride to the given pointer. */
+    static inline SampleData* sampleDataPtrIncrement(SampleData* ptr, size_t stride) {
+        return reinterpret_cast<InputMessage::SampleData*>(reinterpret_cast<char*>(ptr) + stride);
+    }
+};
+
+/*
+ * Publishes input events to an anonymous shared memory buffer.
+ * Uses atomic operations to coordinate shared access with a single concurrent consumer.
+ */
+class InputPublisher {
+public:
+    /* Creates a publisher associated with an input channel. */
+    explicit InputPublisher(const sp<InputChannel>& channel);
+
+    /* Destroys the publisher and releases its input channel. */
+    ~InputPublisher();
+
+    /* Gets the underlying input channel. */
+    inline sp<InputChannel> getChannel() { return mChannel; }
+
+    /* Prepares the publisher for use.  Must be called before it is used.
+     * Returns OK on success.
+     *
+     * This method implicitly calls reset(). */
+    status_t initialize();
+
+    /* Resets the publisher to its initial state and unpins its ashmem buffer.
+     * Returns OK on success.
+     *
+     * Should be called after an event has been consumed to release resources used by the
+     * publisher until the next event is ready to be published.
+     */
+    status_t reset();
+
+    /* Publishes a key event to the ashmem buffer.
+     *
+     * Returns OK on success.
+     * Returns INVALID_OPERATION if the publisher has not been reset.
+     */
+    status_t publishKeyEvent(
+            int32_t deviceId,
+            int32_t nature,
+            int32_t action,
+            int32_t flags,
+            int32_t keyCode,
+            int32_t scanCode,
+            int32_t metaState,
+            int32_t repeatCount,
+            nsecs_t downTime,
+            nsecs_t eventTime);
+
+    /* Publishes a motion event to the ashmem buffer.
+     *
+     * Returns OK on success.
+     * Returns INVALID_OPERATION if the publisher has not been reset.
+     * Returns BAD_VALUE if pointerCount is less than 1 or greater than MAX_POINTERS.
+     */
+    status_t publishMotionEvent(
+            int32_t deviceId,
+            int32_t nature,
+            int32_t action,
+            int32_t edgeFlags,
+            int32_t metaState,
+            float xOffset,
+            float yOffset,
+            float xPrecision,
+            float yPrecision,
+            nsecs_t downTime,
+            nsecs_t eventTime,
+            size_t pointerCount,
+            const int32_t* pointerIds,
+            const PointerCoords* pointerCoords);
+
+    /* Appends a motion sample to a motion event unless already consumed.
+     *
+     * Returns OK on success.
+     * Returns INVALID_OPERATION if the current event is not a MOTION_EVENT_ACTION_MOVE event.
+     * Returns FAILED_TRANSACTION if the current event has already been consumed.
+     * Returns NO_MEMORY if the buffer is full and no additional samples can be added.
+     */
+    status_t appendMotionSample(
+            nsecs_t eventTime,
+            const PointerCoords* pointerCoords);
+
+    /* Sends a dispatch signal to the consumer to inform it that a new message is available.
+     *
+     * Returns OK on success.
+     * Errors probably indicate that the channel is broken.
+     */
+    status_t sendDispatchSignal();
+
+    /* Receives the finished signal from the consumer in reply to the original dispatch signal.
+     *
+     * Returns OK on success.
+     * Returns WOULD_BLOCK if there is no signal present.
+     * Other errors probably indicate that the channel is broken.
+     */
+    status_t receiveFinishedSignal();
+
+private:
+    sp<InputChannel> mChannel;
+
+    size_t mAshmemSize;
+    InputMessage* mSharedMessage;
+    bool mPinned;
+    bool mSemaphoreInitialized;
+    bool mWasDispatched;
+
+    size_t mMotionEventPointerCount;
+    InputMessage::SampleData* mMotionEventSampleDataTail;
+    size_t mMotionEventSampleDataStride;
+
+    status_t publishInputEvent(
+            int32_t type,
+            int32_t deviceId,
+            int32_t nature);
+};
+
+/*
+ * Consumes input events from an anonymous shared memory buffer.
+ * Uses atomic operations to coordinate shared access with a single concurrent publisher.
+ */
+class InputConsumer {
+public:
+    /* Creates a consumer associated with an input channel. */
+    explicit InputConsumer(const sp<InputChannel>& channel);
+
+    /* Destroys the consumer and releases its input channel. */
+    ~InputConsumer();
+
+    /* Gets the underlying input channel. */
+    inline sp<InputChannel> getChannel() { return mChannel; }
+
+    /* Prepares the consumer for use.  Must be called before it is used. */
+    status_t initialize();
+
+    /* Consumes the input event in the buffer and copies its contents into
+     * an InputEvent object created using the specified factory.
+     * This operation will block if the publisher is updating the event.
+     *
+     * Returns OK on success.
+     * Returns INVALID_OPERATION if there is no currently published event.
+     * Returns NO_MEMORY if the event could not be created.
+     */
+    status_t consume(InputEventFactoryInterface* factory, InputEvent** event);
+
+    /* Sends a finished signal to the publisher to inform it that the current message is
+     * finished processing.
+     *
+     * Returns OK on success.
+     * Errors probably indicate that the channel is broken.
+     */
+    status_t sendFinishedSignal();
+
+    /* Receives the dispatched signal from the publisher.
+     *
+     * Returns OK on success.
+     * Returns WOULD_BLOCK if there is no signal present.
+     * Other errors probably indicate that the channel is broken.
+     */
+    status_t receiveDispatchSignal();
+
+private:
+    sp<InputChannel> mChannel;
+
+    size_t mAshmemSize;
+    InputMessage* mSharedMessage;
+
+    void populateKeyEvent(KeyEvent* keyEvent) const;
+    void populateMotionEvent(MotionEvent* motionEvent) const;
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_TRANSPORT_H
diff --git a/include/utils/BitSet.h b/include/utils/BitSet.h
new file mode 100644
index 0000000..19c8bf0
--- /dev/null
+++ b/include/utils/BitSet.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef UTILS_BITSET_H
+#define UTILS_BITSET_H
+
+#include <stdint.h>
+
+/*
+ * Contains some bit manipulation helpers.
+ */
+
+namespace android {
+
+// A simple set of 32 bits that can be individually marked or cleared.
+struct BitSet32 {
+    uint32_t value;
+
+    inline BitSet32() : value(0) { }
+    explicit inline BitSet32(uint32_t value) : value(value) { }
+
+    // Gets the value associated with a particular bit index.
+    static inline uint32_t valueForBit(uint32_t n) { return 0x80000000 >> n; }
+
+    // Clears the bit set.
+    inline void clear() { value = 0; }
+
+    // Returns true if the bit set does not contain any marked bits.
+    inline bool isEmpty() const { return ! value; }
+
+    // Returns true if the specified bit is marked.
+    inline bool hasBit(uint32_t n) const { return value & valueForBit(n); }
+
+    // Marks the specified bit.
+    inline void markBit(uint32_t n) { value |= valueForBit(n); }
+
+    // Clears the specified bit.
+    inline void clearBit(uint32_t n) { value &= ~ valueForBit(n); }
+
+    // Finds the first marked bit in the set.
+    // Result is undefined if all bits are unmarked.
+    inline uint32_t firstMarkedBit() const { return __builtin_clz(value); }
+
+    // Finds the first unmarked bit in the set.
+    // Result is undefined if all bits are marked.
+    inline uint32_t firstUnmarkedBit() const { return __builtin_clz(~ value); }
+
+    inline bool operator== (const BitSet32& other) const { return value == other.value; }
+    inline bool operator!= (const BitSet32& other) const { return value != other.value; }
+};
+
+} // namespace android
+
+#endif // UTILS_BITSET_H
diff --git a/include/utils/Buffer.h b/include/utils/Buffer.h
deleted file mode 100644
index 8e22b0f..0000000
--- a/include/utils/Buffer.h
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef __UTILS_BUFFER_H__
-#define __UTILS_BUFFER_H__ 1
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-namespace android {
-
-class Buffer
-{
-private:
-    char *buf;
-    int bufsiz;
-    int used;
-    void ensureCapacity(int len);
-
-    void
-    makeRoomFor(int len)
-    {
-        if (len + used >= bufsiz) {
-            bufsiz = (len + used) * 3/2 + 2;
-            char *blah = new char[bufsiz];
-
-            memcpy(blah, buf, used);
-            delete[] buf;
-            buf = blah;
-        }
-    }
-    
-public:
-    Buffer()
-    {
-        bufsiz = 16;
-        buf = new char[bufsiz];
-        clear();
-    }
-
-    ~Buffer()
-    {
-       delete[] buf;
-    }
-
-    void
-    clear()
-    {
-        buf[0] = '\0';
-        used = 0;
-    }
-
-    int
-    length()
-    {
-        return used;
-    }
-
-    void
-    append(const char c)
-    {
-        makeRoomFor(1);
-        buf[used] = c;
-        used++;
-        buf[used] = '\0';
-    }
-
-    void
-    append(const char *s, int len)
-    {
-        makeRoomFor(len);
-
-        memcpy(buf + used, s, len);
-        used += len;
-        buf[used] = '\0';
-    }
-
-    void
-    append(const char *s)
-    {
-        append(s, strlen(s));
-    }
-
-    char *
-    getBytes()
-    {
-        return buf;
-    }
-};
-
-}; // namespace android
-
-#endif
diff --git a/include/utils/PollLoop.h b/include/utils/PollLoop.h
new file mode 100644
index 0000000..2ec39fe
--- /dev/null
+++ b/include/utils/PollLoop.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef UTILS_POLL_LOOP_H
+#define UTILS_POLL_LOOP_H
+
+#include <utils/Vector.h>
+#include <utils/threads.h>
+
+#include <poll.h>
+
+namespace android {
+
+/**
+ * A basic file descriptor polling loop based on poll() with callbacks.
+ */
+class PollLoop : public RefBase {
+protected:
+    virtual ~PollLoop();
+
+public:
+    PollLoop();
+
+    /**
+     * A callback that it to be invoked when an event occurs on a file descriptor.
+     * Specifies the events that were triggered and the user data provided when the
+     * callback was set.
+     *
+     * Returns true if the callback should be kept, false if it should be removed automatically
+     * after the callback returns.
+     */
+    typedef bool (*Callback)(int fd, int events, void* data);
+
+    /**
+     * Performs a single call to poll() with optional timeout in milliseconds.
+     * Invokes callbacks for all file descriptors on which an event occurred.
+     *
+     * If the timeout is zero, returns immediately without blocking.
+     * If the timeout is negative, waits indefinitely until awoken.
+     *
+     * Returns true if a callback was invoked or if the loop was awoken by wake().
+     * Returns false if a timeout or error occurred.
+     *
+     * This method must only be called on the main thread.
+     * This method blocks until either a file descriptor is signalled, a timeout occurs,
+     * or wake() is called.
+     * This method does not return until it has finished invoking the appropriate callbacks
+     * for all file descriptors that were signalled.
+     */
+    bool pollOnce(int timeoutMillis);
+
+    /**
+     * Wakes the loop asynchronously.
+     *
+     * This method can be called on any thread.
+     * This method returns immediately.
+     */
+    void wake();
+
+    /**
+     * Sets the callback for a file descriptor, replacing the existing one, if any.
+     * It is an error to call this method with events == 0 or callback == NULL.
+     *
+     * Note that a callback can be invoked with the POLLERR, POLLHUP or POLLNVAL events
+     * even if it is not explicitly requested when registered.
+     *
+     * This method can be called on any thread.
+     * This method may block briefly if it needs to wake the poll loop.
+     */
+    void setCallback(int fd, int events, Callback callback, void* data = NULL);
+
+    /**
+     * Removes the callback for a file descriptor, if one exists.
+     *
+     * When this method returns, it is safe to close the file descriptor since the poll loop
+     * will no longer have a reference to it.  However, it is possible for the callback to
+     * already be running or for it to run one last time if the file descriptor was already
+     * signalled.  Calling code is responsible for ensuring that this case is safely handled.
+     * For example, if the callback takes care of removing itself during its own execution either
+     * by returning false or calling this method, then it can be guaranteed to not be invoked
+     * again at any later time unless registered anew.
+     *
+     * This method can be called on any thread.
+     * This method may block briefly if it needs to wake the poll loop.
+     *
+     * Returns true if a callback was actually removed, false if none was registered.
+     */
+    bool removeCallback(int fd);
+
+private:
+    struct RequestedCallback {
+        Callback callback;
+        void* data;
+    };
+
+    struct PendingCallback {
+        int fd;
+        int events;
+        Callback callback;
+        void* data;
+    };
+
+    Mutex mLock;
+    Condition mAwake;
+    bool mPolling;
+
+    int mWakeReadPipeFd;
+    int mWakeWritePipeFd;
+
+    Vector<struct pollfd> mRequestedFds;
+    Vector<RequestedCallback> mRequestedCallbacks;
+
+    Vector<PendingCallback> mPendingCallbacks; // used privately by pollOnce
+
+    void openWakePipe();
+    void closeWakePipe();
+
+    ssize_t getRequestIndexLocked(int fd);
+    void wakeAndLock();
+};
+
+} // namespace android
+
+#endif // UTILS_POLL_LOOP_H
diff --git a/include/utils/Pool.h b/include/utils/Pool.h
new file mode 100644
index 0000000..2ee768e
--- /dev/null
+++ b/include/utils/Pool.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef UTILS_POOL_H
+#define UTILS_POOL_H
+
+#include <utils/TypeHelpers.h>
+
+namespace android {
+
+class PoolImpl {
+public:
+    PoolImpl(size_t objSize);
+    ~PoolImpl();
+
+    void* allocImpl();
+    void freeImpl(void* obj);
+
+private:
+    size_t mObjSize;
+};
+
+/*
+ * A homogeneous typed memory pool for fixed size objects.
+ * Not intended to be thread-safe.
+ */
+template<typename T>
+class Pool : private PoolImpl {
+public:
+    /* Creates an initially empty pool. */
+    Pool() : PoolImpl(sizeof(T)) { }
+
+    /* Destroys the pool.
+     * Assumes that the pool is empty. */
+    ~Pool() { }
+
+    /* Allocates an object from the pool, growing the pool if needed. */
+    inline T* alloc() {
+        void* mem = allocImpl();
+        if (! traits<T>::has_trivial_ctor) {
+            return new (mem) T();
+        } else {
+            return static_cast<T*>(mem);
+        }
+    }
+
+    /* Frees an object from the pool. */
+    inline void free(T* obj) {
+        if (! traits<T>::has_trivial_dtor) {
+            obj->~T();
+        }
+        freeImpl(obj);
+    }
+};
+
+} // namespace android
+
+#endif // UTILS_POOL_H
diff --git a/include/utils/StopWatch.h b/include/utils/StopWatch.h
index cc0bebc..693dd3c 100644
--- a/include/utils/StopWatch.h
+++ b/include/utils/StopWatch.h
@@ -37,6 +37,8 @@
         const char* name() const;
         nsecs_t     lap();
         nsecs_t     elapsedTime() const;
+
+        void        reset();
         
 private:
     const char*     mName;
diff --git a/include/utils/Vector.h b/include/utils/Vector.h
index ad59fd6..d40ae16 100644
--- a/include/utils/Vector.h
+++ b/include/utils/Vector.h
@@ -114,6 +114,12 @@
             ssize_t         appendVector(const Vector<TYPE>& vector);
 
 
+    //! insert an array at a given index
+            ssize_t         insertArrayAt(const TYPE* array, size_t index, size_t numItems);
+
+    //! append an array at the end of this vector
+            ssize_t         appendArray(const TYPE* array, size_t numItems);
+
             /*! 
              * add/insert/replace items
              */
@@ -259,6 +265,16 @@
 }
 
 template<class TYPE> inline
+ssize_t Vector<TYPE>::insertArrayAt(const TYPE* array, size_t index, size_t numItems) {
+    return VectorImpl::insertAt(array, index, numItems);
+}
+
+template<class TYPE> inline
+ssize_t Vector<TYPE>::appendArray(const TYPE* array, size_t numItems) {
+    return VectorImpl::add(array, numItems);
+}
+
+template<class TYPE> inline
 ssize_t Vector<TYPE>::insertAt(const TYPE& item, size_t index, size_t numItems) {
     return VectorImpl::insertAt(&item, index, numItems);
 }
diff --git a/include/utils/VectorImpl.h b/include/utils/VectorImpl.h
index 49b03f1..46a7bc2 100644
--- a/include/utils/VectorImpl.h
+++ b/include/utils/VectorImpl.h
@@ -76,7 +76,7 @@
             void            push();
             void            push(const void* item);
             ssize_t         add();
-            ssize_t         add(const void* item);
+            ssize_t         add(const void* item, size_t numItems = 1);
             ssize_t         replaceAt(size_t index);
             ssize_t         replaceAt(const void* item, size_t index);
 
@@ -184,6 +184,8 @@
             void            push(const void* item);
             ssize_t         insertVectorAt(const VectorImpl& vector, size_t index);
             ssize_t         appendVector(const VectorImpl& vector);
+            ssize_t         insertArrayAt(const void* array, size_t index, size_t numItems);
+            ssize_t         appendArray(const void* array, size_t numItems);
             ssize_t         insertAt(size_t where, size_t numItems = 1);
             ssize_t         insertAt(const void* item, size_t where, size_t numItems = 1);
             ssize_t         replaceAt(size_t index);
diff --git a/libs/ui/Android.mk b/libs/ui/Android.mk
index f7acd97..24cdc78 100644
--- a/libs/ui/Android.mk
+++ b/libs/ui/Android.mk
@@ -11,6 +11,11 @@
 	GraphicBufferMapper.cpp \
 	KeyLayoutMap.cpp \
 	KeyCharacterMap.cpp \
+	Input.cpp \
+	InputDispatcher.cpp \
+	InputManager.cpp \
+	InputReader.cpp \
+	InputTransport.cpp \
 	IOverlay.cpp \
 	Overlay.cpp \
 	PixelFormat.cpp \
diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp
index d45eaf0..27895f2 100644
--- a/libs/ui/EventHub.cpp
+++ b/libs/ui/EventHub.cpp
@@ -155,77 +155,70 @@
     return 0;
 }
 
-int EventHub::getSwitchState(int sw) const
-{
-#ifdef EV_SW
-    if (sw >= 0 && sw <= SW_MAX) {
-        int32_t devid = mSwitches[sw];
-        if (devid != 0) {
-            return getSwitchState(devid, sw);
+int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t deviceClasses,
+        int32_t scanCode) const {
+    if (scanCode >= 0 && scanCode <= KEY_MAX) {
+        AutoMutex _l(mLock);
+
+        if (deviceId == -1) {
+            for (int i = 0; i < mNumDevicesById; i++) {
+                device_t* device = mDevicesById[i].device;
+                if (device != NULL && (device->classes & deviceClasses) != 0) {
+                    int32_t result = getScanCodeStateLocked(device, scanCode);
+                    if (result >= KEY_STATE_DOWN) {
+                        return result;
+                    }
+                }
+            }
+            return KEY_STATE_UP;
+        } else {
+            device_t* device = getDevice(deviceId);
+            if (device != NULL) {
+                return getScanCodeStateLocked(device, scanCode);
+            }
         }
     }
-#endif
-    return -1;
+    return KEY_STATE_UNKNOWN;
 }
 
-int EventHub::getSwitchState(int32_t deviceId, int sw) const
-{
-#ifdef EV_SW
-    AutoMutex _l(mLock);
-    device_t* device = getDevice(deviceId);
-    if (device == NULL) return -1;
-    
-    if (sw >= 0 && sw <= SW_MAX) {
-        uint8_t sw_bitmask[(SW_MAX+7)/8];
-        memset(sw_bitmask, 0, sizeof(sw_bitmask));
-        if (ioctl(mFDs[id_to_index(device->id)].fd,
-                   EVIOCGSW(sizeof(sw_bitmask)), sw_bitmask) >= 0) {
-            return test_bit(sw, sw_bitmask) ? 1 : 0;
+int32_t EventHub::getScanCodeStateLocked(device_t* device, int32_t scanCode) const {
+    uint8_t key_bitmask[(KEY_MAX + 7) / 8];
+    memset(key_bitmask, 0, sizeof(key_bitmask));
+    if (ioctl(mFDs[id_to_index(device->id)].fd,
+               EVIOCGKEY(sizeof(key_bitmask)), key_bitmask) >= 0) {
+        return test_bit(scanCode, key_bitmask) ? KEY_STATE_DOWN : KEY_STATE_UP;
+    }
+    return KEY_STATE_UNKNOWN;
+}
+
+int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t deviceClasses,
+        int32_t keyCode) const {
+
+    if (deviceId == -1) {
+        for (int i = 0; i < mNumDevicesById; i++) {
+            device_t* device = mDevicesById[i].device;
+            if (device != NULL && (device->classes & deviceClasses) != 0) {
+                int32_t result = getKeyCodeStateLocked(device, keyCode);
+                if (result >= KEY_STATE_DOWN) {
+                    return result;
+                }
+            }
+        }
+        return KEY_STATE_UP;
+    } else {
+        device_t* device = getDevice(deviceId);
+        if (device != NULL) {
+            return getKeyCodeStateLocked(device, keyCode);
         }
     }
-#endif
-    
-    return -1;
+    return KEY_STATE_UNKNOWN;
 }
 
-int EventHub::getScancodeState(int code) const
-{
-    return getScancodeState(mFirstKeyboardId, code);
-}
-
-int EventHub::getScancodeState(int32_t deviceId, int code) const
-{
-    AutoMutex _l(mLock);
-    device_t* device = getDevice(deviceId);
-    if (device == NULL) return -1;
-    
-    if (code >= 0 && code <= KEY_MAX) {
-        uint8_t key_bitmask[(KEY_MAX+7)/8];
-        memset(key_bitmask, 0, sizeof(key_bitmask));
-        if (ioctl(mFDs[id_to_index(device->id)].fd,
-                   EVIOCGKEY(sizeof(key_bitmask)), key_bitmask) >= 0) {
-            return test_bit(code, key_bitmask) ? 1 : 0;
-        }
-    }
-    
-    return -1;
-}
-
-int EventHub::getKeycodeState(int code) const
-{
-    return getKeycodeState(mFirstKeyboardId, code);
-}
-
-int EventHub::getKeycodeState(int32_t deviceId, int code) const
-{
-    AutoMutex _l(mLock);
-    device_t* device = getDevice(deviceId);
-    if (device == NULL || device->layoutMap == NULL) return -1;
-    
+int32_t EventHub::getKeyCodeStateLocked(device_t* device, int32_t keyCode) const {
     Vector<int32_t> scanCodes;
-    device->layoutMap->findScancodes(code, &scanCodes);
-    
-    uint8_t key_bitmask[(KEY_MAX+7)/8];
+    device->layoutMap->findScancodes(keyCode, &scanCodes);
+
+    uint8_t key_bitmask[(KEY_MAX + 7) / 8];
     memset(key_bitmask, 0, sizeof(key_bitmask));
     if (ioctl(mFDs[id_to_index(device->id)].fd,
                EVIOCGKEY(sizeof(key_bitmask)), key_bitmask) >= 0) {
@@ -239,12 +232,45 @@
             int32_t sc = scanCodes.itemAt(i);
             //LOGI("Code %d: down=%d", sc, test_bit(sc, key_bitmask));
             if (sc >= 0 && sc <= KEY_MAX && test_bit(sc, key_bitmask)) {
-                return 1;
+                return KEY_STATE_DOWN;
             }
         }
+        return KEY_STATE_UP;
     }
-    
-    return 0;
+    return KEY_STATE_UNKNOWN;
+}
+
+int32_t EventHub::getSwitchState(int32_t deviceId, int32_t deviceClasses, int32_t sw) const {
+#ifdef EV_SW
+    if (sw >= 0 && sw <= SW_MAX) {
+        AutoMutex _l(mLock);
+
+        if (deviceId == -1) {
+            deviceId = mSwitches[sw];
+            if (deviceId == 0) {
+                return KEY_STATE_UNKNOWN;
+            }
+        }
+
+        device_t* device = getDevice(deviceId);
+        if (device == NULL) {
+            return KEY_STATE_UNKNOWN;
+        }
+
+        return getSwitchStateLocked(device, sw);
+    }
+#endif
+    return KEY_STATE_UNKNOWN;
+}
+
+int32_t EventHub::getSwitchStateLocked(device_t* device, int32_t sw) const {
+    uint8_t sw_bitmask[(SW_MAX + 7) / 8];
+    memset(sw_bitmask, 0, sizeof(sw_bitmask));
+    if (ioctl(mFDs[id_to_index(device->id)].fd,
+               EVIOCGSW(sizeof(sw_bitmask)), sw_bitmask) >= 0) {
+        return test_bit(sw, sw_bitmask) ? KEY_STATE_DOWN : KEY_STATE_UP;
+    }
+    return KEY_STATE_UNKNOWN;
 }
 
 status_t EventHub::scancodeToKeycode(int32_t deviceId, int scancode,
@@ -309,9 +335,6 @@
 
     status_t err;
 
-    fd_set readfds;
-    int maxFd = -1;
-    int cc;
     int i;
     int res;
     int pollres;
@@ -457,7 +480,7 @@
  * Inspect the known devices to determine whether physical keys exist for the given
  * framework-domain key codes.
  */
-bool EventHub::hasKeys(size_t numCodes, int32_t* keyCodes, uint8_t* outFlags) {
+bool EventHub::hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const {
     for (size_t codeIndex = 0; codeIndex < numCodes; codeIndex++) {
         outFlags[codeIndex] = 0;
 
@@ -465,7 +488,8 @@
         Vector<int32_t> scanCodes;
         for (int n = 0; (n < mFDCount) && (outFlags[codeIndex] == 0); n++) {
             if (mDevices[n]) {
-                status_t err = mDevices[n]->layoutMap->findScancodes(keyCodes[codeIndex], &scanCodes);
+                status_t err = mDevices[n]->layoutMap->findScancodes(
+                        keyCodes[codeIndex], &scanCodes);
                 if (!err) {
                     // check the possible scan codes identified by the layout map against the
                     // map of codes actually emitted by the driver
@@ -618,11 +642,11 @@
         //}
         for (int i=0; i<((BTN_MISC+7)/8); i++) {
             if (key_bitmask[i] != 0) {
-                device->classes |= CLASS_KEYBOARD;
+                device->classes |= INPUT_DEVICE_CLASS_KEYBOARD;
                 break;
             }
         }
-        if ((device->classes & CLASS_KEYBOARD) != 0) {
+        if ((device->classes & INPUT_DEVICE_CLASS_KEYBOARD) != 0) {
             device->keyBitmask = new uint8_t[sizeof(key_bitmask)];
             if (device->keyBitmask != NULL) {
                 memcpy(device->keyBitmask, key_bitmask, sizeof(key_bitmask));
@@ -642,7 +666,7 @@
         if (ioctl(fd, EVIOCGBIT(EV_REL, sizeof(rel_bitmask)), rel_bitmask) >= 0)
         {
             if (test_bit(REL_X, rel_bitmask) && test_bit(REL_Y, rel_bitmask)) {
-                device->classes |= CLASS_TRACKBALL;
+                device->classes |= INPUT_DEVICE_CLASS_TRACKBALL;
             }
         }
     }
@@ -656,12 +680,12 @@
     if (test_bit(ABS_MT_TOUCH_MAJOR, abs_bitmask)
             && test_bit(ABS_MT_POSITION_X, abs_bitmask)
             && test_bit(ABS_MT_POSITION_Y, abs_bitmask)) {
-        device->classes |= CLASS_TOUCHSCREEN | CLASS_TOUCHSCREEN_MT;
+        device->classes |= INPUT_DEVICE_CLASS_TOUCHSCREEN | INPUT_DEVICE_CLASS_TOUCHSCREEN_MT;
         
     // Is this an old style single-touch driver?
     } else if (test_bit(BTN_TOUCH, key_bitmask)
             && test_bit(ABS_X, abs_bitmask) && test_bit(ABS_Y, abs_bitmask)) {
-        device->classes |= CLASS_TOUCHSCREEN;
+        device->classes |= INPUT_DEVICE_CLASS_TOUCHSCREEN;
     }
 
 #ifdef EV_SW
@@ -680,7 +704,7 @@
     }
 #endif
 
-    if ((device->classes&CLASS_KEYBOARD) != 0) {
+    if ((device->classes & INPUT_DEVICE_CLASS_KEYBOARD) != 0) {
         char tmpfn[sizeof(name)];
         char keylayoutFilename[300];
 
@@ -723,7 +747,7 @@
 
         // 'Q' key support = cheap test of whether this is an alpha-capable kbd
         if (hasKeycode(device, kKeyCodeQ)) {
-            device->classes |= CLASS_ALPHAKEY;
+            device->classes |= INPUT_DEVICE_CLASS_ALPHAKEY;
         }
         
         // See if this has a DPAD.
@@ -732,7 +756,7 @@
                 hasKeycode(device, kKeyCodeDpadLeft) &&
                 hasKeycode(device, kKeyCodeDpadRight) &&
                 hasKeycode(device, kKeyCodeDpadCenter)) {
-            device->classes |= CLASS_DPAD;
+            device->classes |= INPUT_DEVICE_CLASS_DPAD;
         }
         
         LOGI("New keyboard: device->id=0x%x devname='%s' propName='%s' keylayout='%s'\n",
diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp
new file mode 100644
index 0000000..d367708
--- /dev/null
+++ b/libs/ui/Input.cpp
@@ -0,0 +1,238 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+// Provides a pipe-based transport for native events in the NDK.
+//
+#define LOG_TAG "Input"
+
+//#define LOG_NDEBUG 0
+
+#include <ui/Input.h>
+
+namespace android {
+
+// class InputEvent
+
+void InputEvent::initialize(int32_t deviceId, int32_t nature) {
+    mDeviceId = deviceId;
+    mNature = nature;
+}
+
+// class KeyEvent
+
+void KeyEvent::initialize(
+        int32_t deviceId,
+        int32_t nature,
+        int32_t action,
+        int32_t flags,
+        int32_t keyCode,
+        int32_t scanCode,
+        int32_t metaState,
+        int32_t repeatCount,
+        nsecs_t downTime,
+        nsecs_t eventTime) {
+    InputEvent::initialize(deviceId, nature);
+    mAction = action;
+    mFlags = flags;
+    mKeyCode = keyCode;
+    mScanCode = scanCode;
+    mMetaState = metaState;
+    mRepeatCount = repeatCount;
+    mDownTime = downTime;
+    mEventTime = eventTime;
+}
+
+// class MotionEvent
+
+void MotionEvent::initialize(
+        int32_t deviceId,
+        int32_t nature,
+        int32_t action,
+        int32_t edgeFlags,
+        int32_t metaState,
+        float rawX,
+        float rawY,
+        float xPrecision,
+        float yPrecision,
+        nsecs_t downTime,
+        nsecs_t eventTime,
+        size_t pointerCount,
+        const int32_t* pointerIds,
+        const PointerCoords* pointerCoords) {
+    InputEvent::initialize(deviceId, nature);
+    mAction = action;
+    mEdgeFlags = edgeFlags;
+    mMetaState = metaState;
+    mRawX = rawX;
+    mRawY = rawY;
+    mXPrecision = xPrecision;
+    mYPrecision = yPrecision;
+    mDownTime = downTime;
+    mPointerIds.clear();
+    mPointerIds.appendArray(pointerIds, pointerCount);
+    mSampleEventTimes.clear();
+    mSamplePointerCoords.clear();
+    addSample(eventTime, pointerCoords);
+}
+
+void MotionEvent::addSample(
+        int64_t eventTime,
+        const PointerCoords* pointerCoords) {
+    mSampleEventTimes.push(eventTime);
+    mSamplePointerCoords.appendArray(pointerCoords, getPointerCount());
+}
+
+void MotionEvent::offsetLocation(float xOffset, float yOffset) {
+    if (xOffset != 0 || yOffset != 0) {
+        for (size_t i = 0; i < mSamplePointerCoords.size(); i++) {
+            PointerCoords& pointerCoords = mSamplePointerCoords.editItemAt(i);
+            pointerCoords.x += xOffset;
+            pointerCoords.y += yOffset;
+        }
+    }
+}
+
+} // namespace android
+
+// NDK APIs
+
+using android::InputEvent;
+using android::KeyEvent;
+using android::MotionEvent;
+
+int32_t input_event_get_type(const input_event_t* event) {
+    return reinterpret_cast<const InputEvent*>(event)->getType();
+}
+
+int32_t input_event_get_device_id(const input_event_t* event) {
+    return reinterpret_cast<const InputEvent*>(event)->getDeviceId();
+}
+
+int32_t input_event_get_nature(const input_event_t* event) {
+    return reinterpret_cast<const InputEvent*>(event)->getNature();
+}
+
+int32_t key_event_get_action(const input_event_t* key_event) {
+    return reinterpret_cast<const KeyEvent*>(key_event)->getAction();
+}
+
+int32_t key_event_get_flags(const input_event_t* key_event) {
+    return reinterpret_cast<const KeyEvent*>(key_event)->getFlags();
+}
+
+int32_t key_event_get_key_code(const input_event_t* key_event) {
+    return reinterpret_cast<const KeyEvent*>(key_event)->getKeyCode();
+}
+
+int32_t key_event_get_scan_code(const input_event_t* key_event) {
+    return reinterpret_cast<const KeyEvent*>(key_event)->getScanCode();
+}
+
+int32_t key_event_get_meta_state(const input_event_t* key_event) {
+    return reinterpret_cast<const KeyEvent*>(key_event)->getMetaState();
+}
+int32_t key_event_get_repeat_count(const input_event_t* key_event) {
+    return reinterpret_cast<const KeyEvent*>(key_event)->getRepeatCount();
+}
+
+int64_t key_event_get_down_time(const input_event_t* key_event) {
+    return reinterpret_cast<const KeyEvent*>(key_event)->getDownTime();
+}
+
+int64_t key_event_get_event_time(const input_event_t* key_event) {
+    return reinterpret_cast<const KeyEvent*>(key_event)->getEventTime();
+}
+
+int32_t motion_event_get_action(const input_event_t* motion_event) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getAction();
+}
+
+int32_t motion_event_get_meta_state(const input_event_t* motion_event) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getMetaState();
+}
+
+int32_t motion_event_get_edge_flags(const input_event_t* motion_event) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getEdgeFlags();
+}
+
+int64_t motion_event_get_down_time(const input_event_t* motion_event) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getDownTime();
+}
+
+int64_t motion_event_get_event_time(const input_event_t* motion_event) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getEventTime();
+}
+
+float motion_event_get_x_precision(const input_event_t* motion_event) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getXPrecision();
+}
+
+float motion_event_get_y_precision(const input_event_t* motion_event) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getYPrecision();
+}
+
+size_t motion_event_get_pointer_count(const input_event_t* motion_event) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getPointerCount();
+}
+
+int32_t motion_event_get_pointer_id(const input_event_t* motion_event, size_t pointer_index) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getPointerId(pointer_index);
+}
+
+float motion_event_get_raw_x(const input_event_t* motion_event) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getRawX();
+}
+
+float motion_event_get_raw_y(const input_event_t* motion_event) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getRawY();
+}
+
+float motion_event_get_x(const input_event_t* motion_event, size_t pointer_index) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getX(pointer_index);
+}
+
+float motion_event_get_y(const input_event_t* motion_event, size_t pointer_index) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getY(pointer_index);
+}
+
+float motion_event_get_pressure(const input_event_t* motion_event, size_t pointer_index) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getPressure(pointer_index);
+}
+
+float motion_event_get_size(const input_event_t* motion_event, size_t pointer_index) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getSize(pointer_index);
+}
+
+size_t motion_event_get_history_size(const input_event_t* motion_event) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getHistorySize();
+}
+
+int64_t motion_event_get_historical_event_time(input_event_t* motion_event,
+        size_t history_index) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalEventTime(
+            history_index);
+}
+
+float motion_event_get_historical_x(input_event_t* motion_event, size_t pointer_index,
+        size_t history_index) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalX(
+            pointer_index, history_index);
+}
+
+float motion_event_get_historical_y(input_event_t* motion_event, size_t pointer_index,
+        size_t history_index) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalY(
+            pointer_index, history_index);
+}
+
+float motion_event_get_historical_pressure(input_event_t* motion_event, size_t pointer_index,
+        size_t history_index) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalPressure(
+            pointer_index, history_index);
+}
+
+float motion_event_get_historical_size(input_event_t* motion_event, size_t pointer_index,
+        size_t history_index) {
+    return reinterpret_cast<const MotionEvent*>(motion_event)->getHistoricalSize(
+            pointer_index, history_index);
+}
diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp
new file mode 100644
index 0000000..8e907da
--- /dev/null
+++ b/libs/ui/InputDispatcher.cpp
@@ -0,0 +1,1315 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+// The input dispatcher.
+//
+#define LOG_TAG "InputDispatcher"
+
+//#define LOG_NDEBUG 0
+
+// Log detailed debug messages about each inbound event notification to the dispatcher.
+#define DEBUG_INBOUND_EVENT_DETAILS 1
+
+// Log detailed debug messages about each outbound event processed by the dispatcher.
+#define DEBUG_OUTBOUND_EVENT_DETAILS 1
+
+// Log debug messages about batching.
+#define DEBUG_BATCHING 1
+
+// Log debug messages about the dispatch cycle.
+#define DEBUG_DISPATCH_CYCLE 1
+
+// Log debug messages about performance statistics.
+#define DEBUG_PERFORMANCE_STATISTICS 1
+
+#include <cutils/log.h>
+#include <ui/InputDispatcher.h>
+
+#include <stddef.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <limits.h>
+#include <poll.h>
+
+namespace android {
+
+// TODO, this needs to be somewhere else, perhaps in the policy
+static inline bool isMovementKey(int32_t keyCode) {
+    return keyCode == KEYCODE_DPAD_UP
+            || keyCode == KEYCODE_DPAD_DOWN
+            || keyCode == KEYCODE_DPAD_LEFT
+            || keyCode == KEYCODE_DPAD_RIGHT;
+}
+
+// --- InputDispatcher ---
+
+InputDispatcher::InputDispatcher(const sp<InputDispatchPolicyInterface>& policy) :
+    mPolicy(policy) {
+    mPollLoop = new PollLoop();
+
+    mInboundQueue.head.refCount = -1;
+    mInboundQueue.head.type = EventEntry::TYPE_SENTINEL;
+    mInboundQueue.head.eventTime = LONG_LONG_MIN;
+
+    mInboundQueue.tail.refCount = -1;
+    mInboundQueue.tail.type = EventEntry::TYPE_SENTINEL;
+    mInboundQueue.tail.eventTime = LONG_LONG_MAX;
+
+    mKeyRepeatState.lastKeyEntry = NULL;
+}
+
+InputDispatcher::~InputDispatcher() {
+    resetKeyRepeatLocked();
+
+    while (mConnectionsByReceiveFd.size() != 0) {
+        unregisterInputChannel(mConnectionsByReceiveFd.valueAt(0)->inputChannel);
+    }
+
+    for (EventEntry* entry = mInboundQueue.head.next; entry != & mInboundQueue.tail; ) {
+        EventEntry* next = entry->next;
+        mAllocator.releaseEventEntry(next);
+        entry = next;
+    }
+}
+
+void InputDispatcher::dispatchOnce() {
+    bool allowKeyRepeat = mPolicy->allowKeyRepeat();
+
+    nsecs_t currentTime;
+    nsecs_t nextWakeupTime = LONG_LONG_MAX;
+    { // acquire lock
+        AutoMutex _l(mLock);
+        currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+        // 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
+        // out of sleep.
+        // XXX we should handle resetting input state coming out of sleep more generally elsewhere
+        if (! allowKeyRepeat) {
+            resetKeyRepeatLocked();
+        }
+
+        // Process timeouts for all connections and determine if there are any synchronous
+        // event dispatches pending.
+        bool hasPendingSyncTarget = false;
+        for (size_t i = 0; i < mActiveConnections.size(); ) {
+            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;
+        }
+
+        // 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) {
+            if (mInboundQueue.isEmpty()) {
+                if (mKeyRepeatState.lastKeyEntry) {
+                    if (currentTime >= mKeyRepeatState.nextRepeatTime) {
+                        processKeyRepeatLocked(currentTime);
+                        return; // dispatched once
+                    } else {
+                        if (mKeyRepeatState.nextRepeatTime < nextWakeupTime) {
+                            nextWakeupTime = mKeyRepeatState.nextRepeatTime;
+                        }
+                    }
+                }
+            } else {
+                // Inbound queue has at least one entry.  Dequeue it and begin dispatching.
+                // Note that we do not hold the lock for this process because dispatching may
+                // involve making many callbacks.
+                EventEntry* entry = mInboundQueue.dequeueAtHead();
+
+                switch (entry->type) {
+                case EventEntry::TYPE_CONFIGURATION_CHANGED: {
+                    ConfigurationChangedEntry* typedEntry =
+                            static_cast<ConfigurationChangedEntry*>(entry);
+                    processConfigurationChangedLocked(currentTime, typedEntry);
+                    mAllocator.releaseConfigurationChangedEntry(typedEntry);
+                    break;
+                }
+
+                case EventEntry::TYPE_KEY: {
+                    KeyEntry* typedEntry = static_cast<KeyEntry*>(entry);
+                    processKeyLocked(currentTime, typedEntry);
+                    mAllocator.releaseKeyEntry(typedEntry);
+                    break;
+                }
+
+                case EventEntry::TYPE_MOTION: {
+                    MotionEntry* typedEntry = static_cast<MotionEntry*>(entry);
+                    processMotionLocked(currentTime, typedEntry);
+                    mAllocator.releaseMotionEntry(typedEntry);
+                    break;
+                }
+
+                default:
+                    assert(false);
+                    break;
+                }
+                return; // dispatched once
+            }
+        }
+    } // release lock
+
+    // Wait for callback or timeout or wake.
+    nsecs_t timeout = nanoseconds_to_milliseconds(nextWakeupTime - currentTime);
+    int32_t timeoutMillis = timeout > INT_MAX ? -1 : timeout > 0 ? int32_t(timeout) : 0;
+    mPollLoop->pollOnce(timeoutMillis);
+}
+
+void InputDispatcher::processConfigurationChangedLocked(nsecs_t currentTime,
+        ConfigurationChangedEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+    LOGD("processConfigurationChanged - eventTime=%lld, touchScreenConfig=%d, "
+            "keyboardConfig=%d, navigationConfig=%d", entry->eventTime,
+            entry->touchScreenConfig, entry->keyboardConfig, entry->navigationConfig);
+#endif
+
+    mPolicy->notifyConfigurationChanged(entry->eventTime, entry->touchScreenConfig,
+            entry->keyboardConfig, entry->navigationConfig);
+}
+
+void InputDispatcher::processKeyLocked(nsecs_t currentTime, KeyEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+    LOGD("processKey - eventTime=%lld, deviceId=0x%x, nature=0x%x, policyFlags=0x%x, action=0x%x, "
+            "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld",
+            entry->eventTime, entry->deviceId, entry->nature, entry->policyFlags, entry->action,
+            entry->flags, entry->keyCode, entry->scanCode, entry->metaState,
+            entry->downTime);
+#endif
+
+    // TODO: Poke user activity.
+
+    if (entry->action == KEY_EVENT_ACTION_DOWN) {
+        if (mKeyRepeatState.lastKeyEntry
+                && mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) {
+            // We have seen two identical key downs in a row which indicates that the device
+            // driver is automatically generating key repeats itself.  We take note of the
+            // repeat here, but we disable our own next key repeat timer since it is clear that
+            // we will not need to synthesize key repeats ourselves.
+            entry->repeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1;
+            resetKeyRepeatLocked();
+            mKeyRepeatState.nextRepeatTime = LONG_LONG_MAX; // don't generate repeats ourselves
+        } else {
+            // Not a repeat.  Save key down state in case we do see a repeat later.
+            resetKeyRepeatLocked();
+            mKeyRepeatState.nextRepeatTime = entry->eventTime + mPolicy->getKeyRepeatTimeout();
+        }
+        mKeyRepeatState.lastKeyEntry = entry;
+        entry->refCount += 1;
+    } else {
+        resetKeyRepeatLocked();
+    }
+
+    identifyInputTargetsAndDispatchKeyLocked(currentTime, entry);
+}
+
+void InputDispatcher::processKeyRepeatLocked(nsecs_t currentTime) {
+    // TODO Old WindowManagerServer code sniffs the input queue for following key up
+    //      events and drops the repeat if one is found.  We should do something similar.
+    //      One good place to do it is in notifyKey as soon as the key up enters the
+    //      inbound event queue.
+
+    // Synthesize a key repeat after the repeat timeout expired.
+    // We reuse the previous key entry if otherwise unreferenced.
+    KeyEntry* entry = mKeyRepeatState.lastKeyEntry;
+    if (entry->refCount == 1) {
+        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;
+
+        mKeyRepeatState.lastKeyEntry = newEntry;
+        mAllocator.releaseKeyEntry(entry);
+
+        entry = newEntry;
+    }
+    entry->eventTime = currentTime;
+    entry->downTime = currentTime;
+    entry->policyFlags = 0;
+
+    mKeyRepeatState.nextRepeatTime = currentTime + mPolicy->getKeyRepeatTimeout();
+
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+    LOGD("processKeyRepeat - eventTime=%lld, deviceId=0x%x, nature=0x%x, policyFlags=0x%x, "
+            "action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, "
+            "repeatCount=%d, downTime=%lld",
+            entry->eventTime, entry->deviceId, entry->nature, entry->policyFlags,
+            entry->action, entry->flags, entry->keyCode, entry->scanCode, entry->metaState,
+            entry->repeatCount, entry->downTime);
+#endif
+
+    identifyInputTargetsAndDispatchKeyLocked(currentTime, entry);
+}
+
+void InputDispatcher::processMotionLocked(nsecs_t currentTime, MotionEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+    LOGD("processMotion - eventTime=%lld, deviceId=0x%x, nature=0x%x, policyFlags=0x%x, action=0x%x, "
+            "metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%lld",
+            entry->eventTime, entry->deviceId, entry->nature, entry->policyFlags, entry->action,
+            entry->metaState, entry->edgeFlags, entry->xPrecision, entry->yPrecision,
+            entry->downTime);
+
+    // Print the most recent sample that we have available, this may change due to batching.
+    size_t sampleCount = 1;
+    MotionSample* sample = & entry->firstSample;
+    for (; sample->next != NULL; sample = sample->next) {
+        sampleCount += 1;
+    }
+    for (uint32_t i = 0; i < entry->pointerCount; i++) {
+        LOGD("  Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f",
+                i, entry->pointerIds[i],
+                sample->pointerCoords[i].x,
+                sample->pointerCoords[i].y,
+                sample->pointerCoords[i].pressure,
+                sample->pointerCoords[i].size);
+    }
+
+    // Keep in mind that due to batching, it is possible for the number of samples actually
+    // dispatched to change before the application finally consumed them.
+    if (entry->action == MOTION_EVENT_ACTION_MOVE) {
+        LOGD("  ... Total movement samples currently batched %d ...", sampleCount);
+    }
+#endif
+
+    identifyInputTargetsAndDispatchMotionLocked(currentTime, entry);
+}
+
+void InputDispatcher::identifyInputTargetsAndDispatchKeyLocked(
+        nsecs_t currentTime, KeyEntry* entry) {
+#if DEBUG_DISPATCH_CYCLE
+    LOGD("identifyInputTargetsAndDispatchKey");
+#endif
+
+    mReusableKeyEvent.initialize(entry->deviceId, entry->nature, entry->action, entry->flags,
+            entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount,
+            entry->downTime, entry->eventTime);
+
+    mCurrentInputTargets.clear();
+    mPolicy->getKeyEventTargets(& mReusableKeyEvent, entry->policyFlags,
+            mCurrentInputTargets);
+
+    dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
+}
+
+void InputDispatcher::identifyInputTargetsAndDispatchMotionLocked(
+        nsecs_t currentTime, MotionEntry* entry) {
+#if DEBUG_DISPATCH_CYCLE
+    LOGD("identifyInputTargetsAndDispatchMotion");
+#endif
+
+    mReusableMotionEvent.initialize(entry->deviceId, entry->nature, entry->action,
+            entry->edgeFlags, entry->metaState,
+            entry->firstSample.pointerCoords[0].x, entry->firstSample.pointerCoords[0].y,
+            entry->xPrecision, entry->yPrecision,
+            entry->downTime, entry->eventTime, entry->pointerCount, entry->pointerIds,
+            entry->firstSample.pointerCoords);
+
+    mCurrentInputTargets.clear();
+    mPolicy->getMotionEventTargets(& mReusableMotionEvent, entry->policyFlags,
+            mCurrentInputTargets);
+
+    dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
+}
+
+void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime,
+        EventEntry* eventEntry, bool resumeWithAppendedMotionSample) {
+#if DEBUG_DISPATCH_CYCLE
+    LOGD("dispatchEventToCurrentInputTargets, "
+            "resumeWithAppendedMotionSample=%s",
+            resumeWithAppendedMotionSample ? "true" : "false");
+#endif
+
+    for (size_t i = 0; i < mCurrentInputTargets.size(); i++) {
+        const InputTarget& inputTarget = mCurrentInputTargets.itemAt(i);
+
+        ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(
+                inputTarget.inputChannel->getReceivePipeFd());
+        if (connectionIndex >= 0) {
+            sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+            prepareDispatchCycleLocked(currentTime, connection.get(), eventEntry, & inputTarget,
+                    resumeWithAppendedMotionSample);
+        } else {
+            LOGW("Framework requested delivery of an input event to channel '%s' but it "
+                    "is not registered with the input dispatcher.",
+                    inputTarget.inputChannel->getName().string());
+        }
+    }
+}
+
+void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, Connection* connection,
+        EventEntry* eventEntry, const InputTarget* inputTarget,
+        bool resumeWithAppendedMotionSample) {
+#if DEBUG_DISPATCH_CYCLE
+    LOGD("channel '%s' ~ prepareDispatchCycle, flags=%d, timeout=%lldns, "
+            "xOffset=%f, yOffset=%f, resumeWithAppendedMotionSample=%s",
+            connection->getInputChannelName(), inputTarget->flags, inputTarget->timeout,
+            inputTarget->xOffset, inputTarget->yOffset,
+            resumeWithAppendedMotionSample ? "true" : "false");
+#endif
+
+    // Skip this event if the connection status is not normal.
+    // We don't want to queue outbound events at all if the connection is broken or
+    // not responding.
+    if (connection->status != Connection::STATUS_NORMAL) {
+        LOGV("channel '%s' ~ Dropping event because the channel status is %s",
+                connection->status == Connection::STATUS_BROKEN ? "BROKEN" : "NOT RESPONDING");
+        return;
+    }
+
+    // Resume the dispatch cycle with a freshly appended motion sample.
+    // First we check that the last dispatch entry in the outbound queue is for the same
+    // motion event to which we appended the motion sample.  If we find such a dispatch
+    // entry, and if it is currently in progress then we try to stream the new sample.
+    bool wasEmpty = connection->outboundQueue.isEmpty();
+
+    if (! wasEmpty && resumeWithAppendedMotionSample) {
+        DispatchEntry* motionEventDispatchEntry =
+                connection->findQueuedDispatchEntryForEvent(eventEntry);
+        if (motionEventDispatchEntry) {
+            // If the dispatch entry is not in progress, then we must be busy dispatching an
+            // earlier event.  Not a problem, the motion event is on the outbound queue and will
+            // be dispatched later.
+            if (! motionEventDispatchEntry->inProgress) {
+#if DEBUG_BATCHING
+                LOGD("channel '%s' ~ Not streaming because the motion event has "
+                        "not yet been dispatched.  "
+                        "(Waiting for earlier events to be consumed.)",
+                        connection->getInputChannelName());
+#endif
+                return;
+            }
+
+            // If the dispatch entry is in progress but it already has a tail of pending
+            // motion samples, then it must mean that the shared memory buffer filled up.
+            // Not a problem, when this dispatch cycle is finished, we will eventually start
+            // a new dispatch cycle to process the tail and that tail includes the newly
+            // appended motion sample.
+            if (motionEventDispatchEntry->tailMotionSample) {
+#if DEBUG_BATCHING
+                LOGD("channel '%s' ~ Not streaming because no new samples can "
+                        "be appended to the motion event in this dispatch cycle.  "
+                        "(Waiting for next dispatch cycle to start.)",
+                        connection->getInputChannelName());
+#endif
+                return;
+            }
+
+            // The dispatch entry is in progress and is still potentially open for streaming.
+            // Try to stream the new motion sample.  This might fail if the consumer has already
+            // consumed the motion event (or if the channel is broken).
+            MotionSample* appendedMotionSample = static_cast<MotionEntry*>(eventEntry)->lastSample;
+            status_t status = connection->inputPublisher.appendMotionSample(
+                    appendedMotionSample->eventTime, appendedMotionSample->pointerCoords);
+            if (status == OK) {
+#if DEBUG_BATCHING
+                LOGD("channel '%s' ~ Successfully streamed new motion sample.",
+                        connection->getInputChannelName());
+#endif
+                return;
+            }
+
+#if DEBUG_BATCHING
+            if (status == NO_MEMORY) {
+                LOGD("channel '%s' ~ Could not append motion sample to currently "
+                        "dispatched move event because the shared memory buffer is full.  "
+                        "(Waiting for next dispatch cycle to start.)",
+                        connection->getInputChannelName());
+            } else if (status == status_t(FAILED_TRANSACTION)) {
+                LOGD("channel '%s' ~ Could not append motion sample to currently "
+                        "dispatchedmove event because the event has already been consumed.  "
+                        "(Waiting for next dispatch cycle to start.)",
+                        connection->getInputChannelName());
+            } else {
+                LOGD("channel '%s' ~ Could not append motion sample to currently "
+                        "dispatched move event due to an error, status=%d.  "
+                        "(Waiting for next dispatch cycle to start.)",
+                        connection->getInputChannelName(), status);
+            }
+#endif
+            // Failed to stream.  Start a new tail of pending motion samples to dispatch
+            // in the next cycle.
+            motionEventDispatchEntry->tailMotionSample = appendedMotionSample;
+            return;
+        }
+    }
+
+    // This is a new event.
+    // Enqueue a new dispatch entry onto the outbound queue for this connection.
+    DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry); // increments ref
+    dispatchEntry->targetFlags = inputTarget->flags;
+    dispatchEntry->xOffset = inputTarget->xOffset;
+    dispatchEntry->yOffset = inputTarget->yOffset;
+    dispatchEntry->timeout = inputTarget->timeout;
+    dispatchEntry->inProgress = false;
+    dispatchEntry->headMotionSample = NULL;
+    dispatchEntry->tailMotionSample = NULL;
+
+    // Handle the case where we could not stream a new motion sample because the consumer has
+    // already consumed the motion event (otherwise the corresponding dispatch entry would
+    // still be in the outbound queue for this connection).  We set the head motion sample
+    // to the list starting with the newly appended motion sample.
+    if (resumeWithAppendedMotionSample) {
+#if DEBUG_BATCHING
+        LOGD("channel '%s' ~ Preparing a new dispatch cycle for additional motion samples "
+                "that cannot be streamed because the motion event has already been consumed.",
+                connection->getInputChannelName());
+#endif
+        MotionSample* appendedMotionSample = static_cast<MotionEntry*>(eventEntry)->lastSample;
+        dispatchEntry->headMotionSample = appendedMotionSample;
+    }
+
+    // Enqueue the dispatch entry.
+    connection->outboundQueue.enqueueAtTail(dispatchEntry);
+
+    // If the outbound queue was previously empty, start the dispatch cycle going.
+    if (wasEmpty) {
+        activateConnectionLocked(connection);
+        startDispatchCycleLocked(currentTime, connection);
+    }
+}
+
+void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, Connection* connection) {
+#if DEBUG_DISPATCH_CYCLE
+    LOGD("channel '%s' ~ startDispatchCycle",
+            connection->getInputChannelName());
+#endif
+
+    assert(connection->status == Connection::STATUS_NORMAL);
+    assert(! connection->outboundQueue.isEmpty());
+
+    DispatchEntry* dispatchEntry = connection->outboundQueue.head.next;
+    assert(! dispatchEntry->inProgress);
+
+    // TODO throttle successive ACTION_MOVE motion events for the same device
+    //      possible implementation could set a brief poll timeout here and resume starting the
+    //      dispatch cycle when elapsed
+
+    // Publish the event.
+    status_t status;
+    switch (dispatchEntry->eventEntry->type) {
+    case EventEntry::TYPE_KEY: {
+        KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry);
+
+        // Apply target flags.
+        int32_t action = keyEntry->action;
+        int32_t flags = keyEntry->flags;
+        if (dispatchEntry->targetFlags & InputTarget::FLAG_CANCEL) {
+            flags |= KEY_EVENT_FLAG_CANCELED;
+        }
+
+        // Publish the key event.
+        status = connection->inputPublisher.publishKeyEvent(keyEntry->deviceId, keyEntry->nature,
+                action, flags, keyEntry->keyCode, keyEntry->scanCode,
+                keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
+                keyEntry->eventTime);
+
+        if (status) {
+            LOGE("channel '%s' ~ Could not publish key event, "
+                    "status=%d", connection->getInputChannelName(), status);
+            abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
+            return;
+        }
+        break;
+    }
+
+    case EventEntry::TYPE_MOTION: {
+        MotionEntry* motionEntry = static_cast<MotionEntry*>(dispatchEntry->eventEntry);
+
+        // Apply target flags.
+        int32_t action = motionEntry->action;
+        if (dispatchEntry->targetFlags & InputTarget::FLAG_OUTSIDE) {
+            action = MOTION_EVENT_ACTION_OUTSIDE;
+        }
+        if (dispatchEntry->targetFlags & InputTarget::FLAG_CANCEL) {
+            action = MOTION_EVENT_ACTION_CANCEL;
+        }
+
+        // If headMotionSample is non-NULL, then it points to the first new sample that we
+        // were unable to dispatch during the previous cycle so we resume dispatching from
+        // that point in the list of motion samples.
+        // Otherwise, we just start from the first sample of the motion event.
+        MotionSample* firstMotionSample = dispatchEntry->headMotionSample;
+        if (! firstMotionSample) {
+            firstMotionSample = & motionEntry->firstSample;
+        }
+
+        // Publish the motion event and the first motion sample.
+        status = connection->inputPublisher.publishMotionEvent(motionEntry->deviceId,
+                motionEntry->nature, action, motionEntry->edgeFlags, motionEntry->metaState,
+                dispatchEntry->xOffset, dispatchEntry->yOffset,
+                motionEntry->xPrecision, motionEntry->yPrecision,
+                motionEntry->downTime, firstMotionSample->eventTime,
+                motionEntry->pointerCount, motionEntry->pointerIds,
+                firstMotionSample->pointerCoords);
+
+        if (status) {
+            LOGE("channel '%s' ~ Could not publish motion event, "
+                    "status=%d", connection->getInputChannelName(), status);
+            abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
+            return;
+        }
+
+        // Append additional motion samples.
+        MotionSample* nextMotionSample = firstMotionSample->next;
+        for (; nextMotionSample != NULL; nextMotionSample = nextMotionSample->next) {
+            status = connection->inputPublisher.appendMotionSample(
+                    nextMotionSample->eventTime, nextMotionSample->pointerCoords);
+            if (status == NO_MEMORY) {
+#if DEBUG_DISPATCH_CYCLE
+                    LOGD("channel '%s' ~ Shared memory buffer full.  Some motion samples will "
+                            "be sent in the next dispatch cycle.",
+                            connection->getInputChannelName());
+#endif
+                break;
+            }
+            if (status != OK) {
+                LOGE("channel '%s' ~ Could not append motion sample "
+                        "for a reason other than out of memory, status=%d",
+                        connection->getInputChannelName(), status);
+                abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
+                return;
+            }
+        }
+
+        // Remember the next motion sample that we could not dispatch, in case we ran out
+        // of space in the shared memory buffer.
+        dispatchEntry->tailMotionSample = nextMotionSample;
+        break;
+    }
+
+    default: {
+        assert(false);
+    }
+    }
+
+    // Send the dispatch signal.
+    status = connection->inputPublisher.sendDispatchSignal();
+    if (status) {
+        LOGE("channel '%s' ~ Could not send dispatch signal, status=%d",
+                connection->getInputChannelName(), status);
+        abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
+        return;
+    }
+
+    // Record information about the newly started dispatch cycle.
+    dispatchEntry->inProgress = true;
+
+    connection->lastEventTime = dispatchEntry->eventEntry->eventTime;
+    connection->lastDispatchTime = currentTime;
+
+    nsecs_t timeout = dispatchEntry->timeout;
+    connection->nextTimeoutTime = (timeout >= 0) ? currentTime + timeout : LONG_LONG_MAX;
+
+    // Notify other system components.
+    onDispatchCycleStartedLocked(currentTime, connection);
+}
+
+void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, Connection* connection) {
+#if DEBUG_DISPATCH_CYCLE
+    LOGD("channel '%s' ~ finishDispatchCycle: %01.1fms since event, "
+            "%01.1fms since dispatch",
+            connection->getInputChannelName(),
+            connection->getEventLatencyMillis(currentTime),
+            connection->getDispatchLatencyMillis(currentTime));
+#endif
+
+    if (connection->status == Connection::STATUS_BROKEN) {
+        return;
+    }
+
+    // Clear the pending timeout.
+    connection->nextTimeoutTime = LONG_LONG_MAX;
+
+    if (connection->status == Connection::STATUS_NOT_RESPONDING) {
+        // Recovering from an ANR.
+        connection->status = Connection::STATUS_NORMAL;
+
+        // Notify other system components.
+        onDispatchCycleFinishedLocked(currentTime, connection, true /*recoveredFromANR*/);
+    } else {
+        // Normal finish.  Not much to do here.
+
+        // Notify other system components.
+        onDispatchCycleFinishedLocked(currentTime, connection, false /*recoveredFromANR*/);
+    }
+
+    // Reset the publisher since the event has been consumed.
+    // We do this now so that the publisher can release some of its internal resources
+    // while waiting for the next dispatch cycle to begin.
+    status_t status = connection->inputPublisher.reset();
+    if (status) {
+        LOGE("channel '%s' ~ Could not reset publisher, status=%d",
+                connection->getInputChannelName(), status);
+        abortDispatchCycleLocked(currentTime, connection, true /*broken*/);
+        return;
+    }
+
+    // Start the next dispatch cycle for this connection.
+    while (! connection->outboundQueue.isEmpty()) {
+        DispatchEntry* dispatchEntry = connection->outboundQueue.head.next;
+        if (dispatchEntry->inProgress) {
+             // Finish or resume current event in progress.
+            if (dispatchEntry->tailMotionSample) {
+                // We have a tail of undispatched motion samples.
+                // Reuse the same DispatchEntry and start a new cycle.
+                dispatchEntry->inProgress = false;
+                dispatchEntry->headMotionSample = dispatchEntry->tailMotionSample;
+                dispatchEntry->tailMotionSample = NULL;
+                startDispatchCycleLocked(currentTime, connection);
+                return;
+            }
+            // Finished.
+            connection->outboundQueue.dequeueAtHead();
+            mAllocator.releaseDispatchEntry(dispatchEntry);
+        } else {
+            // If the head is not in progress, then we must have already dequeued the in
+            // progress event, which means we actually aborted it (due to ANR).
+            // So just start the next event for this connection.
+            startDispatchCycleLocked(currentTime, connection);
+            return;
+        }
+    }
+
+    // Outbound queue is empty, deactivate the connection.
+    deactivateConnectionLocked(connection);
+}
+
+bool InputDispatcher::timeoutDispatchCycleLocked(nsecs_t currentTime, Connection* connection) {
+#if DEBUG_DISPATCH_CYCLE
+    LOGD("channel '%s' ~ timeoutDispatchCycle",
+            connection->getInputChannelName());
+#endif
+
+    if (connection->status != Connection::STATUS_NORMAL) {
+        return false;
+    }
+
+    // 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.
+    onDispatchCycleANRLocked(currentTime, connection);
+    return deactivated;
+}
+
+bool InputDispatcher::abortDispatchCycleLocked(nsecs_t currentTime, Connection* connection,
+        bool broken) {
+#if DEBUG_DISPATCH_CYCLE
+    LOGD("channel '%s' ~ abortDispatchCycle, broken=%s",
+            connection->getInputChannelName(), broken ? "true" : "false");
+#endif
+
+    if (connection->status == Connection::STATUS_BROKEN) {
+        return false;
+    }
+
+    // Clear the pending timeout.
+    connection->nextTimeoutTime = LONG_LONG_MAX;
+
+    // Clear the outbound queue.
+    while (! connection->outboundQueue.isEmpty()) {
+        DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead();
+        mAllocator.releaseDispatchEntry(dispatchEntry);
+    }
+
+    // Outbound queue is empty, deactivate the connection.
+    deactivateConnectionLocked(connection);
+
+    // Handle the case where the connection appears to be unrecoverably broken.
+    if (broken) {
+        connection->status = Connection::STATUS_BROKEN;
+
+        // Notify other system components.
+        onDispatchCycleBrokenLocked(currentTime, connection);
+    }
+    return true; /*deactivated*/
+}
+
+bool InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data) {
+    InputDispatcher* d = static_cast<InputDispatcher*>(data);
+
+    { // acquire lock
+        AutoMutex _l(d->mLock);
+
+        ssize_t connectionIndex = d->mConnectionsByReceiveFd.indexOfKey(receiveFd);
+        if (connectionIndex < 0) {
+            LOGE("Received spurious receive callback for unknown input channel.  "
+                    "fd=%d, events=0x%x", receiveFd, events);
+            return false; // remove the callback
+        }
+
+        nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+        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*/);
+            return false; // remove the callback
+        }
+
+        if (! (events & POLLIN)) {
+            LOGW("channel '%s' ~ Received spurious callback for unhandled poll event.  "
+                    "events=0x%x", connection->getInputChannelName(), events);
+            return true;
+        }
+
+        status_t status = connection->inputPublisher.receiveFinishedSignal();
+        if (status) {
+            LOGE("channel '%s' ~ Failed to receive finished signal.  status=%d",
+                    connection->getInputChannelName(), status);
+            d->abortDispatchCycleLocked(currentTime, connection.get(), true /*broken*/);
+            return false; // remove the callback
+        }
+
+        d->finishDispatchCycleLocked(currentTime, connection.get());
+        return true;
+    } // release lock
+}
+
+void InputDispatcher::notifyConfigurationChanged(nsecs_t eventTime, int32_t touchScreenConfig,
+        int32_t keyboardConfig, int32_t navigationConfig) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+    LOGD("notifyConfigurationChanged - eventTime=%lld, touchScreenConfig=%d, "
+            "keyboardConfig=%d, navigationConfig=%d", eventTime,
+            touchScreenConfig, keyboardConfig, navigationConfig);
+#endif
+
+    bool wasEmpty;
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        ConfigurationChangedEntry* newEntry = mAllocator.obtainConfigurationChangedEntry();
+        newEntry->eventTime = eventTime;
+        newEntry->touchScreenConfig = touchScreenConfig;
+        newEntry->keyboardConfig = keyboardConfig;
+        newEntry->navigationConfig = navigationConfig;
+
+        wasEmpty = mInboundQueue.isEmpty();
+        mInboundQueue.enqueueAtTail(newEntry);
+    } // release lock
+
+    if (wasEmpty) {
+        mPollLoop->wake();
+    }
+}
+
+void InputDispatcher::notifyLidSwitchChanged(nsecs_t eventTime, bool lidOpen) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+    LOGD("notifyLidSwitchChanged - eventTime=%lld, open=%s", eventTime,
+            lidOpen ? "true" : "false");
+#endif
+
+    // Send lid switch notification immediately and synchronously.
+    mPolicy->notifyLidSwitchChanged(eventTime, lidOpen);
+}
+
+void InputDispatcher::notifyAppSwitchComing(nsecs_t eventTime) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+    LOGD("notifyAppSwitchComing - eventTime=%lld", eventTime);
+#endif
+
+    // Remove movement keys from the queue from most recent to least recent, stopping at the
+    // first non-movement key.
+    // TODO: Include a detailed description of why we do this...
+
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        for (EventEntry* entry = mInboundQueue.tail.prev; entry != & mInboundQueue.head; ) {
+            EventEntry* prev = entry->prev;
+
+            if (entry->type == EventEntry::TYPE_KEY) {
+                KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);
+                if (isMovementKey(keyEntry->keyCode)) {
+                    LOGV("Dropping movement key during app switch: keyCode=%d, action=%d",
+                            keyEntry->keyCode, keyEntry->action);
+                    mInboundQueue.dequeue(keyEntry);
+                    mAllocator.releaseKeyEntry(keyEntry);
+                } else {
+                    // stop at last non-movement key
+                    break;
+                }
+            }
+
+            entry = prev;
+        }
+    } // release lock
+}
+
+void InputDispatcher::notifyKey(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, nsecs_t downTime) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+    LOGD("notifyKey - eventTime=%lld, deviceId=0x%x, nature=0x%x, policyFlags=0x%x, action=0x%x, "
+            "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld",
+            eventTime, deviceId, nature, policyFlags, action, flags,
+            keyCode, scanCode, metaState, downTime);
+#endif
+
+    bool wasEmpty;
+    { // 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;
+
+        wasEmpty = mInboundQueue.isEmpty();
+        mInboundQueue.enqueueAtTail(newEntry);
+    } // release lock
+
+    if (wasEmpty) {
+        mPollLoop->wake();
+    }
+}
+
+void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t nature,
+        uint32_t policyFlags, int32_t action, int32_t metaState, int32_t edgeFlags,
+        uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords,
+        float xPrecision, float yPrecision, nsecs_t downTime) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+    LOGD("notifyMotion - eventTime=%lld, deviceId=0x%x, nature=0x%x, policyFlags=0x%x, "
+            "action=0x%x, metaState=0x%x, edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, "
+            "downTime=%lld",
+            eventTime, deviceId, nature, policyFlags, action, metaState, edgeFlags,
+            xPrecision, yPrecision, downTime);
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        LOGD("  Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f",
+                i, pointerIds[i], pointerCoords[i].x, pointerCoords[i].y,
+                pointerCoords[i].pressure, pointerCoords[i].size);
+    }
+#endif
+
+    bool wasEmpty;
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        // Attempt batching and streaming of move events.
+        if (action == MOTION_EVENT_ACTION_MOVE) {
+            // BATCHING CASE
+            //
+            // Try to append a move sample to the tail of the inbound queue for this device.
+            // Give up if we encounter a non-move motion event for this device since that
+            // means we cannot append any new samples until a new motion event has started.
+            for (EventEntry* entry = mInboundQueue.tail.prev;
+                    entry != & mInboundQueue.head; entry = entry->prev) {
+                if (entry->type != EventEntry::TYPE_MOTION) {
+                    // Keep looking for motion events.
+                    continue;
+                }
+
+                MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
+                if (motionEntry->deviceId != deviceId) {
+                    // Keep looking for this device.
+                    continue;
+                }
+
+                if (motionEntry->action != MOTION_EVENT_ACTION_MOVE
+                        || motionEntry->pointerCount != pointerCount) {
+                    // Last motion event in the queue for this device is not compatible for
+                    // appending new samples.  Stop here.
+                    goto NoBatchingOrStreaming;
+                }
+
+                // The last motion event is a move and is compatible for appending.
+                // Do the batching magic and exit.
+                mAllocator.appendMotionSample(motionEntry, eventTime, pointerCount, pointerCoords);
+#if DEBUG_BATCHING
+                LOGD("Appended motion sample onto batch for most recent "
+                        "motion event for this device in the inbound queue.");
+#endif
+                return; // done
+            }
+
+            // STREAMING CASE
+            //
+            // There is no pending motion event (of any kind) for this device in the inbound queue.
+            // Search the outbound queues for a synchronously dispatched motion event for this
+            // device.  If found, then we append the new sample to that event and then try to
+            // push it out to all current targets.  It is possible that some targets will already
+            // have consumed the motion event.  This case is automatically handled by the
+            // logic in prepareDispatchCycleLocked by tracking where resumption takes place.
+            //
+            // The reason we look for a synchronously dispatched motion event is because we
+            // want to be sure that no other motion events have been dispatched since the move.
+            // It's also convenient because it means that the input targets are still valid.
+            // This code could be improved to support streaming of asynchronously dispatched
+            // motion events (which might be significantly more efficient) but it may become
+            // a little more complicated as a result.
+            //
+            // Note: This code crucially depends on the invariant that an outbound queue always
+            //       contains at most one synchronous event and it is always last (but it might
+            //       not be first!).
+            for (size_t i = 0; i < mActiveConnections.size(); i++) {
+                Connection* connection = mActiveConnections.itemAt(i);
+                if (! connection->outboundQueue.isEmpty()) {
+                    DispatchEntry* dispatchEntry = connection->outboundQueue.tail.prev;
+                    if (dispatchEntry->targetFlags & InputTarget::FLAG_SYNC) {
+                        if (dispatchEntry->eventEntry->type != EventEntry::TYPE_MOTION) {
+                            goto NoBatchingOrStreaming;
+                        }
+
+                        MotionEntry* syncedMotionEntry = static_cast<MotionEntry*>(
+                                dispatchEntry->eventEntry);
+                        if (syncedMotionEntry->action != MOTION_EVENT_ACTION_MOVE
+                                || syncedMotionEntry->deviceId != deviceId
+                                || syncedMotionEntry->pointerCount != pointerCount) {
+                            goto NoBatchingOrStreaming;
+                        }
+
+                        // Found synced move entry.  Append sample and resume dispatch.
+                        mAllocator.appendMotionSample(syncedMotionEntry, eventTime,
+                                pointerCount, 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);
+                        dispatchEventToCurrentInputTargetsLocked(currentTime, syncedMotionEntry,
+                                true /*resumeWithAppendedMotionSample*/);
+                        return; // done!
+                    }
+                }
+            }
+
+NoBatchingOrStreaming:;
+        }
+
+        // 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];
+        }
+
+        wasEmpty = mInboundQueue.isEmpty();
+        mInboundQueue.enqueueAtTail(newEntry);
+    } // release lock
+
+    if (wasEmpty) {
+        mPollLoop->wake();
+    }
+}
+
+void InputDispatcher::resetKeyRepeatLocked() {
+    if (mKeyRepeatState.lastKeyEntry) {
+        mAllocator.releaseKeyEntry(mKeyRepeatState.lastKeyEntry);
+        mKeyRepeatState.lastKeyEntry = NULL;
+    }
+}
+
+status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel) {
+    int receiveFd;
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        receiveFd = inputChannel->getReceivePipeFd();
+        if (mConnectionsByReceiveFd.indexOfKey(receiveFd) >= 0) {
+            LOGW("Attempted to register already registered input channel '%s'",
+                    inputChannel->getName().string());
+            return BAD_VALUE;
+        }
+
+        sp<Connection> connection = new Connection(inputChannel);
+        status_t status = connection->initialize();
+        if (status) {
+            LOGE("Failed to initialize input publisher for input channel '%s', status=%d",
+                    inputChannel->getName().string(), status);
+            return status;
+        }
+
+        mConnectionsByReceiveFd.add(receiveFd, connection);
+    } // release lock
+
+    mPollLoop->setCallback(receiveFd, POLLIN, handleReceiveCallback, this);
+    return OK;
+}
+
+status_t InputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel) {
+    int32_t receiveFd;
+    { // acquire lock
+        AutoMutex _l(mLock);
+
+        receiveFd = inputChannel->getReceivePipeFd();
+        ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd);
+        if (connectionIndex < 0) {
+            LOGW("Attempted to unregister already unregistered input channel '%s'",
+                    inputChannel->getName().string());
+            return BAD_VALUE;
+        }
+
+        sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+        mConnectionsByReceiveFd.removeItemsAt(connectionIndex);
+
+        connection->status = Connection::STATUS_ZOMBIE;
+
+        nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
+        abortDispatchCycleLocked(currentTime, connection.get(), true /*broken*/);
+    } // release lock
+
+    mPollLoop->removeCallback(receiveFd);
+
+    // Wake the poll loop because removing the connection may have changed the current
+    // synchronization state.
+    mPollLoop->wake();
+    return OK;
+}
+
+void InputDispatcher::activateConnectionLocked(Connection* connection) {
+    for (size_t i = 0; i < mActiveConnections.size(); i++) {
+        if (mActiveConnections.itemAt(i) == connection) {
+            return;
+        }
+    }
+    mActiveConnections.add(connection);
+}
+
+void InputDispatcher::deactivateConnectionLocked(Connection* connection) {
+    for (size_t i = 0; i < mActiveConnections.size(); i++) {
+        if (mActiveConnections.itemAt(i) == connection) {
+            mActiveConnections.removeAt(i);
+            return;
+        }
+    }
+}
+
+void InputDispatcher::onDispatchCycleStartedLocked(nsecs_t currentTime, Connection* connection) {
+}
+
+void InputDispatcher::onDispatchCycleFinishedLocked(nsecs_t currentTime,
+        Connection* connection, bool recoveredFromANR) {
+    if (recoveredFromANR) {
+        LOGI("channel '%s' ~ Recovered from ANR.  %01.1fms since event, "
+                "%01.1fms since dispatch, %01.1fms since ANR",
+                connection->getInputChannelName(),
+                connection->getEventLatencyMillis(currentTime),
+                connection->getDispatchLatencyMillis(currentTime),
+                connection->getANRLatencyMillis(currentTime));
+
+        // TODO tell framework
+    }
+}
+
+void InputDispatcher::onDispatchCycleANRLocked(nsecs_t currentTime, Connection* connection) {
+    LOGI("channel '%s' ~ Not responding!  %01.1fms since event, %01.1fms since dispatch",
+            connection->getInputChannelName(),
+            connection->getEventLatencyMillis(currentTime),
+            connection->getDispatchLatencyMillis(currentTime));
+
+    // TODO tell framework
+}
+
+void InputDispatcher::onDispatchCycleBrokenLocked(nsecs_t currentTime, Connection* connection) {
+    LOGE("channel '%s' ~ Channel is unrecoverably broken and will be disposed!",
+            connection->getInputChannelName());
+
+    // TODO tell framework
+}
+
+// --- InputDispatcher::Allocator ---
+
+InputDispatcher::Allocator::Allocator() {
+}
+
+InputDispatcher::ConfigurationChangedEntry*
+InputDispatcher::Allocator::obtainConfigurationChangedEntry() {
+    ConfigurationChangedEntry* entry = mConfigurationChangeEntryPool.alloc();
+    entry->refCount = 1;
+    entry->type = EventEntry::TYPE_CONFIGURATION_CHANGED;
+    return entry;
+}
+
+InputDispatcher::KeyEntry* InputDispatcher::Allocator::obtainKeyEntry() {
+    KeyEntry* entry = mKeyEntryPool.alloc();
+    entry->refCount = 1;
+    entry->type = EventEntry::TYPE_KEY;
+    return entry;
+}
+
+InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry() {
+    MotionEntry* entry = mMotionEntryPool.alloc();
+    entry->refCount = 1;
+    entry->type = EventEntry::TYPE_MOTION;
+    entry->firstSample.next = NULL;
+    return entry;
+}
+
+InputDispatcher::DispatchEntry* InputDispatcher::Allocator::obtainDispatchEntry(
+        EventEntry* eventEntry) {
+    DispatchEntry* entry = mDispatchEntryPool.alloc();
+    entry->eventEntry = eventEntry;
+    eventEntry->refCount += 1;
+    return entry;
+}
+
+void InputDispatcher::Allocator::releaseEventEntry(EventEntry* entry) {
+    switch (entry->type) {
+    case EventEntry::TYPE_CONFIGURATION_CHANGED:
+        releaseConfigurationChangedEntry(static_cast<ConfigurationChangedEntry*>(entry));
+        break;
+    case EventEntry::TYPE_KEY:
+        releaseKeyEntry(static_cast<KeyEntry*>(entry));
+        break;
+    case EventEntry::TYPE_MOTION:
+        releaseMotionEntry(static_cast<MotionEntry*>(entry));
+        break;
+    default:
+        assert(false);
+        break;
+    }
+}
+
+void InputDispatcher::Allocator::releaseConfigurationChangedEntry(
+        ConfigurationChangedEntry* entry) {
+    entry->refCount -= 1;
+    if (entry->refCount == 0) {
+        mConfigurationChangeEntryPool.free(entry);
+    } else {
+        assert(entry->refCount > 0);
+    }
+}
+
+void InputDispatcher::Allocator::releaseKeyEntry(KeyEntry* entry) {
+    entry->refCount -= 1;
+    if (entry->refCount == 0) {
+        mKeyEntryPool.free(entry);
+    } else {
+        assert(entry->refCount > 0);
+    }
+}
+
+void InputDispatcher::Allocator::releaseMotionEntry(MotionEntry* entry) {
+    entry->refCount -= 1;
+    if (entry->refCount == 0) {
+        freeMotionSampleList(entry->firstSample.next);
+        mMotionEntryPool.free(entry);
+    } else {
+        assert(entry->refCount > 0);
+    }
+}
+
+void InputDispatcher::Allocator::releaseDispatchEntry(DispatchEntry* entry) {
+    releaseEventEntry(entry->eventEntry);
+    mDispatchEntryPool.free(entry);
+}
+
+void InputDispatcher::Allocator::appendMotionSample(MotionEntry* motionEntry,
+        nsecs_t eventTime, int32_t pointerCount, const PointerCoords* pointerCoords) {
+    MotionSample* sample = mMotionSamplePool.alloc();
+    sample->eventTime = eventTime;
+    for (int32_t i = 0; i < pointerCount; i++) {
+        sample->pointerCoords[i] = pointerCoords[i];
+    }
+
+    sample->next = NULL;
+    motionEntry->lastSample->next = sample;
+    motionEntry->lastSample = sample;
+}
+
+void InputDispatcher::Allocator::freeMotionSample(MotionSample* sample) {
+    mMotionSamplePool.free(sample);
+}
+
+void InputDispatcher::Allocator::freeMotionSampleList(MotionSample* head) {
+    while (head) {
+        MotionSample* next = head->next;
+        mMotionSamplePool.free(head);
+        head = next;
+    }
+}
+
+// --- InputDispatcher::Connection ---
+
+InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel) :
+        status(STATUS_NORMAL), inputChannel(inputChannel), inputPublisher(inputChannel),
+        nextTimeoutTime(LONG_LONG_MAX),
+        lastEventTime(LONG_LONG_MAX), lastDispatchTime(LONG_LONG_MAX),
+        lastANRTime(LONG_LONG_MAX) {
+}
+
+InputDispatcher::Connection::~Connection() {
+}
+
+status_t InputDispatcher::Connection::initialize() {
+    return inputPublisher.initialize();
+}
+
+InputDispatcher::DispatchEntry* InputDispatcher::Connection::findQueuedDispatchEntryForEvent(
+        const EventEntry* eventEntry) const {
+    for (DispatchEntry* dispatchEntry = outboundQueue.tail.prev;
+            dispatchEntry != & outboundQueue.head; dispatchEntry = dispatchEntry->prev) {
+        if (dispatchEntry->eventEntry == eventEntry) {
+            return dispatchEntry;
+        }
+    }
+    return NULL;
+}
+
+
+// --- InputDispatcherThread ---
+
+InputDispatcherThread::InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher) :
+        Thread(/*canCallJava*/ true), mDispatcher(dispatcher) {
+}
+
+InputDispatcherThread::~InputDispatcherThread() {
+}
+
+bool InputDispatcherThread::threadLoop() {
+    mDispatcher->dispatchOnce();
+    return true;
+}
+
+} // namespace android
diff --git a/libs/ui/InputManager.cpp b/libs/ui/InputManager.cpp
new file mode 100644
index 0000000..ab354a5
--- /dev/null
+++ b/libs/ui/InputManager.cpp
@@ -0,0 +1,114 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+// The input manager.
+//
+#define LOG_TAG "InputManager"
+
+//#define LOG_NDEBUG 0
+
+#include <cutils/log.h>
+#include <ui/InputManager.h>
+#include <ui/InputReader.h>
+#include <ui/InputDispatcher.h>
+
+namespace android {
+
+InputManager::InputManager(const sp<EventHubInterface>& eventHub,
+        const sp<InputDispatchPolicyInterface>& policy) :
+        mEventHub(eventHub), mPolicy(policy) {
+    mDispatcher = new InputDispatcher(policy);
+    mReader = new InputReader(eventHub, policy, mDispatcher);
+
+    mDispatcherThread = new InputDispatcherThread(mDispatcher);
+    mReaderThread = new InputReaderThread(mReader);
+
+    configureExcludedDevices();
+}
+
+InputManager::~InputManager() {
+    stop();
+}
+
+void InputManager::configureExcludedDevices() {
+    Vector<String8> excludedDeviceNames;
+    mPolicy->getExcludedDeviceNames(excludedDeviceNames);
+
+    for (size_t i = 0; i < excludedDeviceNames.size(); i++) {
+        mEventHub->addExcludedDevice(excludedDeviceNames[i]);
+    }
+}
+
+status_t InputManager::start() {
+    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
+    if (result) {
+        LOGE("Could not start InputDispatcher thread due to error %d.", result);
+        return result;
+    }
+
+    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
+    if (result) {
+        LOGE("Could not start InputReader thread due to error %d.", result);
+
+        mDispatcherThread->requestExit();
+        return result;
+    }
+
+    return OK;
+}
+
+status_t InputManager::stop() {
+    status_t result = mReaderThread->requestExitAndWait();
+    if (result) {
+        LOGW("Could not stop InputReader thread due to error %d.", result);
+    }
+
+    result = mDispatcherThread->requestExitAndWait();
+    if (result) {
+        LOGW("Could not stop InputDispatcher thread due to error %d.", result);
+    }
+
+    return OK;
+}
+
+status_t InputManager::registerInputChannel(const sp<InputChannel>& inputChannel) {
+    return mDispatcher->registerInputChannel(inputChannel);
+}
+
+status_t InputManager::unregisterInputChannel(const sp<InputChannel>& inputChannel) {
+    return mDispatcher->unregisterInputChannel(inputChannel);
+}
+
+int32_t InputManager::getScanCodeState(int32_t deviceId, int32_t deviceClasses, int32_t scanCode)
+    const {
+    int32_t vkKeyCode, vkScanCode;
+    if (mReader->getCurrentVirtualKey(& vkKeyCode, & vkScanCode)) {
+        if (vkScanCode == scanCode) {
+            return KEY_STATE_VIRTUAL;
+        }
+    }
+
+    return mEventHub->getScanCodeState(deviceId, deviceClasses, scanCode);
+}
+
+int32_t InputManager::getKeyCodeState(int32_t deviceId, int32_t deviceClasses, int32_t keyCode)
+    const {
+    int32_t vkKeyCode, vkScanCode;
+    if (mReader->getCurrentVirtualKey(& vkKeyCode, & vkScanCode)) {
+        if (vkKeyCode == keyCode) {
+            return KEY_STATE_VIRTUAL;
+        }
+    }
+
+    return mEventHub->getKeyCodeState(deviceId, deviceClasses, keyCode);
+}
+
+int32_t InputManager::getSwitchState(int32_t deviceId, int32_t deviceClasses, int32_t sw) const {
+    return mEventHub->getSwitchState(deviceId, deviceClasses, sw);
+}
+
+bool InputManager::hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const {
+    return mEventHub->hasKeys(numCodes, keyCodes, outFlags);
+}
+
+} // namespace android
diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp
new file mode 100644
index 0000000..76f9ec9
--- /dev/null
+++ b/libs/ui/InputReader.cpp
@@ -0,0 +1,1844 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+// The input reader.
+//
+#define LOG_TAG "InputReader"
+
+//#define LOG_NDEBUG 0
+
+// Log debug messages for each raw event received from the EventHub.
+#define DEBUG_RAW_EVENTS 0
+
+// Log debug messages about touch screen filtering hacks.
+#define DEBUG_HACKS 1
+
+// Log debug messages about virtual key processing.
+#define DEBUG_VIRTUAL_KEYS 1
+
+// Log debug messages about pointers.
+#define DEBUG_POINTERS 1
+
+#include <cutils/log.h>
+#include <ui/InputReader.h>
+
+#include <stddef.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <limits.h>
+
+namespace android {
+
+// --- Static Functions ---
+
+template<typename T>
+inline static T abs(const T& value) {
+    return value < 0 ? - value : value;
+}
+
+template<typename T>
+inline static T min(const T& a, const T& b) {
+    return a < b ? a : b;
+}
+
+int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState) {
+    int32_t mask;
+    switch (keyCode) {
+    case KEYCODE_ALT_LEFT:
+        mask = META_ALT_LEFT_ON;
+        break;
+    case KEYCODE_ALT_RIGHT:
+        mask = META_ALT_RIGHT_ON;
+        break;
+    case KEYCODE_SHIFT_LEFT:
+        mask = META_SHIFT_LEFT_ON;
+        break;
+    case KEYCODE_SHIFT_RIGHT:
+        mask = META_SHIFT_RIGHT_ON;
+        break;
+    case KEYCODE_SYM:
+        mask = META_SYM_ON;
+        break;
+    default:
+        return oldMetaState;
+    }
+
+    int32_t newMetaState = down ? oldMetaState | mask : oldMetaState & ~ mask
+            & ~ (META_ALT_ON | META_SHIFT_ON);
+
+    if (newMetaState & (META_ALT_LEFT_ON | META_ALT_RIGHT_ON)) {
+        newMetaState |= META_ALT_ON;
+    }
+
+    if (newMetaState & (META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON)) {
+        newMetaState |= META_SHIFT_ON;
+    }
+
+    return newMetaState;
+}
+
+static const int32_t keyCodeRotationMap[][4] = {
+        // key codes enumerated counter-clockwise with the original (unrotated) key first
+        // no rotation,        90 degree rotation,  180 degree rotation, 270 degree rotation
+        { KEYCODE_DPAD_DOWN,   KEYCODE_DPAD_RIGHT,  KEYCODE_DPAD_UP,     KEYCODE_DPAD_LEFT },
+        { KEYCODE_DPAD_RIGHT,  KEYCODE_DPAD_UP,     KEYCODE_DPAD_LEFT,   KEYCODE_DPAD_DOWN },
+        { KEYCODE_DPAD_UP,     KEYCODE_DPAD_LEFT,   KEYCODE_DPAD_DOWN,   KEYCODE_DPAD_RIGHT },
+        { KEYCODE_DPAD_LEFT,   KEYCODE_DPAD_DOWN,   KEYCODE_DPAD_RIGHT,  KEYCODE_DPAD_UP },
+};
+static const int keyCodeRotationMapSize =
+        sizeof(keyCodeRotationMap) / sizeof(keyCodeRotationMap[0]);
+
+int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) {
+    if (orientation != InputDispatchPolicyInterface::ROTATION_0) {
+        for (int i = 0; i < keyCodeRotationMapSize; i++) {
+            if (keyCode == keyCodeRotationMap[i][0]) {
+                return keyCodeRotationMap[i][orientation];
+            }
+        }
+    }
+    return keyCode;
+}
+
+
+// --- InputDevice ---
+
+InputDevice::InputDevice(int32_t id, uint32_t classes, String8 name) :
+    id(id), classes(classes), name(name), ignored(false) {
+}
+
+void InputDevice::reset() {
+    if (isKeyboard()) {
+        keyboard.reset();
+    }
+
+    if (isTrackball()) {
+        trackball.reset();
+    }
+
+    if (isMultiTouchScreen()) {
+        multiTouchScreen.reset();
+    } else if (isSingleTouchScreen()) {
+        singleTouchScreen.reset();
+    }
+
+    if (isTouchScreen()) {
+        touchScreen.reset();
+    }
+}
+
+
+// --- InputDevice::TouchData ---
+
+void InputDevice::TouchData::copyFrom(const TouchData& other) {
+    pointerCount = other.pointerCount;
+    idBits = other.idBits;
+
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        pointers[i] = other.pointers[i];
+        idToIndex[i] = other.idToIndex[i];
+    }
+}
+
+
+// --- InputDevice::KeyboardState ---
+
+void InputDevice::KeyboardState::reset() {
+    current.metaState = META_NONE;
+    current.downTime = 0;
+}
+
+
+// --- InputDevice::TrackballState ---
+
+void InputDevice::TrackballState::reset() {
+    accumulator.clear();
+    current.down = false;
+    current.downTime = 0;
+}
+
+
+// --- InputDevice::TouchScreenState ---
+
+void InputDevice::TouchScreenState::reset() {
+    lastTouch.clear();
+    downTime = 0;
+    currentVirtualKey.down = false;
+
+    for (uint32_t i = 0; i < MAX_POINTERS; i++) {
+        averagingTouchFilter.historyStart[i] = 0;
+        averagingTouchFilter.historyEnd[i] = 0;
+    }
+
+    jumpyTouchFilter.jumpyPointsDropped = 0;
+}
+
+void InputDevice::TouchScreenState::calculatePointerIds() {
+    uint32_t currentPointerCount = currentTouch.pointerCount;
+    uint32_t lastPointerCount = lastTouch.pointerCount;
+
+    if (currentPointerCount == 0) {
+        // No pointers to assign.
+        currentTouch.idBits.clear();
+    } else if (lastPointerCount == 0) {
+        // All pointers are new.
+        currentTouch.idBits.clear();
+        for (uint32_t i = 0; i < currentPointerCount; i++) {
+            currentTouch.pointers[i].id = i;
+            currentTouch.idToIndex[i] = i;
+            currentTouch.idBits.markBit(i);
+        }
+    } else if (currentPointerCount == 1 && lastPointerCount == 1) {
+        // Only one pointer and no change in count so it must have the same id as before.
+        uint32_t id = lastTouch.pointers[0].id;
+        currentTouch.pointers[0].id = id;
+        currentTouch.idToIndex[id] = 0;
+        currentTouch.idBits.value = BitSet32::valueForBit(id);
+    } else {
+        // General case.
+        // We build a heap of squared euclidean distances between current and last pointers
+        // associated with the current and last pointer indices.  Then, we find the best
+        // match (by distance) for each current pointer.
+        struct {
+            uint32_t currentPointerIndex : 8;
+            uint32_t lastPointerIndex : 8;
+            uint64_t distance : 48; // squared distance
+        } heap[MAX_POINTERS * MAX_POINTERS];
+
+        uint32_t heapSize = 0;
+        for (uint32_t currentPointerIndex = 0; currentPointerIndex < currentPointerCount;
+                currentPointerIndex++) {
+            for (uint32_t lastPointerIndex = 0; lastPointerIndex < lastPointerCount;
+                    lastPointerIndex++) {
+                int64_t deltaX = currentTouch.pointers[currentPointerIndex].x
+                        - lastTouch.pointers[lastPointerIndex].x;
+                int64_t deltaY = currentTouch.pointers[currentPointerIndex].y
+                        - lastTouch.pointers[lastPointerIndex].y;
+
+                uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY);
+
+                // Insert new element into the heap (sift up).
+                heapSize += 1;
+                uint32_t insertionIndex = heapSize;
+                while (insertionIndex > 1) {
+                    uint32_t parentIndex = (insertionIndex - 1) / 2;
+                    if (distance < heap[parentIndex].distance) {
+                        heap[insertionIndex] = heap[parentIndex];
+                        insertionIndex = parentIndex;
+                    } else {
+                        break;
+                    }
+                }
+                heap[insertionIndex].currentPointerIndex = currentPointerIndex;
+                heap[insertionIndex].lastPointerIndex = lastPointerIndex;
+                heap[insertionIndex].distance = distance;
+            }
+        }
+
+        // Pull matches out by increasing order of distance.
+        // To avoid reassigning pointers that have already been matched, the loop keeps track
+        // of which last and current pointers have been matched using the matchedXXXBits variables.
+        // It also tracks the used pointer id bits.
+        BitSet32 matchedLastBits(0);
+        BitSet32 matchedCurrentBits(0);
+        BitSet32 usedIdBits(0);
+        bool first = true;
+        for (uint32_t i = min(currentPointerCount, lastPointerCount); i > 0; i--) {
+            for (;;) {
+                if (first) {
+                    // The first time through the loop, we just consume the root element of
+                    // the heap (the one with smalled distance).
+                    first = false;
+                } else {
+                    // Previous iterations consumed the root element of the heap.
+                    // Pop root element off of the heap (sift down).
+                    heapSize -= 1;
+                    assert(heapSize > 0);
+
+                    // Sift down to find where the element at index heapSize needs to be moved.
+                    uint32_t rootIndex = 0;
+                    for (;;) {
+                        uint32_t childIndex = rootIndex * 2 + 1;
+                        if (childIndex >= heapSize) {
+                            break;
+                        }
+
+                        if (childIndex + 1 < heapSize
+                                && heap[childIndex + 1].distance < heap[childIndex].distance) {
+                            childIndex += 1;
+                        }
+
+                        if (heap[heapSize].distance < heap[childIndex].distance) {
+                            break;
+                        }
+
+                        heap[rootIndex] = heap[childIndex];
+                        rootIndex = childIndex;
+                    }
+                    heap[rootIndex] = heap[heapSize];
+                }
+
+                uint32_t currentPointerIndex = heap[0].currentPointerIndex;
+                if (matchedCurrentBits.hasBit(currentPointerIndex)) continue; // already matched
+
+                uint32_t lastPointerIndex = heap[0].lastPointerIndex;
+                if (matchedLastBits.hasBit(lastPointerIndex)) continue; // already matched
+
+                matchedCurrentBits.markBit(currentPointerIndex);
+                matchedLastBits.markBit(lastPointerIndex);
+
+                uint32_t id = lastTouch.pointers[lastPointerIndex].id;
+                currentTouch.pointers[currentPointerIndex].id = id;
+                currentTouch.idToIndex[id] = currentPointerIndex;
+                usedIdBits.markBit(id);
+                break;
+            }
+        }
+
+        // Assign fresh ids to new pointers.
+        if (currentPointerCount > lastPointerCount) {
+            for (uint32_t i = currentPointerCount - lastPointerCount; ;) {
+                uint32_t currentPointerIndex = matchedCurrentBits.firstUnmarkedBit();
+                uint32_t id = usedIdBits.firstUnmarkedBit();
+
+                currentTouch.pointers[currentPointerIndex].id = id;
+                currentTouch.idToIndex[id] = currentPointerIndex;
+                usedIdBits.markBit(id);
+
+                if (--i == 0) break; // done
+                matchedCurrentBits.markBit(currentPointerIndex);
+            }
+        }
+
+        // Fix id bits.
+        currentTouch.idBits = usedIdBits;
+    }
+}
+
+/* Special hack for devices that have bad screen data: if one of the
+ * points has moved more than a screen height from the last position,
+ * then drop it. */
+bool InputDevice::TouchScreenState::applyBadTouchFilter() {
+    uint32_t pointerCount = currentTouch.pointerCount;
+
+    // Nothing to do if there are no points.
+    if (pointerCount == 0) {
+        return false;
+    }
+
+    // Don't do anything if a finger is going down or up.  We run
+    // here before assigning pointer IDs, so there isn't a good
+    // way to do per-finger matching.
+    if (pointerCount != lastTouch.pointerCount) {
+        return false;
+    }
+
+    // We consider a single movement across more than a 7/16 of
+    // the long size of the screen to be bad.  This was a magic value
+    // determined by looking at the maximum distance it is feasible
+    // to actually move in one sample.
+    int32_t maxDeltaY = parameters.yAxis.range * 7 / 16;
+
+    // XXX The original code in InputDevice.java included commented out
+    //     code for testing the X axis.  Note that when we drop a point
+    //     we don't actually restore the old X either.  Strange.
+    //     The old code also tries to track when bad points were previously
+    //     detected but it turns out that due to the placement of a "break"
+    //     at the end of the loop, we never set mDroppedBadPoint to true
+    //     so it is effectively dead code.
+    // Need to figure out if the old code is busted or just overcomplicated
+    // but working as intended.
+
+    // Look through all new points and see if any are farther than
+    // acceptable from all previous points.
+    for (uint32_t i = pointerCount; i-- > 0; ) {
+        int32_t y = currentTouch.pointers[i].y;
+        int32_t closestY = INT_MAX;
+        int32_t closestDeltaY = 0;
+
+#if DEBUG_HACKS
+        LOGD("BadTouchFilter: Looking at next point #%d: y=%d", i, y);
+#endif
+
+        for (uint32_t j = pointerCount; j-- > 0; ) {
+            int32_t lastY = lastTouch.pointers[j].y;
+            int32_t deltaY = abs(y - lastY);
+
+#if DEBUG_HACKS
+            LOGD("BadTouchFilter: Comparing with last point #%d: y=%d deltaY=%d",
+                    j, lastY, deltaY);
+#endif
+
+            if (deltaY < maxDeltaY) {
+                goto SkipSufficientlyClosePoint;
+            }
+            if (deltaY < closestDeltaY) {
+                closestDeltaY = deltaY;
+                closestY = lastY;
+            }
+        }
+
+        // Must not have found a close enough match.
+#if DEBUG_HACKS
+        LOGD("BadTouchFilter: Dropping bad point #%d: newY=%d oldY=%d deltaY=%d maxDeltaY=%d",
+                i, y, closestY, closestDeltaY, maxDeltaY);
+#endif
+
+        currentTouch.pointers[i].y = closestY;
+        return true; // XXX original code only corrects one point
+
+    SkipSufficientlyClosePoint: ;
+    }
+
+    // No change.
+    return false;
+}
+
+/* Special hack for devices that have bad screen data: drop points where
+ * the coordinate value for one axis has jumped to the other pointer's location.
+ */
+bool InputDevice::TouchScreenState::applyJumpyTouchFilter() {
+    uint32_t pointerCount = currentTouch.pointerCount;
+    if (lastTouch.pointerCount != pointerCount) {
+#if DEBUG_HACKS
+        LOGD("JumpyTouchFilter: Different pointer count %d -> %d",
+                lastTouch.pointerCount, pointerCount);
+        for (uint32_t i = 0; i < pointerCount; i++) {
+            LOGD("  Pointer %d (%d, %d)", i,
+                    currentTouch.pointers[i].x, currentTouch.pointers[i].y);
+        }
+#endif
+
+        if (jumpyTouchFilter.jumpyPointsDropped < JUMPY_TRANSITION_DROPS) {
+            if (lastTouch.pointerCount == 1 && pointerCount == 2) {
+                // Just drop the first few events going from 1 to 2 pointers.
+                // They're bad often enough that they're not worth considering.
+                currentTouch.pointerCount = 1;
+                jumpyTouchFilter.jumpyPointsDropped += 1;
+
+#if DEBUG_HACKS
+                LOGD("JumpyTouchFilter: Pointer 2 dropped");
+#endif
+                return true;
+            } else if (lastTouch.pointerCount == 2 && pointerCount == 1) {
+                // The event when we go from 2 -> 1 tends to be messed up too
+                currentTouch.pointerCount = 2;
+                currentTouch.pointers[0] = lastTouch.pointers[0];
+                currentTouch.pointers[1] = lastTouch.pointers[1];
+                jumpyTouchFilter.jumpyPointsDropped += 1;
+
+#if DEBUG_HACKS
+                for (int32_t i = 0; i < 2; i++) {
+                    LOGD("JumpyTouchFilter: Pointer %d replaced (%d, %d)", i,
+                            currentTouch.pointers[i].x, currentTouch.pointers[i].y);
+                }
+#endif
+                return true;
+            }
+        }
+        // Reset jumpy points dropped on other transitions or if limit exceeded.
+        jumpyTouchFilter.jumpyPointsDropped = 0;
+
+#if DEBUG_HACKS
+        LOGD("JumpyTouchFilter: Transition - drop limit reset");
+#endif
+        return false;
+    }
+
+    // We have the same number of pointers as last time.
+    // A 'jumpy' point is one where the coordinate value for one axis
+    // has jumped to the other pointer's location. No need to do anything
+    // else if we only have one pointer.
+    if (pointerCount < 2) {
+        return false;
+    }
+
+    if (jumpyTouchFilter.jumpyPointsDropped < JUMPY_DROP_LIMIT) {
+        int jumpyEpsilon = parameters.yAxis.range / JUMPY_EPSILON_DIVISOR;
+
+        // We only replace the single worst jumpy point as characterized by pointer distance
+        // in a single axis.
+        int32_t badPointerIndex = -1;
+        int32_t badPointerReplacementIndex = -1;
+        int32_t badPointerDistance = INT_MIN; // distance to be corrected
+
+        for (uint32_t i = pointerCount; i-- > 0; ) {
+            int32_t x = currentTouch.pointers[i].x;
+            int32_t y = currentTouch.pointers[i].y;
+
+#if DEBUG_HACKS
+            LOGD("JumpyTouchFilter: Point %d (%d, %d)", i, x, y);
+#endif
+
+            // Check if a touch point is too close to another's coordinates
+            bool dropX = false, dropY = false;
+            for (uint32_t j = 0; j < pointerCount; j++) {
+                if (i == j) {
+                    continue;
+                }
+
+                if (abs(x - currentTouch.pointers[j].x) <= jumpyEpsilon) {
+                    dropX = true;
+                    break;
+                }
+
+                if (abs(y - currentTouch.pointers[j].y) <= jumpyEpsilon) {
+                    dropY = true;
+                    break;
+                }
+            }
+            if (! dropX && ! dropY) {
+                continue; // not jumpy
+            }
+
+            // Find a replacement candidate by comparing with older points on the
+            // complementary (non-jumpy) axis.
+            int32_t distance = INT_MIN; // distance to be corrected
+            int32_t replacementIndex = -1;
+
+            if (dropX) {
+                // X looks too close.  Find an older replacement point with a close Y.
+                int32_t smallestDeltaY = INT_MAX;
+                for (uint32_t j = 0; j < pointerCount; j++) {
+                    int32_t deltaY = abs(y - lastTouch.pointers[j].y);
+                    if (deltaY < smallestDeltaY) {
+                        smallestDeltaY = deltaY;
+                        replacementIndex = j;
+                    }
+                }
+                distance = abs(x - lastTouch.pointers[replacementIndex].x);
+            } else {
+                // Y looks too close.  Find an older replacement point with a close X.
+                int32_t smallestDeltaX = INT_MAX;
+                for (uint32_t j = 0; j < pointerCount; j++) {
+                    int32_t deltaX = abs(x - lastTouch.pointers[j].x);
+                    if (deltaX < smallestDeltaX) {
+                        smallestDeltaX = deltaX;
+                        replacementIndex = j;
+                    }
+                }
+                distance = abs(y - lastTouch.pointers[replacementIndex].y);
+            }
+
+            // If replacing this pointer would correct a worse error than the previous ones
+            // considered, then use this replacement instead.
+            if (distance > badPointerDistance) {
+                badPointerIndex = i;
+                badPointerReplacementIndex = replacementIndex;
+                badPointerDistance = distance;
+            }
+        }
+
+        // Correct the jumpy pointer if one was found.
+        if (badPointerIndex >= 0) {
+#if DEBUG_HACKS
+            LOGD("JumpyTouchFilter: Replacing bad pointer %d with (%d, %d)",
+                    badPointerIndex,
+                    lastTouch.pointers[badPointerReplacementIndex].x,
+                    lastTouch.pointers[badPointerReplacementIndex].y);
+#endif
+
+            currentTouch.pointers[badPointerIndex].x =
+                    lastTouch.pointers[badPointerReplacementIndex].x;
+            currentTouch.pointers[badPointerIndex].y =
+                    lastTouch.pointers[badPointerReplacementIndex].y;
+            jumpyTouchFilter.jumpyPointsDropped += 1;
+            return true;
+        }
+    }
+
+    jumpyTouchFilter.jumpyPointsDropped = 0;
+    return false;
+}
+
+/* Special hack for devices that have bad screen data: aggregate and
+ * compute averages of the coordinate data, to reduce the amount of
+ * jitter seen by applications. */
+void InputDevice::TouchScreenState::applyAveragingTouchFilter() {
+    for (uint32_t currentIndex = 0; currentIndex < currentTouch.pointerCount; currentIndex++) {
+        uint32_t id = currentTouch.pointers[currentIndex].id;
+        int32_t x = currentTouch.pointers[currentIndex].x;
+        int32_t y = currentTouch.pointers[currentIndex].y;
+        int32_t pressure = currentTouch.pointers[currentIndex].pressure;
+
+        if (lastTouch.idBits.hasBit(id)) {
+            // Pointer still down compute average.
+            uint32_t start = averagingTouchFilter.historyStart[id];
+            uint32_t end = averagingTouchFilter.historyEnd[id];
+
+            int64_t deltaX = x - averagingTouchFilter.historyData[end].pointers[id].x;
+            int64_t deltaY = y - averagingTouchFilter.historyData[end].pointers[id].y;
+            uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY);
+
+#if DEBUG_HACKS
+            LOGD("AveragingTouchFilter: Pointer id %d - Distance from last sample: %lld",
+                    id, distance);
+#endif
+
+            if (distance < AVERAGING_DISTANCE_LIMIT) {
+                end += 1;
+                if (end > AVERAGING_HISTORY_SIZE) {
+                    end = 0;
+                }
+
+                if (end == start) {
+                    start += 1;
+                    if (start > AVERAGING_HISTORY_SIZE) {
+                        start = 0;
+                    }
+                }
+
+                averagingTouchFilter.historyStart[id] = start;
+                averagingTouchFilter.historyEnd[id] = end;
+                averagingTouchFilter.historyData[end].pointers[id].x = x;
+                averagingTouchFilter.historyData[end].pointers[id].y = y;
+                averagingTouchFilter.historyData[end].pointers[id].pressure = pressure;
+
+                int32_t averagedX = 0;
+                int32_t averagedY = 0;
+                int32_t totalPressure = 0;
+                for (;;) {
+                    int32_t historicalX = averagingTouchFilter.historyData[start].pointers[id].x;
+                    int32_t historicalY = averagingTouchFilter.historyData[start].pointers[id].x;
+                    int32_t historicalPressure = averagingTouchFilter.historyData[start]
+                            .pointers[id].pressure;
+
+                    averagedX += historicalX;
+                    averagedY += historicalY;
+                    totalPressure += historicalPressure;
+
+                    if (start == end) {
+                        break;
+                    }
+
+                    start += 1;
+                    if (start > AVERAGING_HISTORY_SIZE) {
+                        start = 0;
+                    }
+                }
+
+                averagedX /= totalPressure;
+                averagedY /= totalPressure;
+
+#if DEBUG_HACKS
+                LOGD("AveragingTouchFilter: Pointer id %d - "
+                        "totalPressure=%d, averagedX=%d, averagedY=%d", id, totalPressure,
+                        averagedX, averagedY);
+#endif
+
+                currentTouch.pointers[currentIndex].x = averagedX;
+                currentTouch.pointers[currentIndex].y = averagedY;
+            } else {
+#if DEBUG_HACKS
+                LOGD("AveragingTouchFilter: Pointer id %d - Exceeded max distance", id);
+#endif
+            }
+        } else {
+#if DEBUG_HACKS
+            LOGD("AveragingTouchFilter: Pointer id %d - Pointer went up", id);
+#endif
+        }
+
+        // Reset pointer history.
+        averagingTouchFilter.historyStart[id] = 0;
+        averagingTouchFilter.historyEnd[id] = 0;
+        averagingTouchFilter.historyData[0].pointers[id].x = x;
+        averagingTouchFilter.historyData[0].pointers[id].y = y;
+        averagingTouchFilter.historyData[0].pointers[id].pressure = pressure;
+    }
+}
+
+bool InputDevice::TouchScreenState::isPointInsideDisplay(int32_t x, int32_t y) const {
+    return x >= parameters.xAxis.minValue
+        && x <= parameters.xAxis.maxValue
+        && y >= parameters.yAxis.minValue
+        && y <= parameters.yAxis.maxValue;
+}
+
+
+// --- InputDevice::SingleTouchScreenState ---
+
+void InputDevice::SingleTouchScreenState::reset() {
+    accumulator.clear();
+    current.down = false;
+    current.x = 0;
+    current.y = 0;
+    current.pressure = 0;
+    current.size = 0;
+}
+
+
+// --- InputDevice::MultiTouchScreenState ---
+
+void InputDevice::MultiTouchScreenState::reset() {
+    accumulator.clear();
+}
+
+
+// --- InputReader ---
+
+InputReader::InputReader(const sp<EventHubInterface>& eventHub,
+        const sp<InputDispatchPolicyInterface>& policy,
+        const sp<InputDispatcherInterface>& dispatcher) :
+        mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher) {
+    resetGlobalMetaState();
+    resetDisplayProperties();
+    updateGlobalVirtualKeyState();
+}
+
+InputReader::~InputReader() {
+    for (size_t i = 0; i < mDevices.size(); i++) {
+        delete mDevices.valueAt(i);
+    }
+}
+
+void InputReader::loopOnce() {
+    RawEvent rawEvent;
+    mEventHub->getEvent(& rawEvent.deviceId, & rawEvent.type, & rawEvent.scanCode,
+            & rawEvent.keyCode, & rawEvent.flags, & rawEvent.value, & rawEvent.when);
+
+    // Replace the event timestamp so it is in same timebase as java.lang.System.nanoTime()
+    // and android.os.SystemClock.uptimeMillis() as expected by the rest of the system.
+    rawEvent.when = systemTime(SYSTEM_TIME_MONOTONIC);
+
+#if DEBUG_RAW_EVENTS
+    LOGD("Input event: device=0x%x type=0x%x scancode=%d keycode=%d value=%d",
+            rawEvent.deviceId, rawEvent.type, rawEvent.scanCode, rawEvent.keyCode,
+            rawEvent.value);
+#endif
+
+    process(& rawEvent);
+}
+
+void InputReader::process(const RawEvent* rawEvent) {
+    switch (rawEvent->type) {
+    case EventHubInterface::DEVICE_ADDED:
+        handleDeviceAdded(rawEvent);
+        break;
+
+    case EventHubInterface::DEVICE_REMOVED:
+        handleDeviceRemoved(rawEvent);
+        break;
+
+    case EV_SYN:
+        handleSync(rawEvent);
+        break;
+
+    case EV_KEY:
+        handleKey(rawEvent);
+        break;
+
+    case EV_REL:
+        handleRelativeMotion(rawEvent);
+        break;
+
+    case EV_ABS:
+        handleAbsoluteMotion(rawEvent);
+        break;
+
+    case EV_SW:
+        handleSwitch(rawEvent);
+        break;
+    }
+}
+
+void InputReader::handleDeviceAdded(const RawEvent* rawEvent) {
+    InputDevice* device = getDevice(rawEvent->deviceId);
+    if (device) {
+        LOGW("Ignoring spurious device added event for deviceId %d.", rawEvent->deviceId);
+        return;
+    }
+
+    addDevice(rawEvent->when, rawEvent->deviceId);
+}
+
+void InputReader::handleDeviceRemoved(const RawEvent* rawEvent) {
+    InputDevice* device = getDevice(rawEvent->deviceId);
+    if (! device) {
+        LOGW("Ignoring spurious device removed event for deviceId %d.", rawEvent->deviceId);
+        return;
+    }
+
+    removeDevice(rawEvent->when, device);
+}
+
+void InputReader::handleSync(const RawEvent* rawEvent) {
+    InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId);
+    if (! device) return;
+
+    if (rawEvent->scanCode == SYN_MT_REPORT) {
+        // MultiTouch Sync: The driver has returned all data for *one* of the pointers.
+        // We drop pointers with pressure <= 0 since that indicates they are not down.
+        if (device->isMultiTouchScreen()) {
+            uint32_t pointerIndex = device->multiTouchScreen.accumulator.pointerCount;
+
+            if (device->multiTouchScreen.accumulator.pointers[pointerIndex].fields) {
+                if (pointerIndex == MAX_POINTERS) {
+                    LOGW("MultiTouch device driver returned more than maximum of %d pointers.",
+                            MAX_POINTERS);
+                } else {
+                    pointerIndex += 1;
+                    device->multiTouchScreen.accumulator.pointerCount = pointerIndex;
+                }
+            }
+
+            device->multiTouchScreen.accumulator.pointers[pointerIndex].clear();
+        }
+    } else if (rawEvent->scanCode == SYN_REPORT) {
+        // General Sync: The driver has returned all data for the current event update.
+        if (device->isMultiTouchScreen()) {
+            if (device->multiTouchScreen.accumulator.isDirty()) {
+                onMultiTouchScreenStateChanged(rawEvent->when, device);
+                device->multiTouchScreen.accumulator.clear();
+            }
+        } else if (device->isSingleTouchScreen()) {
+            if (device->singleTouchScreen.accumulator.isDirty()) {
+                onSingleTouchScreenStateChanged(rawEvent->when, device);
+                device->singleTouchScreen.accumulator.clear();
+            }
+        }
+
+        if (device->trackball.accumulator.isDirty()) {
+            onTrackballStateChanged(rawEvent->when, device);
+            device->trackball.accumulator.clear();
+        }
+    }
+}
+
+void InputReader::handleKey(const RawEvent* rawEvent) {
+    InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId);
+    if (! device) return;
+
+    bool down = rawEvent->value != 0;
+    int32_t scanCode = rawEvent->scanCode;
+
+    if (device->isKeyboard() && (scanCode < BTN_FIRST || scanCode > BTN_LAST)) {
+        int32_t keyCode = rawEvent->keyCode;
+        onKey(rawEvent->when, device, down, keyCode, scanCode, rawEvent->flags);
+    } else if (device->isSingleTouchScreen()) {
+        switch (rawEvent->scanCode) {
+        case BTN_TOUCH:
+            device->singleTouchScreen.accumulator.fields |=
+                    InputDevice::SingleTouchScreenState::Accumulator::FIELD_BTN_TOUCH;
+            device->singleTouchScreen.accumulator.btnTouch = down;
+            break;
+        }
+    } else if (device->isTrackball()) {
+        switch (rawEvent->scanCode) {
+        case BTN_MOUSE:
+            device->trackball.accumulator.fields |=
+                    InputDevice::TrackballState::Accumulator::FIELD_BTN_MOUSE;
+            device->trackball.accumulator.btnMouse = down;
+
+            // send the down immediately
+            // XXX this emulates the old behavior of KeyInputQueue, unclear whether it is
+            //     necessary or if we can wait until the next sync
+            onTrackballStateChanged(rawEvent->when, device);
+            device->trackball.accumulator.clear();
+            break;
+        }
+    }
+}
+
+void InputReader::handleRelativeMotion(const RawEvent* rawEvent) {
+    InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId);
+    if (! device) return;
+
+    if (device->isTrackball()) {
+        switch (rawEvent->scanCode) {
+        case REL_X:
+            device->trackball.accumulator.fields |=
+                    InputDevice::TrackballState::Accumulator::FIELD_REL_X;
+            device->trackball.accumulator.relX = rawEvent->value;
+            break;
+        case REL_Y:
+            device->trackball.accumulator.fields |=
+                    InputDevice::TrackballState::Accumulator::FIELD_REL_Y;
+            device->trackball.accumulator.relY = rawEvent->value;
+            break;
+        }
+    }
+}
+
+void InputReader::handleAbsoluteMotion(const RawEvent* rawEvent) {
+    InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId);
+    if (! device) return;
+
+    if (device->isMultiTouchScreen()) {
+        uint32_t pointerIndex = device->multiTouchScreen.accumulator.pointerCount;
+        InputDevice::MultiTouchScreenState::Accumulator::Pointer* pointer =
+                & device->multiTouchScreen.accumulator.pointers[pointerIndex];
+
+        switch (rawEvent->scanCode) {
+        case ABS_MT_POSITION_X:
+            pointer->fields |=
+                    InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_X;
+            pointer->absMTPositionX = rawEvent->value;
+            break;
+        case ABS_MT_POSITION_Y:
+            pointer->fields |=
+                    InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_Y;
+            pointer->absMTPositionY = rawEvent->value;
+            break;
+        case ABS_MT_TOUCH_MAJOR:
+            pointer->fields |=
+                    InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TOUCH_MAJOR;
+            pointer->absMTTouchMajor = rawEvent->value;
+            break;
+        case ABS_MT_WIDTH_MAJOR:
+            pointer->fields |=
+                    InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_WIDTH_MAJOR;
+            pointer->absMTWidthMajor = rawEvent->value;
+            break;
+        case ABS_MT_TRACKING_ID:
+            pointer->fields |=
+                    InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TRACKING_ID;
+            pointer->absMTTrackingId = rawEvent->value;
+            break;
+        }
+    } else if (device->isSingleTouchScreen()) {
+        switch (rawEvent->scanCode) {
+        case ABS_X:
+            device->singleTouchScreen.accumulator.fields |=
+                    InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_X;
+            device->singleTouchScreen.accumulator.absX = rawEvent->value;
+            break;
+        case ABS_Y:
+            device->singleTouchScreen.accumulator.fields |=
+                    InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_Y;
+            device->singleTouchScreen.accumulator.absY = rawEvent->value;
+            break;
+        case ABS_PRESSURE:
+            device->singleTouchScreen.accumulator.fields |=
+                    InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_PRESSURE;
+            device->singleTouchScreen.accumulator.absPressure = rawEvent->value;
+            break;
+        case ABS_TOOL_WIDTH:
+            device->singleTouchScreen.accumulator.fields |=
+                    InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_TOOL_WIDTH;
+            device->singleTouchScreen.accumulator.absToolWidth = rawEvent->value;
+            break;
+        }
+    }
+}
+
+void InputReader::handleSwitch(const RawEvent* rawEvent) {
+    InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId);
+    if (! device) return;
+
+    onSwitch(rawEvent->when, device, rawEvent->value != 0, rawEvent->scanCode);
+}
+
+void InputReader::onKey(nsecs_t when, InputDevice* device,
+        bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags) {
+    /* Refresh display properties so we can rotate key codes according to display orientation */
+
+    if (! refreshDisplayProperties()) {
+        return;
+    }
+
+    /* Update device state */
+
+    int32_t oldMetaState = device->keyboard.current.metaState;
+    int32_t newMetaState = updateMetaState(keyCode, down, oldMetaState);
+    if (oldMetaState != newMetaState) {
+        device->keyboard.current.metaState = newMetaState;
+        resetGlobalMetaState();
+    }
+
+    // FIXME if we send a down event about a rotated key press we should ensure that we send
+    //       a corresponding up event about the rotated key press even if the orientation
+    //       has changed in the meantime
+    keyCode = rotateKeyCode(keyCode, mDisplayOrientation);
+
+    if (down) {
+        device->keyboard.current.downTime = when;
+    }
+
+    /* Apply policy */
+
+    int32_t policyActions = mPolicy->interceptKey(when, device->id,
+            down, keyCode, scanCode, policyFlags);
+
+    if (! applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) {
+        return; // event dropped
+    }
+
+    /* Enqueue key event for dispatch */
+
+    int32_t keyEventAction;
+    if (down) {
+        device->keyboard.current.downTime = when;
+        keyEventAction = KEY_EVENT_ACTION_DOWN;
+    } else {
+        keyEventAction = KEY_EVENT_ACTION_UP;
+    }
+
+    int32_t keyEventFlags = KEY_EVENT_FLAG_FROM_SYSTEM;
+    if (policyActions & InputDispatchPolicyInterface::ACTION_WOKE_HERE) {
+        keyEventFlags = keyEventFlags | KEY_EVENT_FLAG_WOKE_HERE;
+    }
+
+    mDispatcher->notifyKey(when, device->id, INPUT_EVENT_NATURE_KEY, policyFlags,
+            keyEventAction, keyEventFlags, keyCode, scanCode,
+            device->keyboard.current.metaState,
+            device->keyboard.current.downTime);
+}
+
+void InputReader::onSwitch(nsecs_t when, InputDevice* device, bool down,
+        int32_t code) {
+    switch (code) {
+    case SW_LID:
+        mDispatcher->notifyLidSwitchChanged(when, ! down);
+    }
+}
+
+void InputReader::onMultiTouchScreenStateChanged(nsecs_t when,
+        InputDevice* device) {
+    static const uint32_t REQUIRED_FIELDS =
+            InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_X
+            | InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_Y
+            | InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TOUCH_MAJOR
+            | InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_WIDTH_MAJOR;
+
+    /* Refresh display properties so we can map touch screen coords into display coords */
+
+    if (! refreshDisplayProperties()) {
+        return;
+    }
+
+    /* Update device state */
+
+    InputDevice::MultiTouchScreenState* in = & device->multiTouchScreen;
+    InputDevice::TouchData* out = & device->touchScreen.currentTouch;
+
+    uint32_t inCount = in->accumulator.pointerCount;
+    uint32_t outCount = 0;
+    bool havePointerIds = true;
+
+    out->clear();
+
+    for (uint32_t inIndex = 0; inIndex < inCount; inIndex++) {
+        uint32_t fields = in->accumulator.pointers[inIndex].fields;
+
+        if ((fields & REQUIRED_FIELDS) != REQUIRED_FIELDS) {
+#if DEBUG_POINTERS
+            LOGD("Pointers: Missing required multitouch pointer fields: index=%d, fields=%d",
+                    inIndex, fields);
+            continue;
+#endif
+        }
+
+        if (in->accumulator.pointers[inIndex].absMTTouchMajor <= 0) {
+            // Pointer is not down.  Drop it.
+            continue;
+        }
+
+        // FIXME assignment of pressure may be incorrect, probably better to let
+        // pressure = touch / width.  Later on we pass width to MotionEvent as a size, which
+        // isn't quite right either.  Should be using touch for that.
+        out->pointers[outCount].x = in->accumulator.pointers[inIndex].absMTPositionX;
+        out->pointers[outCount].y = in->accumulator.pointers[inIndex].absMTPositionY;
+        out->pointers[outCount].pressure = in->accumulator.pointers[inIndex].absMTTouchMajor;
+        out->pointers[outCount].size = in->accumulator.pointers[inIndex].absMTWidthMajor;
+
+        if (havePointerIds) {
+            if (fields & InputDevice::MultiTouchScreenState::Accumulator::
+                    FIELD_ABS_MT_TRACKING_ID) {
+                uint32_t id = uint32_t(in->accumulator.pointers[inIndex].absMTTrackingId);
+
+                if (id > MAX_POINTER_ID) {
+#if DEBUG_POINTERS
+                    LOGD("Pointers: Ignoring driver provided pointer id %d because "
+                            "it is larger than max supported id %d for optimizations",
+                            id, MAX_POINTER_ID);
+#endif
+                    havePointerIds = false;
+                }
+                else {
+                    out->pointers[outCount].id = id;
+                    out->idToIndex[id] = outCount;
+                    out->idBits.markBit(id);
+                }
+            } else {
+                havePointerIds = false;
+            }
+        }
+
+        outCount += 1;
+    }
+
+    out->pointerCount = outCount;
+
+    onTouchScreenChanged(when, device, havePointerIds);
+}
+
+void InputReader::onSingleTouchScreenStateChanged(nsecs_t when,
+        InputDevice* device) {
+    static const uint32_t POSITION_FIELDS =
+            InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_X
+            | InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_Y
+            | InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_PRESSURE
+            | InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_TOOL_WIDTH;
+
+    /* Refresh display properties so we can map touch screen coords into display coords */
+
+    if (! refreshDisplayProperties()) {
+        return;
+    }
+
+    /* Update device state */
+
+    InputDevice::SingleTouchScreenState* in = & device->singleTouchScreen;
+    InputDevice::TouchData* out = & device->touchScreen.currentTouch;
+
+    uint32_t fields = in->accumulator.fields;
+
+    if (fields & InputDevice::SingleTouchScreenState::Accumulator::FIELD_BTN_TOUCH) {
+        in->current.down = in->accumulator.btnTouch;
+    }
+
+    if ((fields & POSITION_FIELDS) == POSITION_FIELDS) {
+        in->current.x = in->accumulator.absX;
+        in->current.y = in->accumulator.absY;
+        in->current.pressure = in->accumulator.absPressure;
+        in->current.size = in->accumulator.absToolWidth;
+    }
+
+    out->clear();
+
+    if (in->current.down) {
+        out->pointerCount = 1;
+        out->pointers[0].id = 0;
+        out->pointers[0].x = in->current.x;
+        out->pointers[0].y = in->current.y;
+        out->pointers[0].pressure = in->current.pressure;
+        out->pointers[0].size = in->current.size;
+        out->idToIndex[0] = 0;
+        out->idBits.markBit(0);
+    }
+
+    onTouchScreenChanged(when, device, true);
+}
+
+void InputReader::onTouchScreenChanged(nsecs_t when,
+        InputDevice* device, bool havePointerIds) {
+    /* Apply policy */
+
+    int32_t policyActions = mPolicy->interceptTouch(when);
+
+    uint32_t policyFlags = 0;
+    if (! applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) {
+        device->touchScreen.lastTouch.clear();
+        return; // event dropped
+    }
+
+    /* Preprocess pointer data */
+
+    if (device->touchScreen.parameters.useBadTouchFilter) {
+        if (device->touchScreen.applyBadTouchFilter()) {
+            havePointerIds = false;
+        }
+    }
+
+    if (device->touchScreen.parameters.useJumpyTouchFilter) {
+        if (device->touchScreen.applyJumpyTouchFilter()) {
+            havePointerIds = false;
+        }
+    }
+
+    if (! havePointerIds) {
+        device->touchScreen.calculatePointerIds();
+    }
+
+    InputDevice::TouchData temp;
+    InputDevice::TouchData* savedTouch;
+    if (device->touchScreen.parameters.useAveragingTouchFilter) {
+        temp.copyFrom(device->touchScreen.currentTouch);
+        savedTouch = & temp;
+
+        device->touchScreen.applyAveragingTouchFilter();
+    } else {
+        savedTouch = & device->touchScreen.currentTouch;
+    }
+
+    /* Process virtual keys or touches */
+
+    if (! consumeVirtualKeyTouches(when, device, policyFlags)) {
+        dispatchTouches(when, device, policyFlags);
+    }
+
+    // Copy current touch to last touch in preparation for the next cycle.
+    device->touchScreen.lastTouch.copyFrom(*savedTouch);
+}
+
+bool InputReader::consumeVirtualKeyTouches(nsecs_t when,
+        InputDevice* device, uint32_t policyFlags) {
+    if (device->touchScreen.currentVirtualKey.down) {
+        if (device->touchScreen.currentTouch.pointerCount == 0) {
+            // Pointer went up while virtual key was down.  Send key up event.
+            device->touchScreen.currentVirtualKey.down = false;
+
+#if DEBUG_VIRTUAL_KEYS
+            LOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d",
+                    device->touchScreen.currentVirtualKey.keyCode,
+                    device->touchScreen.currentVirtualKey.scanCode);
+#endif
+
+            dispatchVirtualKey(when, device, policyFlags, KEY_EVENT_ACTION_UP,
+                    KEY_EVENT_FLAG_FROM_SYSTEM | KEY_EVENT_FLAG_VIRTUAL_HARD_KEY);
+            return true; // consumed
+        }
+
+        int32_t x = device->touchScreen.currentTouch.pointers[0].x;
+        int32_t y = device->touchScreen.currentTouch.pointers[0].y;
+        if (device->touchScreen.isPointInsideDisplay(x, y)) {
+            // Pointer moved inside the display area.  Send key cancellation.
+            device->touchScreen.currentVirtualKey.down = false;
+
+#if DEBUG_VIRTUAL_KEYS
+            LOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d",
+                    device->touchScreen.currentVirtualKey.keyCode,
+                    device->touchScreen.currentVirtualKey.scanCode);
+#endif
+
+            dispatchVirtualKey(when, device, policyFlags, KEY_EVENT_ACTION_UP,
+                    KEY_EVENT_FLAG_FROM_SYSTEM | KEY_EVENT_FLAG_VIRTUAL_HARD_KEY
+                            | KEY_EVENT_FLAG_CANCELED);
+
+            // Clear the last touch data so we will consider the pointer as having just been
+            // pressed down when generating subsequent motion events.
+            device->touchScreen.lastTouch.clear();
+            return false; // not consumed
+        }
+    } else if (device->touchScreen.currentTouch.pointerCount > 0
+            && device->touchScreen.lastTouch.pointerCount == 0) {
+        int32_t x = device->touchScreen.currentTouch.pointers[0].x;
+        int32_t y = device->touchScreen.currentTouch.pointers[0].y;
+        for (size_t i = 0; i < device->touchScreen.virtualKeys.size(); i++) {
+            const InputDevice::VirtualKey& virtualKey = device->touchScreen.virtualKeys[i];
+
+#if DEBUG_VIRTUAL_KEYS
+            LOGD("VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, "
+                    "left=%d, top=%d, right=%d, bottom=%d",
+                    x, y,
+                    virtualKey.keyCode, virtualKey.scanCode,
+                    virtualKey.hitLeft, virtualKey.hitTop,
+                    virtualKey.hitRight, virtualKey.hitBottom);
+#endif
+
+            if (virtualKey.isHit(x, y)) {
+                device->touchScreen.currentVirtualKey.down = true;
+                device->touchScreen.currentVirtualKey.downTime = when;
+                device->touchScreen.currentVirtualKey.keyCode = virtualKey.keyCode;
+                device->touchScreen.currentVirtualKey.scanCode = virtualKey.scanCode;
+
+#if DEBUG_VIRTUAL_KEYS
+                    LOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d",
+                            device->touchScreen.currentVirtualKey.keyCode,
+                            device->touchScreen.currentVirtualKey.scanCode);
+#endif
+
+                dispatchVirtualKey(when, device, policyFlags, KEY_EVENT_ACTION_DOWN,
+                        KEY_EVENT_FLAG_FROM_SYSTEM | KEY_EVENT_FLAG_VIRTUAL_HARD_KEY);
+                return true; // consumed
+            }
+        }
+    }
+
+    return false; // not consumed
+}
+
+void InputReader::dispatchVirtualKey(nsecs_t when,
+        InputDevice* device, uint32_t policyFlags,
+        int32_t keyEventAction, int32_t keyEventFlags) {
+    int32_t keyCode = device->touchScreen.currentVirtualKey.keyCode;
+    int32_t scanCode = device->touchScreen.currentVirtualKey.scanCode;
+    nsecs_t downTime = device->touchScreen.currentVirtualKey.downTime;
+    int32_t metaState = globalMetaState();
+
+    updateGlobalVirtualKeyState();
+
+    mPolicy->virtualKeyFeedback(when, device->id, keyEventAction, keyEventFlags,
+            keyCode, scanCode, metaState, downTime);
+
+    mDispatcher->notifyKey(when, device->id, INPUT_EVENT_NATURE_KEY, policyFlags,
+            keyEventAction, keyEventFlags, keyCode, scanCode, metaState, downTime);
+}
+
+void InputReader::dispatchTouches(nsecs_t when,
+        InputDevice* device, uint32_t policyFlags) {
+    uint32_t currentPointerCount = device->touchScreen.currentTouch.pointerCount;
+    uint32_t lastPointerCount = device->touchScreen.lastTouch.pointerCount;
+    if (currentPointerCount == 0 && lastPointerCount == 0) {
+        return; // nothing to do!
+    }
+
+    BitSet32 currentIdBits = device->touchScreen.currentTouch.idBits;
+    BitSet32 lastIdBits = device->touchScreen.lastTouch.idBits;
+
+    if (currentIdBits == lastIdBits) {
+        // No pointer id changes so this is a move event.
+        // The dispatcher takes care of batching moves so we don't have to deal with that here.
+        int32_t motionEventAction = MOTION_EVENT_ACTION_MOVE;
+        dispatchTouch(when, device, policyFlags, & device->touchScreen.currentTouch,
+                currentIdBits, motionEventAction);
+    } else {
+        // There may be pointers going up and pointers going down at the same time when pointer
+        // ids are reported by the device driver.
+        BitSet32 upIdBits(lastIdBits.value & ~ currentIdBits.value);
+        BitSet32 downIdBits(currentIdBits.value & ~ lastIdBits.value);
+        BitSet32 activeIdBits(lastIdBits.value);
+
+        while (! upIdBits.isEmpty()) {
+            uint32_t upId = upIdBits.firstMarkedBit();
+            upIdBits.clearBit(upId);
+            BitSet32 oldActiveIdBits = activeIdBits;
+            activeIdBits.clearBit(upId);
+
+            int32_t motionEventAction;
+            if (activeIdBits.isEmpty()) {
+                motionEventAction = MOTION_EVENT_ACTION_UP;
+            } else {
+                motionEventAction = MOTION_EVENT_ACTION_POINTER_UP
+                        | (upId << MOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+            }
+
+            dispatchTouch(when, device, policyFlags, & device->touchScreen.lastTouch,
+                    oldActiveIdBits, motionEventAction);
+        }
+
+        while (! downIdBits.isEmpty()) {
+            uint32_t downId = downIdBits.firstMarkedBit();
+            downIdBits.clearBit(downId);
+            BitSet32 oldActiveIdBits = activeIdBits;
+            activeIdBits.markBit(downId);
+
+            int32_t motionEventAction;
+            if (oldActiveIdBits.isEmpty()) {
+                motionEventAction = MOTION_EVENT_ACTION_DOWN;
+                device->touchScreen.downTime = when;
+            } else {
+                motionEventAction = MOTION_EVENT_ACTION_POINTER_DOWN
+                        | (downId << MOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+            }
+
+            dispatchTouch(when, device, policyFlags, & device->touchScreen.currentTouch,
+                    activeIdBits, motionEventAction);
+        }
+    }
+}
+
+void InputReader::dispatchTouch(nsecs_t when, InputDevice* device, uint32_t policyFlags,
+        InputDevice::TouchData* touch, BitSet32 idBits,
+        int32_t motionEventAction) {
+    int32_t orientedWidth, orientedHeight;
+    switch (mDisplayOrientation) {
+    case InputDispatchPolicyInterface::ROTATION_90:
+    case InputDispatchPolicyInterface::ROTATION_270:
+        orientedWidth = mDisplayHeight;
+        orientedHeight = mDisplayWidth;
+        break;
+    default:
+        orientedWidth = mDisplayWidth;
+        orientedHeight = mDisplayHeight;
+        break;
+    }
+
+    uint32_t pointerCount = 0;
+    int32_t pointerIds[MAX_POINTERS];
+    PointerCoords pointerCoords[MAX_POINTERS];
+
+    // Walk through the the active pointers and map touch screen coordinates (TouchData) into
+    // display coordinates (PointerCoords) and adjust for display orientation.
+    while (! idBits.isEmpty()) {
+        uint32_t id = idBits.firstMarkedBit();
+        idBits.clearBit(id);
+        uint32_t index = touch->idToIndex[id];
+
+        float x = (float(touch->pointers[index].x)
+                        - device->touchScreen.parameters.xAxis.minValue)
+                * device->touchScreen.precalculated.xScale;
+        float y = (float(touch->pointers[index].y)
+                        - device->touchScreen.parameters.yAxis.minValue)
+                * device->touchScreen.precalculated.yScale;
+        float pressure = (float(touch->pointers[index].pressure)
+                        - device->touchScreen.parameters.pressureAxis.minValue)
+                * device->touchScreen.precalculated.pressureScale;
+        float size = (float(touch->pointers[index].size)
+                        - device->touchScreen.parameters.sizeAxis.minValue)
+                * device->touchScreen.precalculated.sizeScale;
+
+        switch (mDisplayOrientation) {
+        case InputDispatchPolicyInterface::ROTATION_90: {
+            float xTemp = x;
+            x = y;
+            y = mDisplayHeight - xTemp;
+            break;
+        }
+        case InputDispatchPolicyInterface::ROTATION_180: {
+            x = mDisplayWidth - x;
+            y = mDisplayHeight - y;
+            break;
+        }
+        case InputDispatchPolicyInterface::ROTATION_270: {
+            float xTemp = x;
+            x = mDisplayWidth - y;
+            y = xTemp;
+            break;
+        }
+        }
+
+        pointerIds[pointerCount] = int32_t(id);
+
+        pointerCoords[pointerCount].x = x;
+        pointerCoords[pointerCount].y = y;
+        pointerCoords[pointerCount].pressure = pressure;
+        pointerCoords[pointerCount].size = size;
+
+        pointerCount += 1;
+    }
+
+    // Check edge flags by looking only at the first pointer since the flags are
+    // global to the event.
+    // XXX Maybe we should revise the edge flags API to work on a per-pointer basis.
+    int32_t motionEventEdgeFlags = 0;
+    if (motionEventAction == MOTION_EVENT_ACTION_DOWN) {
+        if (pointerCoords[0].x <= 0) {
+            motionEventEdgeFlags |= MOTION_EVENT_EDGE_FLAG_LEFT;
+        } else if (pointerCoords[0].x >= orientedWidth) {
+            motionEventEdgeFlags |= MOTION_EVENT_EDGE_FLAG_RIGHT;
+        }
+        if (pointerCoords[0].y <= 0) {
+            motionEventEdgeFlags |= MOTION_EVENT_EDGE_FLAG_TOP;
+        } else if (pointerCoords[0].y >= orientedHeight) {
+            motionEventEdgeFlags |= MOTION_EVENT_EDGE_FLAG_BOTTOM;
+        }
+    }
+
+    nsecs_t downTime = device->touchScreen.downTime;
+    mDispatcher->notifyMotion(when, device->id, INPUT_EVENT_NATURE_TOUCH, policyFlags,
+            motionEventAction, globalMetaState(), motionEventEdgeFlags,
+            pointerCount, pointerIds, pointerCoords,
+            0, 0, downTime);
+}
+
+void InputReader::onTrackballStateChanged(nsecs_t when,
+        InputDevice* device) {
+    static const uint32_t DELTA_FIELDS =
+            InputDevice::TrackballState::Accumulator::FIELD_REL_X
+            | InputDevice::TrackballState::Accumulator::FIELD_REL_Y;
+
+    /* Refresh display properties so we can trackball moves according to display orientation */
+
+    if (! refreshDisplayProperties()) {
+        return;
+    }
+
+    /* Update device state */
+
+    uint32_t fields = device->trackball.accumulator.fields;
+    bool downChanged = fields & InputDevice::TrackballState::Accumulator::FIELD_BTN_MOUSE;
+    bool deltaChanged = (fields & DELTA_FIELDS) == DELTA_FIELDS;
+
+    bool down;
+    if (downChanged) {
+        if (device->trackball.accumulator.btnMouse) {
+            device->trackball.current.down = true;
+            device->trackball.current.downTime = when;
+            down = true;
+        } else {
+            device->trackball.current.down = false;
+            down = false;
+        }
+    } else {
+        down = device->trackball.current.down;
+    }
+
+    /* Apply policy */
+
+    int32_t policyActions = mPolicy->interceptTrackball(when, downChanged, down, deltaChanged);
+
+    uint32_t policyFlags = 0;
+    if (! applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) {
+        return; // event dropped
+    }
+
+    /* Enqueue motion event for dispatch */
+
+    int32_t motionEventAction;
+    if (downChanged) {
+        motionEventAction = down ? MOTION_EVENT_ACTION_DOWN : MOTION_EVENT_ACTION_UP;
+    } else {
+        motionEventAction = MOTION_EVENT_ACTION_MOVE;
+    }
+
+    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.pressure = 1.0f; // XXX Consider making this 1.0f if down, 0 otherwise.
+    pointerCoords.size = 0;
+
+    float temp;
+    switch (mDisplayOrientation) {
+    case InputDispatchPolicyInterface::ROTATION_90:
+        temp = pointerCoords.x;
+        pointerCoords.x = pointerCoords.y;
+        pointerCoords.y = - temp;
+        break;
+
+    case InputDispatchPolicyInterface::ROTATION_180:
+        pointerCoords.x = - pointerCoords.x;
+        pointerCoords.y = - pointerCoords.y;
+        break;
+
+    case InputDispatchPolicyInterface::ROTATION_270:
+        temp = pointerCoords.x;
+        pointerCoords.x = - pointerCoords.y;
+        pointerCoords.y = temp;
+        break;
+    }
+
+    mDispatcher->notifyMotion(when, device->id, INPUT_EVENT_NATURE_TRACKBALL, policyFlags,
+            motionEventAction, globalMetaState(), MOTION_EVENT_EDGE_FLAG_NONE,
+            1, & pointerId, & pointerCoords,
+            device->trackball.precalculated.xPrecision,
+            device->trackball.precalculated.yPrecision,
+            device->trackball.current.downTime);
+}
+
+void InputReader::onConfigurationChanged(nsecs_t when) {
+    // Reset global meta state because it depends on the list of all configured devices.
+    resetGlobalMetaState();
+
+    // Reset virtual keys, just in case.
+    updateGlobalVirtualKeyState();
+
+    // Enqueue configuration changed.
+    // XXX This stuff probably needs to be tracked elsewhere in an input device registry
+    //     of some kind that can be asynchronously updated and queried.  (Same as above?)
+    int32_t touchScreenConfig = InputDispatchPolicyInterface::TOUCHSCREEN_NOTOUCH;
+    int32_t keyboardConfig = InputDispatchPolicyInterface::KEYBOARD_NOKEYS;
+    int32_t navigationConfig = InputDispatchPolicyInterface::NAVIGATION_NONAV;
+
+    for (size_t i = 0; i < mDevices.size(); i++) {
+        InputDevice* device = mDevices.valueAt(i);
+        int32_t deviceClasses = device->classes;
+
+        if (deviceClasses & INPUT_DEVICE_CLASS_TOUCHSCREEN) {
+            touchScreenConfig = InputDispatchPolicyInterface::TOUCHSCREEN_FINGER;
+        }
+        if (deviceClasses & INPUT_DEVICE_CLASS_ALPHAKEY) {
+            keyboardConfig = InputDispatchPolicyInterface::KEYBOARD_QWERTY;
+        }
+        if (deviceClasses & INPUT_DEVICE_CLASS_TRACKBALL) {
+            navigationConfig = InputDispatchPolicyInterface::NAVIGATION_TRACKBALL;
+        } else if (deviceClasses & INPUT_DEVICE_CLASS_DPAD) {
+            navigationConfig = InputDispatchPolicyInterface::NAVIGATION_DPAD;
+        }
+    }
+
+    mDispatcher->notifyConfigurationChanged(when, touchScreenConfig,
+            keyboardConfig, navigationConfig);
+}
+
+bool InputReader::applyStandardInputDispatchPolicyActions(nsecs_t when,
+        int32_t policyActions, uint32_t* policyFlags) {
+    if (policyActions & InputDispatchPolicyInterface::ACTION_APP_SWITCH_COMING) {
+        mDispatcher->notifyAppSwitchComing(when);
+    }
+
+    if (policyActions & InputDispatchPolicyInterface::ACTION_WOKE_HERE) {
+        *policyFlags |= POLICY_FLAG_WOKE_HERE;
+    }
+
+    if (policyActions & InputDispatchPolicyInterface::ACTION_BRIGHT_HERE) {
+        *policyFlags |= POLICY_FLAG_BRIGHT_HERE;
+    }
+
+    return policyActions & InputDispatchPolicyInterface::ACTION_DISPATCH;
+}
+
+void InputReader::resetDisplayProperties() {
+    mDisplayWidth = mDisplayHeight = -1;
+    mDisplayOrientation = -1;
+}
+
+bool InputReader::refreshDisplayProperties() {
+    int32_t newWidth, newHeight, newOrientation;
+    if (mPolicy->getDisplayInfo(0, & newWidth, & newHeight, & newOrientation)) {
+        if (newWidth != mDisplayWidth || newHeight != mDisplayHeight) {
+            LOGD("Display size changed from %dx%d to %dx%d, updating device configuration",
+                    mDisplayWidth, mDisplayHeight, newWidth, newHeight);
+
+            mDisplayWidth = newWidth;
+            mDisplayHeight = newHeight;
+
+            for (size_t i = 0; i < mDevices.size(); i++) {
+                configureDeviceForCurrentDisplaySize(mDevices.valueAt(i));
+            }
+        }
+
+        mDisplayOrientation = newOrientation;
+        return true;
+    } else {
+        resetDisplayProperties();
+        return false;
+    }
+}
+
+InputDevice* InputReader::getDevice(int32_t deviceId) {
+    ssize_t index = mDevices.indexOfKey(deviceId);
+    return index >= 0 ? mDevices.valueAt((size_t) index) : NULL;
+}
+
+InputDevice* InputReader::getNonIgnoredDevice(int32_t deviceId) {
+    InputDevice* device = getDevice(deviceId);
+    return device && ! device->ignored ? device : NULL;
+}
+
+void InputReader::addDevice(nsecs_t when, int32_t deviceId) {
+    uint32_t classes = mEventHub->getDeviceClasses(deviceId);
+    String8 name = mEventHub->getDeviceName(deviceId);
+    InputDevice* device = new InputDevice(deviceId, classes, name);
+
+    if (classes != 0) {
+        LOGI("Device added: id=0x%x, name=%s, classes=%02x", device->id,
+                device->name.string(), device->classes);
+
+        configureDevice(device);
+    } else {
+        LOGI("Device added: id=0x%x, name=%s (ignored non-input device)", device->id,
+                device->name.string());
+
+        device->ignored = true;
+    }
+
+    device->reset();
+
+    mDevices.add(deviceId, device);
+
+    if (! device->ignored) {
+        onConfigurationChanged(when);
+    }
+}
+
+void InputReader::removeDevice(nsecs_t when, InputDevice* device) {
+    mDevices.removeItem(device->id);
+
+    if (! device->ignored) {
+        LOGI("Device removed: id=0x%x, name=%s, classes=%02x", device->id,
+                device->name.string(), device->classes);
+
+        onConfigurationChanged(when);
+    } else {
+        LOGI("Device removed: id=0x%x, name=%s (ignored non-input device)", device->id,
+                device->name.string());
+    }
+
+    delete device;
+}
+
+void InputReader::configureDevice(InputDevice* device) {
+    if (device->isMultiTouchScreen()) {
+        configureAbsoluteAxisInfo(device, ABS_MT_POSITION_X, "X",
+                & device->touchScreen.parameters.xAxis);
+        configureAbsoluteAxisInfo(device, ABS_MT_POSITION_Y, "Y",
+                & device->touchScreen.parameters.yAxis);
+        configureAbsoluteAxisInfo(device, ABS_MT_TOUCH_MAJOR, "Pressure",
+                & device->touchScreen.parameters.pressureAxis);
+        configureAbsoluteAxisInfo(device, ABS_MT_WIDTH_MAJOR, "Size",
+                & device->touchScreen.parameters.sizeAxis);
+    } else if (device->isSingleTouchScreen()) {
+        configureAbsoluteAxisInfo(device, ABS_X, "X",
+                & device->touchScreen.parameters.xAxis);
+        configureAbsoluteAxisInfo(device, ABS_Y, "Y",
+                & device->touchScreen.parameters.yAxis);
+        configureAbsoluteAxisInfo(device, ABS_PRESSURE, "Pressure",
+                & device->touchScreen.parameters.pressureAxis);
+        configureAbsoluteAxisInfo(device, ABS_TOOL_WIDTH, "Size",
+                & device->touchScreen.parameters.sizeAxis);
+    }
+
+    if (device->isTouchScreen()) {
+        device->touchScreen.parameters.useBadTouchFilter =
+                mPolicy->filterTouchEvents();
+        device->touchScreen.parameters.useAveragingTouchFilter =
+                mPolicy->filterTouchEvents();
+        device->touchScreen.parameters.useJumpyTouchFilter =
+                mPolicy->filterJumpyTouchEvents();
+
+        device->touchScreen.precalculated.pressureScale =
+                1.0f / device->touchScreen.parameters.pressureAxis.range;
+        device->touchScreen.precalculated.sizeScale =
+                1.0f / device->touchScreen.parameters.sizeAxis.range;
+    }
+
+    if (device->isTrackball()) {
+        device->trackball.precalculated.xPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
+        device->trackball.precalculated.yPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
+        device->trackball.precalculated.xScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
+        device->trackball.precalculated.yScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
+    }
+
+    configureDeviceForCurrentDisplaySize(device);
+}
+
+void InputReader::configureDeviceForCurrentDisplaySize(InputDevice* device) {
+    if (device->isTouchScreen()) {
+        if (mDisplayWidth < 0) {
+            LOGD("Skipping part of touch screen configuration since display size is unknown.");
+        } else {
+            LOGI("Device configured: id=0x%x, name=%s (display size was changed)", device->id,
+                    device->name.string());
+            configureVirtualKeys(device);
+
+            device->touchScreen.precalculated.xScale =
+                    float(mDisplayWidth) / device->touchScreen.parameters.xAxis.range;
+            device->touchScreen.precalculated.yScale =
+                    float(mDisplayHeight) / device->touchScreen.parameters.yAxis.range;
+        }
+    }
+}
+
+void InputReader::configureVirtualKeys(InputDevice* device) {
+    device->touchScreen.virtualKeys.clear();
+
+    Vector<InputDispatchPolicyInterface::VirtualKeyDefinition> virtualKeyDefinitions;
+    mPolicy->getVirtualKeyDefinitions(device->name, virtualKeyDefinitions);
+    if (virtualKeyDefinitions.size() == 0) {
+        return;
+    }
+
+    device->touchScreen.virtualKeys.setCapacity(virtualKeyDefinitions.size());
+
+    int32_t touchScreenLeft = device->touchScreen.parameters.xAxis.minValue;
+    int32_t touchScreenTop = device->touchScreen.parameters.yAxis.minValue;
+    int32_t touchScreenWidth = device->touchScreen.parameters.xAxis.range;
+    int32_t touchScreenHeight = device->touchScreen.parameters.yAxis.range;
+
+    for (size_t i = 0; i < virtualKeyDefinitions.size(); i++) {
+        const InputDispatchPolicyInterface::VirtualKeyDefinition& virtualKeyDefinition =
+                virtualKeyDefinitions[i];
+
+        device->touchScreen.virtualKeys.add();
+        InputDevice::VirtualKey& virtualKey =
+                device->touchScreen.virtualKeys.editTop();
+
+        virtualKey.scanCode = virtualKeyDefinition.scanCode;
+        int32_t keyCode;
+        uint32_t flags;
+        if (mEventHub->scancodeToKeycode(device->id, virtualKey.scanCode,
+                & keyCode, & flags)) {
+            LOGI("  VirtualKey %d: could not obtain key code, ignoring", virtualKey.scanCode);
+            device->touchScreen.virtualKeys.pop(); // drop the key
+            continue;
+        }
+
+        virtualKey.keyCode = keyCode;
+        virtualKey.flags = flags;
+
+        // convert the key definition's display coordinates into touch coordinates for a hit box
+        int32_t halfWidth = virtualKeyDefinition.width / 2;
+        int32_t halfHeight = virtualKeyDefinition.height / 2;
+
+        virtualKey.hitLeft = (virtualKeyDefinition.centerX - halfWidth)
+                * touchScreenWidth / mDisplayWidth + touchScreenLeft;
+        virtualKey.hitRight= (virtualKeyDefinition.centerX + halfWidth)
+                * touchScreenWidth / mDisplayWidth + touchScreenLeft;
+        virtualKey.hitTop = (virtualKeyDefinition.centerY - halfHeight)
+                * touchScreenHeight / mDisplayHeight + touchScreenTop;
+        virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight)
+                * touchScreenHeight / mDisplayHeight + touchScreenTop;
+
+        LOGI("  VirtualKey %d: keyCode=%d hitLeft=%d hitRight=%d hitTop=%d hitBottom=%d",
+                virtualKey.scanCode, virtualKey.keyCode,
+                virtualKey.hitLeft, virtualKey.hitRight, virtualKey.hitTop, virtualKey.hitBottom);
+    }
+}
+
+void InputReader::configureAbsoluteAxisInfo(InputDevice* device,
+        int axis, const char* name, InputDevice::AbsoluteAxisInfo* out) {
+    if (! mEventHub->getAbsoluteInfo(device->id, axis,
+            & out->minValue, & out->maxValue, & out->flat, &out->fuzz)) {
+        out->range = out->maxValue - out->minValue;
+        if (out->range != 0) {
+            LOGI("  %s: min=%d max=%d flat=%d fuzz=%d",
+                    name, out->minValue, out->maxValue, out->flat, out->fuzz);
+            return;
+        }
+    }
+
+    out->minValue = 0;
+    out->maxValue = 0;
+    out->flat = 0;
+    out->fuzz = 0;
+    out->range = 0;
+    LOGI("  %s: unknown axis values, setting to zero", name);
+}
+
+void InputReader::resetGlobalMetaState() {
+    mGlobalMetaState = -1;
+}
+
+int32_t InputReader::globalMetaState() {
+    if (mGlobalMetaState == -1) {
+        mGlobalMetaState = 0;
+        for (size_t i = 0; i < mDevices.size(); i++) {
+            InputDevice* device = mDevices.valueAt(i);
+            if (device->isKeyboard()) {
+                mGlobalMetaState |= device->keyboard.current.metaState;
+            }
+        }
+    }
+    return mGlobalMetaState;
+}
+
+void InputReader::updateGlobalVirtualKeyState() {
+    int32_t keyCode = -1, scanCode = -1;
+
+    for (size_t i = 0; i < mDevices.size(); i++) {
+        InputDevice* device = mDevices.valueAt(i);
+        if (device->isTouchScreen()) {
+            if (device->touchScreen.currentVirtualKey.down) {
+                keyCode = device->touchScreen.currentVirtualKey.keyCode;
+                scanCode = device->touchScreen.currentVirtualKey.scanCode;
+            }
+        }
+    }
+
+    {
+        AutoMutex _l(mExportedStateLock);
+
+        mGlobalVirtualKeyCode = keyCode;
+        mGlobalVirtualScanCode = scanCode;
+    }
+}
+
+bool InputReader::getCurrentVirtualKey(int32_t* outKeyCode, int32_t* outScanCode) const {
+    AutoMutex _l(mExportedStateLock);
+
+    *outKeyCode = mGlobalVirtualKeyCode;
+    *outScanCode = mGlobalVirtualScanCode;
+    return mGlobalVirtualKeyCode != -1;
+}
+
+
+// --- InputReaderThread ---
+
+InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) :
+        Thread(/*canCallJava*/ true), mReader(reader) {
+}
+
+InputReaderThread::~InputReaderThread() {
+}
+
+bool InputReaderThread::threadLoop() {
+    mReader->loopOnce();
+    return true;
+}
+
+} // namespace android
diff --git a/libs/ui/InputTransport.cpp b/libs/ui/InputTransport.cpp
new file mode 100644
index 0000000..a24180f
--- /dev/null
+++ b/libs/ui/InputTransport.cpp
@@ -0,0 +1,684 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+// Provides a shared memory transport for input events.
+//
+#define LOG_TAG "InputTransport"
+
+//#define LOG_NDEBUG 0
+
+// Log debug messages about channel signalling (send signal, receive signal)
+#define DEBUG_CHANNEL_SIGNALS 1
+
+// Log debug messages whenever InputChannel objects are created/destroyed
+#define DEBUG_CHANNEL_LIFECYCLE 1
+
+// Log debug messages about transport actions (initialize, reset, publish, ...)
+#define DEBUG_TRANSPORT_ACTIONS 1
+
+
+#include <cutils/ashmem.h>
+#include <cutils/log.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <ui/InputTransport.h>
+#include <unistd.h>
+
+namespace android {
+
+// Must be at least sizeof(InputMessage) + sufficient space for pointer data
+static const int DEFAULT_MESSAGE_BUFFER_SIZE = 16384;
+
+// Signal sent by the producer to the consumer to inform it that a new message is
+// available to be consumed in the shared memory buffer.
+static const char INPUT_SIGNAL_DISPATCH = 'D';
+
+// Signal sent by the consumer to the producer to inform it that it has finished
+// consuming the most recent message.
+static const char INPUT_SIGNAL_FINISHED = 'f';
+
+
+// --- InputChannel ---
+
+InputChannel::InputChannel(const String8& name, int32_t ashmemFd, int32_t receivePipeFd,
+        int32_t sendPipeFd) :
+        mName(name), mAshmemFd(ashmemFd), mReceivePipeFd(receivePipeFd), mSendPipeFd(sendPipeFd) {
+#if DEBUG_CHANNEL_LIFECYCLE
+    LOGD("Input channel constructed: name='%s', ashmemFd=%d, receivePipeFd=%d, sendPipeFd=%d",
+            mName.string(), ashmemFd, receivePipeFd, sendPipeFd);
+#endif
+
+    int result = fcntl(mReceivePipeFd, F_SETFL, O_NONBLOCK);
+    LOG_ALWAYS_FATAL_IF(result != 0, "channel '%s' ~ Could not make receive pipe "
+            "non-blocking.  errno=%d", mName.string(), errno);
+
+    result = fcntl(mSendPipeFd, F_SETFL, O_NONBLOCK);
+    LOG_ALWAYS_FATAL_IF(result != 0, "channel '%s' ~ Could not make send pipe "
+            "non-blocking.  errno=%d", mName.string(), errno);
+}
+
+InputChannel::~InputChannel() {
+#if DEBUG_CHANNEL_LIFECYCLE
+    LOGD("Input channel destroyed: name='%s', ashmemFd=%d, receivePipeFd=%d, sendPipeFd=%d",
+            mName.string(), mAshmemFd, mReceivePipeFd, mSendPipeFd);
+#endif
+
+    ::close(mAshmemFd);
+    ::close(mReceivePipeFd);
+    ::close(mSendPipeFd);
+}
+
+status_t InputChannel::openInputChannelPair(const String8& name,
+        InputChannel** outServerChannel, InputChannel** outClientChannel) {
+    status_t result;
+
+    int serverAshmemFd = ashmem_create_region(name.string(), DEFAULT_MESSAGE_BUFFER_SIZE);
+    if (serverAshmemFd < 0) {
+        result = -errno;
+        LOGE("channel '%s' ~ Could not create shared memory region. errno=%d",
+                name.string(), errno);
+    } else {
+        result = ashmem_set_prot_region(serverAshmemFd, PROT_READ | PROT_WRITE);
+        if (result < 0) {
+            LOGE("channel '%s' ~ Error %d trying to set protection of ashmem fd %d.",
+                    name.string(), result, serverAshmemFd);
+        } else {
+            // Dup the file descriptor because the server and client input channel objects that
+            // are returned may have different lifetimes but they share the same shared memory region.
+            int clientAshmemFd;
+            clientAshmemFd = dup(serverAshmemFd);
+            if (clientAshmemFd < 0) {
+                result = -errno;
+                LOGE("channel '%s' ~ Could not dup() shared memory region fd. errno=%d",
+                        name.string(), errno);
+            } else {
+                int forward[2];
+                if (pipe(forward)) {
+                    result = -errno;
+                    LOGE("channel '%s' ~ Could not create forward pipe.  errno=%d",
+                            name.string(), errno);
+                } else {
+                    int reverse[2];
+                    if (pipe(reverse)) {
+                        result = -errno;
+                        LOGE("channel '%s' ~ Could not create reverse pipe.  errno=%d",
+                                name.string(), errno);
+                    } else {
+                        String8 serverChannelName = name;
+                        serverChannelName.append(" (server)");
+                        *outServerChannel = new InputChannel(serverChannelName,
+                                serverAshmemFd, reverse[0], forward[1]);
+
+                        String8 clientChannelName = name;
+                        clientChannelName.append(" (client)");
+                        *outClientChannel = new InputChannel(clientChannelName,
+                                clientAshmemFd, forward[0], reverse[1]);
+                        return OK;
+                    }
+                    ::close(forward[0]);
+                    ::close(forward[1]);
+                }
+                ::close(clientAshmemFd);
+            }
+        }
+        ::close(serverAshmemFd);
+    }
+
+    *outServerChannel = NULL;
+    *outClientChannel = NULL;
+    return result;
+}
+
+status_t InputChannel::sendSignal(char signal) {
+    ssize_t nWrite = ::write(mSendPipeFd, & signal, 1);
+
+    if (nWrite == 1) {
+#if DEBUG_CHANNEL_SIGNALS
+        LOGD("channel '%s' ~ sent signal '%c'", mName.string(), signal);
+#endif
+        return OK;
+    }
+
+#if DEBUG_CHANNEL_SIGNALS
+    LOGD("channel '%s' ~ error sending signal '%c', errno=%d", mName.string(), signal, errno);
+#endif
+    return -errno;
+}
+
+status_t InputChannel::receiveSignal(char* outSignal) {
+    ssize_t nRead = ::read(mReceivePipeFd, outSignal, 1);
+    if (nRead == 1) {
+#if DEBUG_CHANNEL_SIGNALS
+        LOGD("channel '%s' ~ received signal '%c'", mName.string(), *outSignal);
+#endif
+        return OK;
+    }
+
+    if (errno == EAGAIN) {
+#if DEBUG_CHANNEL_SIGNALS
+        LOGD("channel '%s' ~ receive signal failed because no signal available", mName.string());
+#endif
+        return WOULD_BLOCK;
+    }
+
+#if DEBUG_CHANNEL_SIGNALS
+    LOGD("channel '%s' ~ receive signal failed, errno=%d", mName.string(), errno);
+#endif
+    return -errno;
+}
+
+
+// --- InputPublisher ---
+
+InputPublisher::InputPublisher(const sp<InputChannel>& channel) :
+        mChannel(channel), mSharedMessage(NULL),
+        mPinned(false), mSemaphoreInitialized(false), mWasDispatched(false),
+        mMotionEventSampleDataTail(NULL) {
+}
+
+InputPublisher::~InputPublisher() {
+    reset();
+
+    if (mSharedMessage) {
+        munmap(mSharedMessage, mAshmemSize);
+    }
+}
+
+status_t InputPublisher::initialize() {
+#if DEBUG_TRANSPORT_ACTIONS
+    LOGD("channel '%s' publisher ~ initialize",
+            mChannel->getName().string());
+#endif
+
+    int ashmemFd = mChannel->getAshmemFd();
+    int result = ashmem_get_size_region(ashmemFd);
+    if (result < 0) {
+        LOGE("channel '%s' publisher ~ Error %d getting size of ashmem fd %d.",
+                mChannel->getName().string(), result, ashmemFd);
+        return UNKNOWN_ERROR;
+    }
+    mAshmemSize = (size_t) result;
+
+    mSharedMessage = static_cast<InputMessage*>(mmap(NULL, mAshmemSize,
+            PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0));
+    if (! mSharedMessage) {
+        LOGE("channel '%s' publisher ~ mmap failed on ashmem fd %d.",
+                mChannel->getName().string(), ashmemFd);
+        return NO_MEMORY;
+    }
+
+    mPinned = true;
+    mSharedMessage->consumed = false;
+
+    return reset();
+}
+
+status_t InputPublisher::reset() {
+#if DEBUG_TRANSPORT_ACTIONS
+    LOGD("channel '%s' publisher ~ reset",
+        mChannel->getName().string());
+#endif
+
+    if (mPinned) {
+        // Destroy the semaphore since we are about to unpin the memory region that contains it.
+        int result;
+        if (mSemaphoreInitialized) {
+            if (mSharedMessage->consumed) {
+                result = sem_post(& mSharedMessage->semaphore);
+                if (result < 0) {
+                    LOGE("channel '%s' publisher ~ Error %d in sem_post.",
+                            mChannel->getName().string(), errno);
+                    return UNKNOWN_ERROR;
+                }
+            }
+
+            result = sem_destroy(& mSharedMessage->semaphore);
+            if (result < 0) {
+                LOGE("channel '%s' publisher ~ Error %d in sem_destroy.",
+                        mChannel->getName().string(), errno);
+                return UNKNOWN_ERROR;
+            }
+
+            mSemaphoreInitialized = false;
+        }
+
+        // Unpin the region since we no longer care about its contents.
+        int ashmemFd = mChannel->getAshmemFd();
+        result = ashmem_unpin_region(ashmemFd, 0, 0);
+        if (result < 0) {
+            LOGE("channel '%s' publisher ~ Error %d unpinning ashmem fd %d.",
+                    mChannel->getName().string(), result, ashmemFd);
+            return UNKNOWN_ERROR;
+        }
+
+        mPinned = false;
+    }
+
+    mMotionEventSampleDataTail = NULL;
+    mWasDispatched = false;
+    return OK;
+}
+
+status_t InputPublisher::publishInputEvent(
+        int32_t type,
+        int32_t deviceId,
+        int32_t nature) {
+    if (mPinned) {
+        LOGE("channel '%s' publisher ~ Attempted to publish a new event but publisher has "
+                "not yet been reset.", mChannel->getName().string());
+        return INVALID_OPERATION;
+    }
+
+    // Pin the region.
+    // We do not check for ASHMEM_NOT_PURGED because we don't care about the previous
+    // contents of the buffer so it does not matter whether it was purged in the meantime.
+    int ashmemFd = mChannel->getAshmemFd();
+    int result = ashmem_pin_region(ashmemFd, 0, 0);
+    if (result < 0) {
+        LOGE("channel '%s' publisher ~ Error %d pinning ashmem fd %d.",
+                mChannel->getName().string(), result, ashmemFd);
+        return UNKNOWN_ERROR;
+    }
+
+    mPinned = true;
+
+    result = sem_init(& mSharedMessage->semaphore, 1, 1);
+    if (result < 0) {
+        LOGE("channel '%s' publisher ~ Error %d in sem_init.",
+                mChannel->getName().string(), errno);
+        return UNKNOWN_ERROR;
+    }
+
+    mSemaphoreInitialized = true;
+
+    mSharedMessage->consumed = false;
+    mSharedMessage->type = type;
+    mSharedMessage->deviceId = deviceId;
+    mSharedMessage->nature = nature;
+    return OK;
+}
+
+status_t InputPublisher::publishKeyEvent(
+        int32_t deviceId,
+        int32_t nature,
+        int32_t action,
+        int32_t flags,
+        int32_t keyCode,
+        int32_t scanCode,
+        int32_t metaState,
+        int32_t repeatCount,
+        nsecs_t downTime,
+        nsecs_t eventTime) {
+#if DEBUG_TRANSPORT_ACTIONS
+    LOGD("channel '%s' publisher ~ publishKeyEvent: deviceId=%d, nature=%d, "
+            "action=%d, flags=%d, keyCode=%d, scanCode=%d, metaState=%d, repeatCount=%d,"
+            "downTime=%lld, eventTime=%lld",
+            mChannel->getName().string(),
+            deviceId, nature, action, flags, keyCode, scanCode, metaState, repeatCount,
+            downTime, eventTime);
+#endif
+
+    status_t result = publishInputEvent(INPUT_EVENT_TYPE_KEY, deviceId, nature);
+    if (result < 0) {
+        return result;
+    }
+
+    mSharedMessage->key.action = action;
+    mSharedMessage->key.flags = flags;
+    mSharedMessage->key.keyCode = keyCode;
+    mSharedMessage->key.scanCode = scanCode;
+    mSharedMessage->key.metaState = metaState;
+    mSharedMessage->key.repeatCount = repeatCount;
+    mSharedMessage->key.downTime = downTime;
+    mSharedMessage->key.eventTime = eventTime;
+    return OK;
+}
+
+status_t InputPublisher::publishMotionEvent(
+        int32_t deviceId,
+        int32_t nature,
+        int32_t action,
+        int32_t edgeFlags,
+        int32_t metaState,
+        float xOffset,
+        float yOffset,
+        float xPrecision,
+        float yPrecision,
+        nsecs_t downTime,
+        nsecs_t eventTime,
+        size_t pointerCount,
+        const int32_t* pointerIds,
+        const PointerCoords* pointerCoords) {
+#if DEBUG_TRANSPORT_ACTIONS
+    LOGD("channel '%s' publisher ~ publishMotionEvent: deviceId=%d, nature=%d, "
+            "action=%d, edgeFlags=%d, metaState=%d, xOffset=%f, yOffset=%f, "
+            "xPrecision=%f, yPrecision=%f, downTime=%lld, eventTime=%lld, "
+            "pointerCount=%d",
+            mChannel->getName().string(),
+            deviceId, nature, action, edgeFlags, metaState, xOffset, yOffset,
+            xPrecision, yPrecision, downTime, eventTime, pointerCount);
+#endif
+
+    if (pointerCount > MAX_POINTERS || pointerCount < 1) {
+        LOGE("channel '%s' publisher ~ Invalid number of pointers provided: %d.",
+                mChannel->getName().string(), pointerCount);
+        return BAD_VALUE;
+    }
+
+    status_t result = publishInputEvent(INPUT_EVENT_TYPE_MOTION, deviceId, nature);
+    if (result < 0) {
+        return result;
+    }
+
+    mSharedMessage->motion.action = action;
+    mSharedMessage->motion.edgeFlags = edgeFlags;
+    mSharedMessage->motion.metaState = metaState;
+    mSharedMessage->motion.xOffset = xOffset;
+    mSharedMessage->motion.yOffset = yOffset;
+    mSharedMessage->motion.xPrecision = xPrecision;
+    mSharedMessage->motion.yPrecision = yPrecision;
+    mSharedMessage->motion.downTime = downTime;
+    mSharedMessage->motion.pointerCount = pointerCount;
+
+    mSharedMessage->motion.sampleCount = 1;
+    mSharedMessage->motion.sampleData[0].eventTime = eventTime;
+
+    for (size_t i = 0; i < pointerCount; i++) {
+        mSharedMessage->motion.pointerIds[i] = pointerIds[i];
+        mSharedMessage->motion.sampleData[0].coords[i] = pointerCoords[i];
+    }
+
+    // Cache essential information about the motion event to ensure that a malicious consumer
+    // cannot confuse the publisher by modifying the contents of the shared memory buffer while
+    // it is being updated.
+    if (action == MOTION_EVENT_ACTION_MOVE) {
+        mMotionEventPointerCount = pointerCount;
+        mMotionEventSampleDataStride = InputMessage::sampleDataStride(pointerCount);
+        mMotionEventSampleDataTail = InputMessage::sampleDataPtrIncrement(
+                mSharedMessage->motion.sampleData, mMotionEventSampleDataStride);
+    } else {
+        mMotionEventSampleDataTail = NULL;
+    }
+    return OK;
+}
+
+status_t InputPublisher::appendMotionSample(
+        nsecs_t eventTime,
+        const PointerCoords* pointerCoords) {
+#if DEBUG_TRANSPORT_ACTIONS
+    LOGD("channel '%s' publisher ~ appendMotionSample: eventTime=%lld",
+            mChannel->getName().string(), eventTime);
+#endif
+
+    if (! mPinned || ! mMotionEventSampleDataTail) {
+        LOGE("channel '%s' publisher ~ Cannot append motion sample because there is no current "
+                "MOTION_EVENT_ACTION_MOVE event.", mChannel->getName().string());
+        return INVALID_OPERATION;
+    }
+
+    InputMessage::SampleData* newTail = InputMessage::sampleDataPtrIncrement(
+            mMotionEventSampleDataTail, mMotionEventSampleDataStride);
+    size_t newBytesUsed = reinterpret_cast<char*>(newTail) -
+            reinterpret_cast<char*>(mSharedMessage);
+
+    if (newBytesUsed > mAshmemSize) {
+        LOGD("channel '%s' publisher ~ Cannot append motion sample because the shared memory "
+                "buffer is full.  Buffer size: %d bytes, pointers: %d, samples: %d",
+                mChannel->getName().string(),
+                mAshmemSize, mMotionEventPointerCount, mSharedMessage->motion.sampleCount);
+        return NO_MEMORY;
+    }
+
+    int result;
+    if (mWasDispatched) {
+        result = sem_trywait(& mSharedMessage->semaphore);
+        if (result < 0) {
+            if (errno == EAGAIN) {
+                // Only possible source of contention is the consumer having consumed (or being in the
+                // process of consuming) the message and left the semaphore count at 0.
+                LOGD("channel '%s' publisher ~ Cannot append motion sample because the message has "
+                        "already been consumed.", mChannel->getName().string());
+                return FAILED_TRANSACTION;
+            } else {
+                LOGE("channel '%s' publisher ~ Error %d in sem_trywait.",
+                        mChannel->getName().string(), errno);
+                return UNKNOWN_ERROR;
+            }
+        }
+    }
+
+    mMotionEventSampleDataTail->eventTime = eventTime;
+    for (size_t i = 0; i < mMotionEventPointerCount; i++) {
+        mMotionEventSampleDataTail->coords[i] = pointerCoords[i];
+    }
+    mMotionEventSampleDataTail = newTail;
+
+    mSharedMessage->motion.sampleCount += 1;
+
+    if (mWasDispatched) {
+        result = sem_post(& mSharedMessage->semaphore);
+        if (result < 0) {
+            LOGE("channel '%s' publisher ~ Error %d in sem_post.",
+                    mChannel->getName().string(), errno);
+            return UNKNOWN_ERROR;
+        }
+    }
+    return OK;
+}
+
+status_t InputPublisher::sendDispatchSignal() {
+#if DEBUG_TRANSPORT_ACTIONS
+    LOGD("channel '%s' publisher ~ sendDispatchSignal",
+            mChannel->getName().string());
+#endif
+
+    mWasDispatched = true;
+    return mChannel->sendSignal(INPUT_SIGNAL_DISPATCH);
+}
+
+status_t InputPublisher::receiveFinishedSignal() {
+#if DEBUG_TRANSPORT_ACTIONS
+    LOGD("channel '%s' publisher ~ receiveFinishedSignal",
+            mChannel->getName().string());
+#endif
+
+    char signal;
+    status_t result = mChannel->receiveSignal(& signal);
+    if (result) {
+        return result;
+    }
+    if (signal != INPUT_SIGNAL_FINISHED) {
+        LOGE("channel '%s' publisher ~ Received unexpected signal '%c' from consumer",
+                mChannel->getName().string(), signal);
+        return UNKNOWN_ERROR;
+    }
+    return OK;
+}
+
+// --- InputConsumer ---
+
+InputConsumer::InputConsumer(const sp<InputChannel>& channel) :
+        mChannel(channel), mSharedMessage(NULL) {
+}
+
+InputConsumer::~InputConsumer() {
+    if (mSharedMessage) {
+        munmap(mSharedMessage, mAshmemSize);
+    }
+}
+
+status_t InputConsumer::initialize() {
+#if DEBUG_TRANSPORT_ACTIONS
+    LOGD("channel '%s' consumer ~ initialize",
+            mChannel->getName().string());
+#endif
+
+    int ashmemFd = mChannel->getAshmemFd();
+    int result = ashmem_get_size_region(ashmemFd);
+    if (result < 0) {
+        LOGE("channel '%s' consumer ~ Error %d getting size of ashmem fd %d.",
+                mChannel->getName().string(), result, ashmemFd);
+        return UNKNOWN_ERROR;
+    }
+
+    mAshmemSize = (size_t) result;
+
+    mSharedMessage = static_cast<InputMessage*>(mmap(NULL, mAshmemSize,
+            PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0));
+    if (! mSharedMessage) {
+        LOGE("channel '%s' consumer ~ mmap failed on ashmem fd %d.",
+                mChannel->getName().string(), ashmemFd);
+        return NO_MEMORY;
+    }
+
+    return OK;
+}
+
+status_t InputConsumer::consume(InputEventFactoryInterface* factory, InputEvent** event) {
+#if DEBUG_TRANSPORT_ACTIONS
+    LOGD("channel '%s' consumer ~ consume",
+            mChannel->getName().string());
+#endif
+
+    *event = NULL;
+
+    int ashmemFd = mChannel->getAshmemFd();
+    int result = ashmem_pin_region(ashmemFd, 0, 0);
+    if (result != ASHMEM_NOT_PURGED) {
+        if (result == ASHMEM_WAS_PURGED) {
+            LOGE("channel '%s' consumer ~ Error %d pinning ashmem fd %d because it was purged "
+                    "which probably indicates that the publisher and consumer are out of sync.",
+                    mChannel->getName().string(), result, ashmemFd);
+            return INVALID_OPERATION;
+        }
+
+        LOGE("channel '%s' consumer ~ Error %d pinning ashmem fd %d.",
+                mChannel->getName().string(), result, ashmemFd);
+        return UNKNOWN_ERROR;
+    }
+
+    if (mSharedMessage->consumed) {
+        LOGE("channel '%s' consumer ~ The current message has already been consumed.",
+                mChannel->getName().string());
+        return INVALID_OPERATION;
+    }
+
+    // Acquire but *never release* the semaphore.  Contention on the semaphore is used to signal
+    // to the publisher that the message has been consumed (or is in the process of being
+    // consumed).  Eventually the publisher will reinitialize the semaphore for the next message.
+    result = sem_wait(& mSharedMessage->semaphore);
+    if (result < 0) {
+        LOGE("channel '%s' consumer ~ Error %d in sem_wait.",
+                mChannel->getName().string(), errno);
+        return UNKNOWN_ERROR;
+    }
+
+    mSharedMessage->consumed = true;
+
+    switch (mSharedMessage->type) {
+    case INPUT_EVENT_TYPE_KEY: {
+        KeyEvent* keyEvent = factory->createKeyEvent();
+        if (! keyEvent) return NO_MEMORY;
+
+        populateKeyEvent(keyEvent);
+
+        *event = keyEvent;
+        break;
+    }
+
+    case INPUT_EVENT_TYPE_MOTION: {
+        MotionEvent* motionEvent = factory->createMotionEvent();
+        if (! motionEvent) return NO_MEMORY;
+
+        populateMotionEvent(motionEvent);
+
+        *event = motionEvent;
+        break;
+    }
+
+    default:
+        LOGE("channel '%s' consumer ~ Received message of unknown type %d",
+                mChannel->getName().string(), mSharedMessage->type);
+        return UNKNOWN_ERROR;
+    }
+
+    return OK;
+}
+
+status_t InputConsumer::sendFinishedSignal() {
+#if DEBUG_TRANSPORT_ACTIONS
+    LOGD("channel '%s' consumer ~ sendFinishedSignal",
+            mChannel->getName().string());
+#endif
+
+    return mChannel->sendSignal(INPUT_SIGNAL_FINISHED);
+}
+
+status_t InputConsumer::receiveDispatchSignal() {
+#if DEBUG_TRANSPORT_ACTIONS
+    LOGD("channel '%s' consumer ~ receiveDispatchSignal",
+            mChannel->getName().string());
+#endif
+
+    char signal;
+    status_t result = mChannel->receiveSignal(& signal);
+    if (result) {
+        return result;
+    }
+    if (signal != INPUT_SIGNAL_DISPATCH) {
+        LOGE("channel '%s' consumer ~ Received unexpected signal '%c' from publisher",
+                mChannel->getName().string(), signal);
+        return UNKNOWN_ERROR;
+    }
+    return OK;
+}
+
+void InputConsumer::populateKeyEvent(KeyEvent* keyEvent) const {
+    keyEvent->initialize(
+            mSharedMessage->deviceId,
+            mSharedMessage->nature,
+            mSharedMessage->key.action,
+            mSharedMessage->key.flags,
+            mSharedMessage->key.keyCode,
+            mSharedMessage->key.scanCode,
+            mSharedMessage->key.metaState,
+            mSharedMessage->key.repeatCount,
+            mSharedMessage->key.downTime,
+            mSharedMessage->key.eventTime);
+}
+
+void InputConsumer::populateMotionEvent(MotionEvent* motionEvent) const {
+    motionEvent->initialize(
+            mSharedMessage->deviceId,
+            mSharedMessage->nature,
+            mSharedMessage->motion.action,
+            mSharedMessage->motion.edgeFlags,
+            mSharedMessage->motion.metaState,
+            mSharedMessage->motion.sampleData[0].coords[0].x,
+            mSharedMessage->motion.sampleData[0].coords[0].y,
+            mSharedMessage->motion.xPrecision,
+            mSharedMessage->motion.yPrecision,
+            mSharedMessage->motion.downTime,
+            mSharedMessage->motion.sampleData[0].eventTime,
+            mSharedMessage->motion.pointerCount,
+            mSharedMessage->motion.pointerIds,
+            mSharedMessage->motion.sampleData[0].coords);
+
+    size_t sampleCount = mSharedMessage->motion.sampleCount;
+    if (sampleCount > 1) {
+        InputMessage::SampleData* sampleData = mSharedMessage->motion.sampleData;
+        size_t sampleDataStride = InputMessage::sampleDataStride(
+                mSharedMessage->motion.pointerCount);
+
+        while (--sampleCount > 0) {
+            sampleData = InputMessage::sampleDataPtrIncrement(sampleData, sampleDataStride);
+            motionEvent->addSample(sampleData->eventTime, sampleData->coords);
+        }
+    }
+
+    motionEvent->offsetLocation(mSharedMessage->motion.xOffset,
+            mSharedMessage->motion.yOffset);
+}
+
+} // namespace android
diff --git a/libs/ui/tests/Android.mk b/libs/ui/tests/Android.mk
index 6cc4a5a..018f18d 100644
--- a/libs/ui/tests/Android.mk
+++ b/libs/ui/tests/Android.mk
@@ -1,16 +1,38 @@
+# Build the unit tests.
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES:= \
-	region.cpp
+test_src_files := \
+    InputDispatcher_test.cpp
 
 LOCAL_SHARED_LIBRARIES := \
 	libcutils \
 	libutils \
-    libui
+	libEGL \
+	libbinder \
+	libpixelflinger \
+	libhardware \
+	libhardware_legacy \
+	libui \
+	libstlport
 
-LOCAL_MODULE:= test-region
+LOCAL_STATIC_LIBRARIES := \
+	libgtest \
+	libgtest_main
 
-LOCAL_MODULE_TAGS := tests
+LOCAL_C_INCLUDES := \
+    bionic \
+    bionic/libstdc++/include \
+    external/gtest/include \
+    external/stlport/stlport
 
-include $(BUILD_EXECUTABLE)
+LOCAL_MODULE_TAGS := eng tests
+
+$(foreach file,$(test_src_files), \
+    $(eval LOCAL_SRC_FILES := $(file)) \
+    $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \
+    $(eval include $(BUILD_EXECUTABLE)) \
+)
+
+# Build the manual test programs.
+include $(call all-subdir-makefiles)
diff --git a/libs/ui/tests/InputDispatcher_test.cpp b/libs/ui/tests/InputDispatcher_test.cpp
new file mode 100644
index 0000000..3d92043
--- /dev/null
+++ b/libs/ui/tests/InputDispatcher_test.cpp
@@ -0,0 +1,19 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+
+#include <ui/InputDispatcher.h>
+#include <gtest/gtest.h>
+
+namespace android {
+
+class InputDispatcherTest : public testing::Test {
+public:
+};
+
+TEST_F(InputDispatcherTest, Dummy) {
+    SCOPED_TRACE("Trace");
+    ASSERT_FALSE(true);
+}
+
+} // namespace android
diff --git a/libs/ui/tests/region/Android.mk b/libs/ui/tests/region/Android.mk
new file mode 100644
index 0000000..6cc4a5a
--- /dev/null
+++ b/libs/ui/tests/region/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	region.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+	libcutils \
+	libutils \
+    libui
+
+LOCAL_MODULE:= test-region
+
+LOCAL_MODULE_TAGS := tests
+
+include $(BUILD_EXECUTABLE)
diff --git a/libs/ui/tests/region.cpp b/libs/ui/tests/region/region.cpp
similarity index 100%
rename from libs/ui/tests/region.cpp
rename to libs/ui/tests/region/region.cpp
diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk
index afecdcb..945b039 100644
--- a/libs/utils/Android.mk
+++ b/libs/utils/Android.mk
@@ -26,6 +26,8 @@
 	Debug.cpp \
 	FileMap.cpp \
 	Flattenable.cpp \
+	PollLoop.cpp \
+	Pool.cpp \
 	RefBase.cpp \
 	ResourceTypes.cpp \
 	SharedBuffer.cpp \
diff --git a/libs/utils/PollLoop.cpp b/libs/utils/PollLoop.cpp
new file mode 100644
index 0000000..90a3e8b
--- /dev/null
+++ b/libs/utils/PollLoop.cpp
@@ -0,0 +1,267 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+// A select loop implementation.
+//
+#define LOG_TAG "PollLoop"
+
+//#define LOG_NDEBUG 0
+
+// Debugs poll and wake interactions.
+#define DEBUG_POLL_AND_WAKE 0
+
+// Debugs callback registration and invocation.
+#define DEBUG_CALLBACKS 1
+
+#include <cutils/log.h>
+#include <utils/PollLoop.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+
+namespace android {
+
+PollLoop::PollLoop() :
+        mPolling(false) {
+    openWakePipe();
+}
+
+PollLoop::~PollLoop() {
+    closeWakePipe();
+}
+
+void PollLoop::openWakePipe() {
+    int wakeFds[2];
+    int result = pipe(wakeFds);
+    LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);
+
+    mWakeReadPipeFd = wakeFds[0];
+    mWakeWritePipeFd = wakeFds[1];
+
+    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
+    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d",
+            errno);
+
+    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
+    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",
+            errno);
+
+    // Add the wake pipe to the head of the request list with a null callback.
+    struct pollfd requestedFd;
+    requestedFd.fd = mWakeReadPipeFd;
+    requestedFd.events = POLLIN;
+    mRequestedFds.insertAt(requestedFd, 0);
+
+    RequestedCallback requestedCallback;
+    requestedCallback.callback = NULL;
+    requestedCallback.data = NULL;
+    mRequestedCallbacks.insertAt(requestedCallback, 0);
+}
+
+void PollLoop::closeWakePipe() {
+    close(mWakeReadPipeFd);
+    close(mWakeWritePipeFd);
+
+    // Note: We don't need to remove the poll structure or callback entry because this
+    //       method is currently only called by the destructor.
+}
+
+bool PollLoop::pollOnce(int timeoutMillis) {
+    mLock.lock();
+    mPolling = true;
+    mLock.unlock();
+
+    bool result;
+    size_t requestedCount = mRequestedFds.size();
+
+#if DEBUG_POLL_AND_WAKE
+    LOGD("%p ~ pollOnce - waiting on %d fds", this, requestedCount);
+    for (size_t i = 0; i < requestedCount; i++) {
+        LOGD("  fd %d - events %d", mRequestedFds[i].fd, mRequestedFds[i].events);
+    }
+#endif
+
+    int respondedCount = poll(mRequestedFds.editArray(), requestedCount, timeoutMillis);
+
+    if (respondedCount == 0) {
+        // Timeout
+#if DEBUG_POLL_AND_WAKE
+        LOGD("%p ~ pollOnce - timeout", this);
+#endif
+        result = false;
+        goto Done;
+    }
+
+    if (respondedCount < 0) {
+        // Error
+#if DEBUG_POLL_AND_WAKE
+        LOGD("%p ~ pollOnce - error, errno=%d", this, errno);
+#endif
+        if (errno != EINTR) {
+            LOGW("Poll failed with an unexpected error, errno=%d", errno);
+        }
+        result = false;
+        goto Done;
+    }
+
+#if DEBUG_POLL_AND_WAKE
+    LOGD("%p ~ pollOnce - handling responses from %d fds", this, respondedCount);
+    for (size_t i = 0; i < requestedCount; i++) {
+        LOGD("  fd %d - events %d, revents %d", mRequestedFds[i].fd, mRequestedFds[i].events,
+                mRequestedFds[i].revents);
+    }
+#endif
+
+    mPendingCallbacks.clear();
+    for (size_t i = 0; i < requestedCount; i++) {
+        const struct pollfd& requestedFd = mRequestedFds.itemAt(i);
+
+        short revents = requestedFd.revents;
+        if (revents) {
+            const RequestedCallback& requestedCallback = mRequestedCallbacks.itemAt(i);
+            Callback callback = requestedCallback.callback;
+
+            if (callback) {
+                PendingCallback pendingCallback;
+                pendingCallback.fd = requestedFd.fd;
+                pendingCallback.events = requestedFd.revents;
+                pendingCallback.callback = callback;
+                pendingCallback.data = requestedCallback.data;
+                mPendingCallbacks.push(pendingCallback);
+            } else {
+                if (requestedFd.fd == mWakeReadPipeFd) {
+#if DEBUG_POLL_AND_WAKE
+                    LOGD("%p ~ pollOnce - awoken", this);
+#endif
+                    char buffer[16];
+                    ssize_t nRead;
+                    do {
+                        nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
+                    } while (nRead == sizeof(buffer));
+                } else {
+#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
+                    LOGD("%p ~ pollOnce - fd %d has no callback!", this, requestedFd.fd);
+#endif
+                }
+            }
+
+            respondedCount -= 1;
+            if (respondedCount == 0) {
+                break;
+            }
+        }
+    }
+    result = true;
+
+Done:
+    mLock.lock();
+    mPolling = false;
+    mAwake.broadcast();
+    mLock.unlock();
+
+    if (result) {
+        size_t pendingCount = mPendingCallbacks.size();
+        for (size_t i = 0; i < pendingCount; i++) {
+            const PendingCallback& pendingCallback = mPendingCallbacks.itemAt(i);
+#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
+            LOGD("%p ~ pollOnce - invoking callback for fd %d", this, pendingCallback.fd);
+#endif
+
+            bool keep = pendingCallback.callback(pendingCallback.fd, pendingCallback.events,
+                    pendingCallback.data);
+            if (! keep) {
+                removeCallback(pendingCallback.fd);
+            }
+        }
+    }
+
+#if DEBUG_POLL_AND_WAKE
+    LOGD("%p ~ pollOnce - done", this);
+#endif
+    return result;
+}
+
+void PollLoop::wake() {
+#if DEBUG_POLL_AND_WAKE
+    LOGD("%p ~ wake", this);
+#endif
+
+    ssize_t nWrite = write(mWakeWritePipeFd, "W", 1);
+    if (nWrite != 1) {
+        if (errno != EAGAIN) {
+            LOGW("Could not write wake signal, errno=%d", errno);
+        }
+    }
+}
+
+void PollLoop::setCallback(int fd, int events, Callback callback, void* data) {
+#if DEBUG_CALLBACKS
+    LOGD("%p ~ setCallback - fd=%d, events=%d", this, fd, events);
+#endif
+
+    if (! events || ! callback) {
+        LOGE("Invalid attempt to set a callback with no selected poll events or no callback.");
+        removeCallback(fd);
+        return;
+    }
+
+    wakeAndLock();
+
+    struct pollfd requestedFd;
+    requestedFd.fd = fd;
+    requestedFd.events = events;
+
+    RequestedCallback requestedCallback;
+    requestedCallback.callback = callback;
+    requestedCallback.data = data;
+
+    ssize_t index = getRequestIndexLocked(fd);
+    if (index < 0) {
+        mRequestedFds.push(requestedFd);
+        mRequestedCallbacks.push(requestedCallback);
+    } else {
+        mRequestedFds.replaceAt(requestedFd, size_t(index));
+        mRequestedCallbacks.replaceAt(requestedCallback, size_t(index));
+    }
+
+    mLock.unlock();
+}
+
+bool PollLoop::removeCallback(int fd) {
+#if DEBUG_CALLBACKS
+    LOGD("%p ~ removeCallback - fd=%d", this, fd);
+#endif
+
+    wakeAndLock();
+
+    ssize_t index = getRequestIndexLocked(fd);
+    if (index >= 0) {
+        mRequestedFds.removeAt(size_t(index));
+        mRequestedCallbacks.removeAt(size_t(index));
+    }
+
+    mLock.unlock();
+    return index >= 0;
+}
+
+ssize_t PollLoop::getRequestIndexLocked(int fd) {
+    size_t requestCount = mRequestedFds.size();
+
+    for (size_t i = 0; i < requestCount; i++) {
+        if (mRequestedFds.itemAt(i).fd == fd) {
+            return i;
+        }
+    }
+
+    return -1;
+}
+
+void PollLoop::wakeAndLock() {
+    mLock.lock();
+    while (mPolling) {
+        wake();
+        mAwake.wait(mLock);
+    }
+}
+
+} // namespace android
diff --git a/libs/utils/Pool.cpp b/libs/utils/Pool.cpp
new file mode 100644
index 0000000..8f18cb9
--- /dev/null
+++ b/libs/utils/Pool.cpp
@@ -0,0 +1,37 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+// A simple memory pool.
+//
+#define LOG_TAG "Pool"
+
+//#define LOG_NDEBUG 0
+
+#include <cutils/log.h>
+#include <utils/Pool.h>
+
+#include <stdlib.h>
+
+namespace android {
+
+// TODO Provide a real implementation of a pool.  This is just a stub for initial development.
+
+PoolImpl::PoolImpl(size_t objSize) :
+    mObjSize(objSize) {
+}
+
+PoolImpl::~PoolImpl() {
+}
+
+void* PoolImpl::allocImpl() {
+    void* ptr = malloc(mObjSize);
+    LOG_ALWAYS_FATAL_IF(ptr == NULL, "Cannot allocate new pool object.");
+    return ptr;
+}
+
+void PoolImpl::freeImpl(void* obj) {
+    LOG_ALWAYS_FATAL_IF(obj == NULL, "Caller attempted to free NULL pool object.");
+    return free(obj);
+}
+
+} // namespace android
diff --git a/libs/utils/StopWatch.cpp b/libs/utils/StopWatch.cpp
index 68a1c52..b5dda2f 100644
--- a/libs/utils/StopWatch.cpp
+++ b/libs/utils/StopWatch.cpp
@@ -30,10 +30,9 @@
 
 
 StopWatch::StopWatch(const char *name, int clock, uint32_t flags)
-    :   mName(name), mClock(clock), mFlags(flags),
-        mStartTime(0), mNumLaps(0)
+    :   mName(name), mClock(clock), mFlags(flags)
 {
-    mStartTime = systemTime(mClock);
+    reset();
 }
 
 StopWatch::~StopWatch()
@@ -72,6 +71,12 @@
     return systemTime(mClock) - mStartTime;
 }
 
+void StopWatch::reset()
+{
+    mNumLaps = 0;
+    mStartTime = systemTime(mClock);
+}
+
 
 /*****************************************************************************/
 
diff --git a/libs/utils/VectorImpl.cpp b/libs/utils/VectorImpl.cpp
index 0322af7..b09c6ca 100644
--- a/libs/utils/VectorImpl.cpp
+++ b/libs/utils/VectorImpl.cpp
@@ -108,13 +108,7 @@
 
 ssize_t VectorImpl::insertVectorAt(const VectorImpl& vector, size_t index)
 {
-    if (index > size())
-        return BAD_INDEX;
-    void* where = _grow(index, vector.size());
-    if (where) {
-        _do_copy(where, vector.arrayImpl(), vector.size());
-    }
-    return where ? index : (ssize_t)NO_MEMORY;
+    return insertAt(vector.arrayImpl(), index, vector.size());
 }
 
 ssize_t VectorImpl::appendVector(const VectorImpl& vector)
@@ -226,9 +220,9 @@
     return add(0);
 }
 
-ssize_t VectorImpl::add(const void* item)
+ssize_t VectorImpl::add(const void* item, size_t numItems)
 {
-    return insertAt(item, size());
+    return insertAt(item, size(), numItems);
 }
 
 ssize_t VectorImpl::replaceAt(size_t index)
diff --git a/libs/utils/tests/Android.mk b/libs/utils/tests/Android.mk
new file mode 100644
index 0000000..45e8061
--- /dev/null
+++ b/libs/utils/tests/Android.mk
@@ -0,0 +1,33 @@
+# Build the unit tests.
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+test_src_files := \
+	PollLoop_test.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+	libz \
+	liblog \
+	libcutils \
+	libutils \
+	libstlport
+
+LOCAL_STATIC_LIBRARIES := \
+	libgtest \
+	libgtest_main
+
+LOCAL_C_INCLUDES := \
+    external/zlib \
+    external/icu4c/common \
+    bionic \
+    bionic/libstdc++/include \
+    external/gtest/include \
+    external/stlport/stlport
+
+LOCAL_MODULE_TAGS := eng tests
+
+$(foreach file,$(test_src_files), \
+    $(eval LOCAL_SRC_FILES := $(file)) \
+    $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \
+    $(eval include $(BUILD_EXECUTABLE)) \
+)
diff --git a/libs/utils/tests/PollLoop_test.cpp b/libs/utils/tests/PollLoop_test.cpp
new file mode 100644
index 0000000..6c719c8
--- /dev/null
+++ b/libs/utils/tests/PollLoop_test.cpp
@@ -0,0 +1,398 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+
+#include <utils/PollLoop.h>
+#include <utils/Timers.h>
+#include <utils/StopWatch.h>
+#include <gtest/gtest.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "TestHelpers.h"
+
+// # of milliseconds to fudge stopwatch measurements
+#define TIMING_TOLERANCE_MS 25
+
+namespace android {
+
+class Pipe {
+public:
+    int sendFd;
+    int receiveFd;
+
+    Pipe() {
+        int fds[2];
+        ::pipe(fds);
+
+        receiveFd = fds[0];
+        sendFd = fds[1];
+    }
+
+    ~Pipe() {
+        ::close(sendFd);
+        ::close(receiveFd);
+    }
+
+    bool writeSignal() {
+        return ::write(sendFd, "*", 1) == 1;
+    }
+
+    bool readSignal() {
+        char buf[1];
+        return ::read(receiveFd, buf, 1) == 1;
+    }
+};
+
+class DelayedWake : public DelayedTask {
+    sp<PollLoop> mPollLoop;
+
+public:
+    DelayedWake(int delayMillis, const sp<PollLoop> pollLoop) :
+        DelayedTask(delayMillis), mPollLoop(pollLoop) {
+    }
+
+protected:
+    virtual void doTask() {
+        mPollLoop->wake();
+    }
+};
+
+class DelayedWriteSignal : public DelayedTask {
+    Pipe* mPipe;
+
+public:
+    DelayedWriteSignal(int delayMillis, Pipe* pipe) :
+        DelayedTask(delayMillis), mPipe(pipe) {
+    }
+
+protected:
+    virtual void doTask() {
+        mPipe->writeSignal();
+    }
+};
+
+class CallbackHandler {
+public:
+    void setCallback(const sp<PollLoop>& pollLoop, int fd, int events) {
+        pollLoop->setCallback(fd, events, staticHandler, this);
+    }
+
+protected:
+    virtual ~CallbackHandler() { }
+
+    virtual bool handler(int fd, int events) = 0;
+
+private:
+    static bool staticHandler(int fd, int events, void* data) {
+        return static_cast<CallbackHandler*>(data)->handler(fd, events);
+    }
+};
+
+class StubCallbackHandler : public CallbackHandler {
+public:
+    bool nextResult;
+    int callbackCount;
+
+    int fd;
+    int events;
+
+    StubCallbackHandler(bool nextResult) : nextResult(nextResult),
+            callbackCount(0), fd(-1), events(-1) {
+    }
+
+protected:
+    virtual bool handler(int fd, int events) {
+        callbackCount += 1;
+        this->fd = fd;
+        this->events = events;
+        return nextResult;
+    }
+};
+
+class PollLoopTest : public testing::Test {
+protected:
+    sp<PollLoop> mPollLoop;
+
+    virtual void SetUp() {
+        mPollLoop = new PollLoop();
+    }
+
+    virtual void TearDown() {
+        mPollLoop.clear();
+    }
+};
+
+
+TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndNotAwoken_WaitsForTimeoutAndReturnsFalse) {
+    StopWatch stopWatch("pollOnce");
+    bool result = mPollLoop->pollOnce(100);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal timeout";
+    EXPECT_FALSE(result)
+            << "pollOnce result should be false because timeout occurred";
+}
+
+TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndAwokenBeforeWaiting_ImmediatelyReturnsTrue) {
+    mPollLoop->wake();
+
+    StopWatch stopWatch("pollOnce");
+    bool result = mPollLoop->pollOnce(1000);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. zero because wake() was called before waiting";
+    EXPECT_TRUE(result)
+            << "pollOnce result should be true because loop was awoken";
+}
+
+TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndAwokenWhileWaiting_PromptlyReturnsTrue) {
+    sp<DelayedWake> delayedWake = new DelayedWake(100, mPollLoop);
+    delayedWake->run();
+
+    StopWatch stopWatch("pollOnce");
+    bool result = mPollLoop->pollOnce(1000);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal wake delay";
+    EXPECT_TRUE(result)
+            << "pollOnce result should be true because loop was awoken";
+}
+
+TEST_F(PollLoopTest, PollOnce_WhenZeroTimeoutAndNoRegisteredFDs_ImmediatelyReturnsFalse) {
+    StopWatch stopWatch("pollOnce");
+    bool result = mPollLoop->pollOnce(0);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should be approx. zero";
+    EXPECT_FALSE(result)
+            << "pollOnce result should be false because timeout occurred";
+}
+
+TEST_F(PollLoopTest, PollOnce_WhenZeroTimeoutAndNoSignalledFDs_ImmediatelyReturnsFalse) {
+    Pipe pipe;
+    StubCallbackHandler handler(true);
+
+    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
+
+    StopWatch stopWatch("pollOnce");
+    bool result = mPollLoop->pollOnce(0);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should be approx. zero";
+    EXPECT_FALSE(result)
+            << "pollOnce result should be false because timeout occurred";
+    EXPECT_EQ(0, handler.callbackCount)
+            << "callback should not have been invoked because FD was not signalled";
+}
+
+TEST_F(PollLoopTest, PollOnce_WhenZeroTimeoutAndSignalledFD_ImmediatelyInvokesCallbackAndReturnsTrue) {
+    Pipe pipe;
+    StubCallbackHandler handler(true);
+
+    ASSERT_TRUE(pipe.writeSignal());
+    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
+
+    StopWatch stopWatch("pollOnce");
+    bool result = mPollLoop->pollOnce(0);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should be approx. zero";
+    EXPECT_TRUE(result)
+            << "pollOnce result should be true because FD was signalled";
+    EXPECT_EQ(1, handler.callbackCount)
+            << "callback should be invoked exactly once";
+    EXPECT_EQ(pipe.receiveFd, handler.fd)
+            << "callback should have received pipe fd as parameter";
+    EXPECT_EQ(POLL_IN, handler.events)
+            << "callback should have received POLL_IN as events";
+}
+
+TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndNoSignalledFDs_WaitsForTimeoutAndReturnsFalse) {
+    Pipe pipe;
+    StubCallbackHandler handler(true);
+
+    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
+
+    StopWatch stopWatch("pollOnce");
+    bool result = mPollLoop->pollOnce(100);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal timeout";
+    EXPECT_FALSE(result)
+            << "pollOnce result should be false because timeout occurred";
+    EXPECT_EQ(0, handler.callbackCount)
+            << "callback should not have been invoked because FD was not signalled";
+}
+
+TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndSignalledFDBeforeWaiting_ImmediatelyInvokesCallbackAndReturnsTrue) {
+    Pipe pipe;
+    StubCallbackHandler handler(true);
+
+    pipe.writeSignal();
+    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
+
+    StopWatch stopWatch("pollOnce");
+    bool result = mPollLoop->pollOnce(100);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    ASSERT_TRUE(pipe.readSignal())
+            << "signal should actually have been written";
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should be approx. zero";
+    EXPECT_TRUE(result)
+            << "pollOnce result should be true because FD was signalled";
+    EXPECT_EQ(1, handler.callbackCount)
+            << "callback should be invoked exactly once";
+    EXPECT_EQ(pipe.receiveFd, handler.fd)
+            << "callback should have received pipe fd as parameter";
+    EXPECT_EQ(POLL_IN, handler.events)
+            << "callback should have received POLL_IN as events";
+}
+
+TEST_F(PollLoopTest, PollOnce_WhenNonZeroTimeoutAndSignalledFDWhileWaiting_PromptlyInvokesCallbackAndReturnsTrue) {
+    Pipe pipe;
+    StubCallbackHandler handler(true);
+    sp<DelayedWriteSignal> delayedWriteSignal = new DelayedWriteSignal(100, & pipe);
+
+    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
+    delayedWriteSignal->run();
+
+    StopWatch stopWatch("pollOnce");
+    bool result = mPollLoop->pollOnce(1000);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    ASSERT_TRUE(pipe.readSignal())
+            << "signal should actually have been written";
+    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal signal delay";
+    EXPECT_TRUE(result)
+            << "pollOnce result should be true because FD was signalled";
+    EXPECT_EQ(1, handler.callbackCount)
+            << "callback should be invoked exactly once";
+    EXPECT_EQ(pipe.receiveFd, handler.fd)
+            << "callback should have received pipe fd as parameter";
+    EXPECT_EQ(POLL_IN, handler.events)
+            << "callback should have received POLL_IN as events";
+}
+
+TEST_F(PollLoopTest, PollOnce_WhenCallbackAddedThenRemoved_CallbackShouldNotBeInvoked) {
+    Pipe pipe;
+    StubCallbackHandler handler(true);
+
+    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
+    pipe.writeSignal(); // would cause FD to be considered signalled
+    mPollLoop->removeCallback(pipe.receiveFd);
+
+    StopWatch stopWatch("pollOnce");
+    bool result = mPollLoop->pollOnce(100);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    ASSERT_TRUE(pipe.readSignal())
+            << "signal should actually have been written";
+    EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal timeout because FD was no longer registered";
+    EXPECT_FALSE(result)
+            << "pollOnce result should be false because timeout occurred";
+    EXPECT_EQ(0, handler.callbackCount)
+            << "callback should not be invoked";
+}
+
+TEST_F(PollLoopTest, PollOnce_WhenCallbackReturnsFalse_CallbackShouldNotBeInvokedAgainLater) {
+    Pipe pipe;
+    StubCallbackHandler handler(false);
+
+    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
+
+    // First loop: Callback is registered and FD is signalled.
+    pipe.writeSignal();
+
+    StopWatch stopWatch("pollOnce");
+    bool result = mPollLoop->pollOnce(0);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    ASSERT_TRUE(pipe.readSignal())
+            << "signal should actually have been written";
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal zero because FD was already signalled";
+    EXPECT_TRUE(result)
+            << "pollOnce result should be true because FD was signalled";
+    EXPECT_EQ(1, handler.callbackCount)
+            << "callback should be invoked";
+
+    // Second loop: Callback is no longer registered and FD is signalled.
+    pipe.writeSignal();
+
+    stopWatch.reset();
+    result = mPollLoop->pollOnce(0);
+    elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    ASSERT_TRUE(pipe.readSignal())
+            << "signal should actually have been written";
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. equal zero because timeout was zero";
+    EXPECT_FALSE(result)
+            << "pollOnce result should be false because timeout occurred";
+    EXPECT_EQ(1, handler.callbackCount)
+            << "callback should not be invoked this time";
+}
+
+TEST_F(PollLoopTest, RemoveCallback_WhenCallbackNotAdded_ReturnsFalse) {
+    bool result = mPollLoop->removeCallback(1);
+
+    EXPECT_FALSE(result)
+            << "removeCallback should return false because FD not registered";
+}
+
+TEST_F(PollLoopTest, RemoveCallback_WhenCallbackAddedThenRemovedTwice_ReturnsTrueFirstTimeAndReturnsFalseSecondTime) {
+    Pipe pipe;
+    StubCallbackHandler handler(false);
+    handler.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
+
+    // First time.
+    bool result = mPollLoop->removeCallback(pipe.receiveFd);
+
+    EXPECT_TRUE(result)
+            << "removeCallback should return true first time because FD was registered";
+
+    // Second time.
+    result = mPollLoop->removeCallback(pipe.receiveFd);
+
+    EXPECT_FALSE(result)
+            << "removeCallback should return false second time because FD was no longer registered";
+}
+
+TEST_F(PollLoopTest, PollOnce_WhenCallbackAddedTwice_OnlySecondCallbackShouldBeInvoked) {
+    Pipe pipe;
+    StubCallbackHandler handler1(true);
+    StubCallbackHandler handler2(true);
+
+    handler1.setCallback(mPollLoop, pipe.receiveFd, POLL_IN);
+    handler2.setCallback(mPollLoop, pipe.receiveFd, POLL_IN); // replace it
+    pipe.writeSignal(); // would cause FD to be considered signalled
+
+    StopWatch stopWatch("pollOnce");
+    bool result = mPollLoop->pollOnce(100);
+    int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime());
+
+    ASSERT_TRUE(pipe.readSignal())
+            << "signal should actually have been written";
+    EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS)
+            << "elapsed time should approx. zero because FD was already signalled";
+    EXPECT_TRUE(result)
+            << "pollOnce result should be true because FD was signalled";
+    EXPECT_EQ(0, handler1.callbackCount)
+            << "original handler callback should not be invoked because it was replaced";
+    EXPECT_EQ(1, handler2.callbackCount)
+            << "replacement handler callback should be invoked";
+}
+
+
+} // namespace android
diff --git a/libs/utils/tests/TestHelpers.h b/libs/utils/tests/TestHelpers.h
new file mode 100644
index 0000000..e55af3c
--- /dev/null
+++ b/libs/utils/tests/TestHelpers.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef TESTHELPERS_H
+#define TESTHELPERS_H
+
+#include <utils/threads.h>
+
+namespace android {
+
+class DelayedTask : public Thread {
+    int mDelayMillis;
+
+public:
+    DelayedTask(int delayMillis) : mDelayMillis(delayMillis) { }
+
+protected:
+    virtual ~DelayedTask() { }
+
+    virtual void doTask() = 0;
+
+    virtual bool threadLoop() {
+        usleep(mDelayMillis * 1000);
+        doTask();
+        return false;
+    }
+};
+
+} // namespace android
+
+#endif // TESTHELPERS_H
diff --git a/native/include/android/input.h b/native/include/android/input.h
new file mode 100644
index 0000000..ee2f664
--- /dev/null
+++ b/native/include/android/input.h
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_INPUT_H
+#define _ANDROID_INPUT_H
+
+/******************************************************************
+ *
+ * IMPORTANT NOTICE:
+ *
+ *   This file is part of Android's set of stable system headers
+ *   exposed by the Android NDK (Native Development Kit).
+ *
+ *   Third-party source AND binary code relies on the definitions
+ *   here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES.
+ *
+ *   - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES)
+ *   - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS
+ *   - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY
+ *   - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES
+ */
+
+/*
+ * Structures and functions to receive and process input events in
+ * native code.
+ *
+ * NOTE: These functions MUST be implemented by /system/lib/libui.so
+ */
+
+#include <sys/types.h>
+#include <android/keycodes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Input device classes.
+ */
+enum {
+    /* The input device is a keyboard. */
+    INPUT_DEVICE_CLASS_KEYBOARD      = 0x00000001,
+
+    /* The input device is an alpha-numeric keyboard (not just a dial pad). */
+    INPUT_DEVICE_CLASS_ALPHAKEY      = 0x00000002,
+
+    /* The input device is a touchscreen (either single-touch or multi-touch). */
+    INPUT_DEVICE_CLASS_TOUCHSCREEN   = 0x00000004,
+
+    /* The input device is a trackball. */
+    INPUT_DEVICE_CLASS_TRACKBALL     = 0x00000008,
+
+    /* The input device is a multi-touch touchscreen. */
+    INPUT_DEVICE_CLASS_TOUCHSCREEN_MT= 0x00000010,
+
+    /* The input device is a directional pad. */
+    INPUT_DEVICE_CLASS_DPAD          = 0x00000020
+};
+
+/*
+ * Key states (may be returned by queries about the current state of a
+ * particular key code, scan code or switch).
+ *
+ * XXX should we call this BUTTON_STATE_XXX?
+ */
+enum {
+    /* The key state is unknown or the requested key itself is not supported. */
+    KEY_STATE_UNKNOWN = -1,
+
+    /* The key is up. */
+    KEY_STATE_UP = 0,
+
+    /* The key is down. */
+    KEY_STATE_DOWN = 1,
+
+    /* The key is down but is a virtual key press that is being emulated by the system. */
+    KEY_STATE_VIRTUAL = 2
+};
+
+/*
+ * Meta key / modifer state.
+ */
+enum {
+    /* No meta keys are pressed. */
+    META_NONE = 0,
+
+    /* This mask is used to check whether one of the ALT meta keys is pressed. */
+    META_ALT_ON = 0x02,
+
+    /* This mask is used to check whether the left ALT meta key is pressed. */
+    META_ALT_LEFT_ON = 0x10,
+
+    /* This mask is used to check whether the right ALT meta key is pressed. */
+    META_ALT_RIGHT_ON = 0x20,
+
+    /* This mask is used to check whether one of the SHIFT meta keys is pressed. */
+    META_SHIFT_ON = 0x01,
+
+    /* This mask is used to check whether the left SHIFT meta key is pressed. */
+    META_SHIFT_LEFT_ON = 0x40,
+
+    /* This mask is used to check whether the right SHIFT meta key is pressed. */
+    META_SHIFT_RIGHT_ON = 0x80,
+
+    /* This mask is used to check whether the SYM meta key is pressed. */
+    META_SYM_ON = 0x04
+};
+
+/*
+ * Input events.
+ *
+ * Input events are opaque structures.  Use the provided accessors functions to
+ * read their properties.
+ */
+struct input_event_t;
+typedef struct input_event_t input_event_t;
+
+/*
+ * Input event types.
+ */
+enum {
+    /* Indicates that the input event is a key event. */
+    INPUT_EVENT_TYPE_KEY = 1,
+
+    /* Indicates that the input event is a motion event. */
+    INPUT_EVENT_TYPE_MOTION = 2
+};
+
+/*
+ * Key event actions.
+ */
+enum {
+    /* The key has been pressed down. */
+    KEY_EVENT_ACTION_DOWN = 0,
+
+    /* The key has been released. */
+    KEY_EVENT_ACTION_UP = 1,
+
+    /* Multiple duplicate key events have occurred in a row, or a complex string is
+     * being delivered.  The repeat_count property of the key event contains the number
+     * of times the given key code should be executed.
+     */
+    KEY_EVENT_ACTION_MULTIPLE = 2
+};
+
+/*
+ * Key event flags.
+ */
+enum {
+    /* This mask is set if the device woke because of this key event. */
+    KEY_EVENT_FLAG_WOKE_HERE = 0x1,
+
+    /* This mask is set if the key event was generated by a software keyboard. */
+    KEY_EVENT_FLAG_SOFT_KEYBOARD = 0x2,
+
+    /* This mask is set if we don't want the key event to cause us to leave touch mode. */
+    KEY_EVENT_FLAG_KEEP_TOUCH_MODE = 0x4,
+
+    /* This mask is set if an event was known to come from a trusted part
+     * of the system.  That is, the event is known to come from the user,
+     * and could not have been spoofed by a third party component. */
+    KEY_EVENT_FLAG_FROM_SYSTEM = 0x8,
+
+    /* This mask is used for compatibility, to identify enter keys that are
+     * coming from an IME whose enter key has been auto-labelled "next" or
+     * "done".  This allows TextView to dispatch these as normal enter keys
+     * for old applications, but still do the appropriate action when
+     * receiving them. */
+    KEY_EVENT_FLAG_EDITOR_ACTION = 0x10,
+
+    /* When associated with up key events, this indicates that the key press
+     * has been canceled.  Typically this is used with virtual touch screen
+     * keys, where the user can slide from the virtual key area on to the
+     * display: in that case, the application will receive a canceled up
+     * event and should not perform the action normally associated with the
+     * key.  Note that for this to work, the application can not perform an
+     * action for a key until it receives an up or the long press timeout has
+     * expired. */
+    KEY_EVENT_FLAG_CANCELED = 0x20,
+
+    /* This key event was generated by a virtual (on-screen) hard key area.
+     * Typically this is an area of the touchscreen, outside of the regular
+     * display, dedicated to "hardware" buttons. */
+    KEY_EVENT_FLAG_VIRTUAL_HARD_KEY = 0x40,
+
+    /* This flag is set for the first key repeat that occurs after the
+     * long press timeout. */
+    KEY_EVENT_FLAG_LONG_PRESS = 0x80,
+
+    /* Set when a key event has KEY_EVENT_FLAG_CANCELED set because a long
+     * press action was executed while it was down. */
+    KEY_EVENT_FLAG_CANCELED_LONG_PRESS = 0x100,
+
+    /* Set for KEY_EVENT_ACTION_UP when this event's key code is still being
+     * tracked from its initial down.  That is, somebody requested that tracking
+     * started on the key down and a long press has not caused
+     * the tracking to be canceled. */
+    KEY_EVENT_FLAG_TRACKING = 0x200
+};
+
+/*
+ * Motion event actions.
+ */
+
+/* Bit shift for the action bits holding the pointer index as
+ * defined by MOTION_EVENT_ACTION_POINTER_INDEX_MASK.
+ */
+#define MOTION_EVENT_ACTION_POINTER_INDEX_SHIFT 8
+
+enum {
+    /* Bit mask of the parts of the action code that are the action itself.
+     */
+    MOTION_EVENT_ACTION_MASK = 0xff,
+
+    /* Bits in the action code that represent a pointer index, used with
+     * MOTION_EVENT_ACTION_POINTER_DOWN and MOTION_EVENT_ACTION_POINTER_UP.  Shifting
+     * down by MOTION_EVENT_ACTION_POINTER_INDEX_SHIFT provides the actual pointer
+     * index where the data for the pointer going up or down can be found.
+     */
+    MOTION_EVENT_ACTION_POINTER_INDEX_MASK  = 0xff00,
+
+    /* A pressed gesture has started, the motion contains the initial starting location.
+     */
+    MOTION_EVENT_ACTION_DOWN = 0,
+
+    /* A pressed gesture has finished, the motion contains the final release location
+     * as well as any intermediate points since the last down or move event.
+     */
+    MOTION_EVENT_ACTION_UP = 1,
+
+    /* A change has happened during a press gesture (between MOTION_EVENT_ACTION_DOWN and
+     * MOTION_EVENT_ACTION_UP).  The motion contains the most recent point, as well as
+     * any intermediate points since the last down or move event.
+     */
+    MOTION_EVENT_ACTION_MOVE = 2,
+
+    /* The current gesture has been aborted.
+     * You will not receive any more points in it.  You should treat this as
+     * an up event, but not perform any action that you normally would.
+     */
+    MOTION_EVENT_ACTION_CANCEL = 3,
+
+    /* A movement has happened outside of the normal bounds of the UI element.
+     * This does not provide a full gesture, but only the initial location of the movement/touch.
+     */
+    MOTION_EVENT_ACTION_OUTSIDE = 4,
+
+    /* A non-primary pointer has gone down.
+     * The bits in MOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
+     */
+    MOTION_EVENT_ACTION_POINTER_DOWN = 5,
+
+    /* A non-primary pointer has gone up.
+     * The bits in MOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
+     */
+    MOTION_EVENT_ACTION_POINTER_UP = 6
+};
+
+/*
+ * Motion event edge touch flags.
+ */
+enum {
+    /* No edges intersected */
+    MOTION_EVENT_EDGE_FLAG_NONE = 0,
+
+    /* Flag indicating the motion event intersected the top edge of the screen. */
+    MOTION_EVENT_EDGE_FLAG_TOP = 0x01,
+
+    /* Flag indicating the motion event intersected the bottom edge of the screen. */
+    MOTION_EVENT_EDGE_FLAG_BOTTOM = 0x02,
+
+    /* Flag indicating the motion event intersected the left edge of the screen. */
+    MOTION_EVENT_EDGE_FLAG_LEFT = 0x04,
+
+    /* Flag indicating the motion event intersected the right edge of the screen. */
+    MOTION_EVENT_EDGE_FLAG_RIGHT = 0x08
+};
+
+/*
+ * Specifies the logical nature of an input event.
+ * For example, the nature distinguishes between motion events that represent touches and
+ * those that represent trackball moves.
+ *
+ * XXX This concept is tentative.  Another idea would be to associate events with logical
+ *     controllers rather than physical devices.   The interpretation of an event would
+ *     be made with respect to the nature of the controller that is considered the logical
+ *     source of an event.  The decoupling is beneficial since multiple physical (and virtual)
+ *     devices could be responsible for producing events that would be associated with
+ *     various logical controllers.  For example, the hard keyboard, on screen keyboard,
+ *     and peripheral keyboard could be mapped onto a single logical "keyboard" controller
+ *     (or treated independently, if desired).
+ */
+enum {
+    INPUT_EVENT_NATURE_KEY = 1,
+    INPUT_EVENT_NATURE_TOUCH = 2,
+    INPUT_EVENT_NATURE_TRACKBALL = 3
+};
+
+/*
+ * Input event accessors.
+ *
+ * Note that most functions can only be used on input events that are of a given type.
+ * Calling these functions on input events of other types will yield undefined behavior.
+ */
+
+/*** Accessors for all input events. ***/
+
+/* Get the input event type. */
+int32_t input_event_get_type(const input_event_t* event);
+
+/* Get the id for the device that an input event came from.
+ *
+ * Input events can be generated by multiple different input devices.
+ * Use the input device id to obtain information about the input
+ * device that was responsible for generating a particular event.
+ *
+ * An input device id of 0 indicates that the event didn't come from a physical device;
+ * other numbers are arbitrary and you shouldn't depend on the values.
+ * Use the provided input device query API to obtain information about input devices.
+ */
+int32_t input_event_get_device_id(const input_event_t* event);
+
+/* Get the input event nature. */
+int32_t input_event_get_nature(const input_event_t* event);
+
+/*** Accessors for key events only. ***/
+
+/* Get the key event action. */
+int32_t key_event_get_action(const input_event_t* key_event);
+
+/* Get the key event flags. */
+int32_t key_event_get_flags(const input_event_t* key_event);
+
+/* Get the key code of the key event.
+ * This is the physical key that was pressed, not the Unicode character. */
+int32_t key_event_get_key_code(const input_event_t* key_event);
+
+/* Get the hardware key id of this key event.
+ * These values are not reliable and vary from device to device. */
+int32_t key_event_get_scan_code(const input_event_t* key_event);
+
+/* Get the meta key state. */
+int32_t key_event_get_meta_state(const input_event_t* key_event);
+
+/* Get the repeat count of the event.
+ * For both key up an key down events, this is the number of times the key has
+ * repeated with the first down starting at 0 and counting up from there.  For
+ * multiple key events, this is the number of down/up pairs that have occurred. */
+int32_t key_event_get_repeat_count(const input_event_t* key_event);
+
+/* Get the time of the most recent key down event, in the
+ * java.lang.System.nanoTime() time base.  If this is a down event,
+ * this will be the same as eventTime.
+ * Note that when chording keys, this value is the down time of the most recently
+ * pressed key, which may not be the same physical key of this event. */
+int64_t key_event_get_down_time(const input_event_t* key_event);
+
+/* Get the time this event occurred, in the
+ * java.lang.System.nanoTime() time base. */
+int64_t key_event_get_event_time(const input_event_t* key_event);
+
+/*** Accessors for motion events only. ***/
+
+/* Get the combined motion event action code and pointer index. */
+int32_t motion_event_get_action(const input_event_t* motion_event);
+
+/* Get the state of any meta / modifier keys that were in effect when the
+ * event was generated. */
+int32_t motion_event_get_meta_state(const input_event_t* motion_event);
+
+/* Get a bitfield indicating which edges, if any, were touched by this motion event.
+ * For touch events, clients can use this to determine if the user's finger was
+ * touching the edge of the display. */
+int32_t motion_event_get_edge_flags(const input_event_t* motion_event);
+
+/* Get the time when the user originally pressed down to start a stream of
+ * position events, in the java.lang.System.nanoTime() time base. */
+int64_t motion_event_get_down_time(const input_event_t* motion_event);
+
+/* Get the time when this specific event was generated,
+ * in the java.lang.System.nanoTime() time base. */
+int64_t motion_event_get_event_time(const input_event_t* motion_event);
+
+/* Get the precision of the X coordinates being reported.
+ * You can multiply this number with an X coordinate sample to find the
+ * actual hardware value of the X coordinate. */
+float motion_event_get_x_precision(const input_event_t* motion_event);
+
+/* Get the precision of the Y coordinates being reported.
+ * You can multiply this number with a Y coordinate sample to find the
+ * actual hardware value of the Y coordinate. */
+float motion_event_get_y_precision(const input_event_t* motion_event);
+
+/* Get the number of pointers of data contained in this event.
+ * Always >= 1. */
+size_t motion_event_get_pointer_count(const input_event_t* motion_event);
+
+/* Get the pointer identifier associated with a particular pointer
+ * data index is this event.  The identifier tells you the actual pointer
+ * number associated with the data, accounting for individual pointers
+ * going up and down since the start of the current gesture. */
+int32_t motion_event_get_pointer_id(const input_event_t* motion_event, size_t pointer_index);
+
+/* Get the original raw X coordinate of this event.  For touch
+ * events on the screen, this is the original location of the event
+ * on the screen, before it had been adjusted for the containing window
+ * and views. */
+float motion_event_get_raw_x(const input_event_t* motion_event);
+
+/* Get the original raw X coordinate of this event.  For touch
+ * events on the screen, this is the original location of the event
+ * on the screen, before it had been adjusted for the containing window
+ * and views. */
+float motion_event_get_raw_y(const input_event_t* motion_event);
+
+/* Get the current X coordinate of this event for the given pointer index.
+ * Whole numbers are pixels; the value may have a fraction for input devices
+ * that are sub-pixel precise. */
+float motion_event_get_x(const input_event_t* motion_event, size_t pointer_index);
+
+/* Get the current Y coordinate of this event for the given pointer index.
+ * Whole numbers are pixels; the value may have a fraction for input devices
+ * that are sub-pixel precise. */
+float motion_event_get_y(const input_event_t* motion_event, size_t pointer_index);
+
+/* Get the current pressure of this event for the given pointer index.
+ * The pressure generally ranges from 0 (no pressure at all) to 1 (normal pressure),
+ * however values higher than 1 may be generated depending on the calibration of
+ * the input device. */
+float motion_event_get_pressure(const input_event_t* motion_event, size_t pointer_index);
+
+/* Get the current scaled value of the approximate size for the given pointer index.
+ * This represents some approximation of the area of the screen being
+ * pressed; the actual value in pixels corresponding to the
+ * touch is normalized with the device specific range of values
+ * and scaled to a value between 0 and 1.  The value of size can be used to
+ * determine fat touch events. */
+float motion_event_get_size(const input_event_t* motion_event, size_t pointer_index);
+
+/* Get the number of historical points in this event.  These are movements that
+ * have occurred between this event and the previous event.  This only applies
+ * to MOTION_EVENT_ACTION_MOVE events -- all other actions will have a size of 0.
+ * Historical samples are indexed from oldest to newest. */
+size_t motion_event_get_history_size(const input_event_t* motion_event);
+
+/* Get the time that a historical movement occurred between this event and
+ * the previous event, in the java.lang.System.nanoTime() time base. */
+int64_t motion_event_get_historical_event_time(input_event_t* motion_event,
+        size_t history_index);
+
+/* Get the historical X coordinate of this event for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * Whole numbers are pixels; the value may have a fraction for input devices
+ * that are sub-pixel precise. */
+float motion_event_get_historical_x(input_event_t* motion_event, size_t pointer_index,
+        size_t history_index);
+
+/* Get the historical Y coordinate of this event for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * Whole numbers are pixels; the value may have a fraction for input devices
+ * that are sub-pixel precise. */
+float motion_event_get_historical_y(input_event_t* motion_event, size_t pointer_index,
+        size_t history_index);
+
+/* Get the historical pressure of this event for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * The pressure generally ranges from 0 (no pressure at all) to 1 (normal pressure),
+ * however values higher than 1 may be generated depending on the calibration of
+ * the input device. */
+float motion_event_get_historical_pressure(input_event_t* motion_event, size_t pointer_index,
+        size_t history_index);
+
+/* Get the current scaled value of the approximate size for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * This represents some approximation of the area of the screen being
+ * pressed; the actual value in pixels corresponding to the
+ * touch is normalized with the device specific range of values
+ * and scaled to a value between 0 and 1.  The value of size can be used to
+ * determine fat touch events. */
+float motion_event_get_historical_size(input_event_t* motion_event, size_t pointer_index,
+        size_t history_index);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _ANDROID_INPUT_H
diff --git a/native/include/android/keycodes.h b/native/include/android/keycodes.h
new file mode 100644
index 0000000..36855c5
--- /dev/null
+++ b/native/include/android/keycodes.h
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_KEYCODES_H
+#define _ANDROID_KEYCODES_H
+
+/******************************************************************
+ *
+ * IMPORTANT NOTICE:
+ *
+ *   This file is part of Android's set of stable system headers
+ *   exposed by the Android NDK (Native Development Kit).
+ *
+ *   Third-party source AND binary code relies on the definitions
+ *   here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES.
+ *
+ *   - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES)
+ *   - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS
+ *   - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY
+ *   - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES
+ */
+
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Key codes.
+ *
+ * XXX: The declarations in <ui/KeycodeLabel.h> should be updated to use these instead.
+ *      We should probably move this into android/keycodes.h and add some new API for
+ *      getting labels so that we can remove the other tables also in KeycodeLabel.h.
+ */
+enum {
+    KEYCODE_UNKNOWN         = 0,
+    KEYCODE_SOFT_LEFT       = 1,
+    KEYCODE_SOFT_RIGHT      = 2,
+    KEYCODE_HOME            = 3,
+    KEYCODE_BACK            = 4,
+    KEYCODE_CALL            = 5,
+    KEYCODE_ENDCALL         = 6,
+    KEYCODE_0               = 7,
+    KEYCODE_1               = 8,
+    KEYCODE_2               = 9,
+    KEYCODE_3               = 10,
+    KEYCODE_4               = 11,
+    KEYCODE_5               = 12,
+    KEYCODE_6               = 13,
+    KEYCODE_7               = 14,
+    KEYCODE_8               = 15,
+    KEYCODE_9               = 16,
+    KEYCODE_STAR            = 17,
+    KEYCODE_POUND           = 18,
+    KEYCODE_DPAD_UP         = 19,
+    KEYCODE_DPAD_DOWN       = 20,
+    KEYCODE_DPAD_LEFT       = 21,
+    KEYCODE_DPAD_RIGHT      = 22,
+    KEYCODE_DPAD_CENTER     = 23,
+    KEYCODE_VOLUME_UP       = 24,
+    KEYCODE_VOLUME_DOWN     = 25,
+    KEYCODE_POWER           = 26,
+    KEYCODE_CAMERA          = 27,
+    KEYCODE_CLEAR           = 28,
+    KEYCODE_A               = 29,
+    KEYCODE_B               = 30,
+    KEYCODE_C               = 31,
+    KEYCODE_D               = 32,
+    KEYCODE_E               = 33,
+    KEYCODE_F               = 34,
+    KEYCODE_G               = 35,
+    KEYCODE_H               = 36,
+    KEYCODE_I               = 37,
+    KEYCODE_J               = 38,
+    KEYCODE_K               = 39,
+    KEYCODE_L               = 40,
+    KEYCODE_M               = 41,
+    KEYCODE_N               = 42,
+    KEYCODE_O               = 43,
+    KEYCODE_P               = 44,
+    KEYCODE_Q               = 45,
+    KEYCODE_R               = 46,
+    KEYCODE_S               = 47,
+    KEYCODE_T               = 48,
+    KEYCODE_U               = 49,
+    KEYCODE_V               = 50,
+    KEYCODE_W               = 51,
+    KEYCODE_X               = 52,
+    KEYCODE_Y               = 53,
+    KEYCODE_Z               = 54,
+    KEYCODE_COMMA           = 55,
+    KEYCODE_PERIOD          = 56,
+    KEYCODE_ALT_LEFT        = 57,
+    KEYCODE_ALT_RIGHT       = 58,
+    KEYCODE_SHIFT_LEFT      = 59,
+    KEYCODE_SHIFT_RIGHT     = 60,
+    KEYCODE_TAB             = 61,
+    KEYCODE_SPACE           = 62,
+    KEYCODE_SYM             = 63,
+    KEYCODE_EXPLORER        = 64,
+    KEYCODE_ENVELOPE        = 65,
+    KEYCODE_ENTER           = 66,
+    KEYCODE_DEL             = 67,
+    KEYCODE_GRAVE           = 68,
+    KEYCODE_MINUS           = 69,
+    KEYCODE_EQUALS          = 70,
+    KEYCODE_LEFT_BRACKET    = 71,
+    KEYCODE_RIGHT_BRACKET   = 72,
+    KEYCODE_BACKSLASH       = 73,
+    KEYCODE_SEMICOLON       = 74,
+    KEYCODE_APOSTROPHE      = 75,
+    KEYCODE_SLASH           = 76,
+    KEYCODE_AT              = 77,
+    KEYCODE_NUM             = 78,
+    KEYCODE_HEADSETHOOK     = 79,
+    KEYCODE_FOCUS           = 80,   // *Camera* focus
+    KEYCODE_PLUS            = 81,
+    KEYCODE_MENU            = 82,
+    KEYCODE_NOTIFICATION    = 83,
+    KEYCODE_SEARCH          = 84,
+    KEYCODE_MEDIA_PLAY_PAUSE= 85,
+    KEYCODE_MEDIA_STOP      = 86,
+    KEYCODE_MEDIA_NEXT      = 87,
+    KEYCODE_MEDIA_PREVIOUS  = 88,
+    KEYCODE_MEDIA_REWIND    = 89,
+    KEYCODE_MEDIA_FAST_FORWARD = 90,
+    KEYCODE_MUTE            = 91,
+    KEYCODE_PAGE_UP         = 92,
+    KEYCODE_PAGE_DOWN       = 93
+
+    /* NOTE: If you add a new keycode here you must also add it to:
+     *  native/include/android/keycodes.h
+     *  frameworks/base/include/ui/KeycodeLabels.h
+     *   frameworks/base/core/java/android/view/KeyEvent.java
+     *   tools/puppet_master/PuppetMaster.nav_keys.py
+     *   frameworks/base/core/res/res/values/attrs.xml
+     */
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _ANDROID_KEYCODES_H
diff --git a/policy/com/android/internal/policy/impl/KeyguardViewMediator.java b/policy/com/android/internal/policy/impl/KeyguardViewMediator.java
index eb61f5e..88203c3 100644
--- a/policy/com/android/internal/policy/impl/KeyguardViewMediator.java
+++ b/policy/com/android/internal/policy/impl/KeyguardViewMediator.java
@@ -86,7 +86,7 @@
  * This class is created by the initialization routine of the {@link WindowManagerPolicy},
  * and runs on its thread.  The keyguard UI is created from that thread in the
  * constructor of this class.  The apis may be called from other threads, including the
- * {@link com.android.server.KeyInputQueue}'s and {@link android.view.WindowManager}'s.
+ * {@link com.android.server.InputManager}'s and {@link android.view.WindowManager}'s.
  * Therefore, methods on this class are synchronized, and any action that is pointed
  * directly to the keyguard UI is posted to a {@link Handler} to ensure it is taken on the UI
  * thread of the keyguard.
diff --git a/policy/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/com/android/internal/policy/impl/PhoneWindowManager.java
index d152bc4..a01e25b 100755
--- a/policy/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -1641,6 +1641,36 @@
         }
         return false;
     }
+    
+    public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
+        // lid changed state
+        mLidOpen = lidOpen;
+        boolean awakeNow = mKeyguardMediator.doLidChangeTq(mLidOpen);
+        updateRotation(Surface.FLAGS_ORIENTATION_ANIMATION_DISABLE);
+        if (awakeNow) {
+            // If the lid opening and we don't have to keep the
+            // keyguard up, then we can turn on the screen
+            // immediately.
+            mKeyguardMediator.pokeWakelock();
+        } else if (keyguardIsShowingTq()) {
+            if (mLidOpen) {
+                // If we are opening the lid and not hiding the
+                // keyguard, then we need to have it turn on the
+                // screen once it is shown.
+                mKeyguardMediator.onWakeKeyWhenKeyguardShowingTq(
+                        KeyEvent.KEYCODE_POWER);
+            }
+        } else {
+            // Light up the keyboard if we are sliding up.
+            if (mLidOpen) {
+                mPowerManager.userActivity(SystemClock.uptimeMillis(), false,
+                        LocalPowerManager.BUTTON_EVENT);
+            } else {
+                mPowerManager.userActivity(SystemClock.uptimeMillis(), false,
+                        LocalPowerManager.OTHER_EVENT);
+            }
+        }
+    }
 
     
     /** {@inheritDoc} */
diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java
new file mode 100644
index 0000000..72c4166
--- /dev/null
+++ b/services/java/com/android/server/InputManager.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import com.android.internal.util.XmlUtils;
+import com.android.server.KeyInputQueue.VirtualKey;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Environment;
+import android.os.LocalPowerManager;
+import android.os.PowerManager;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml;
+import android.view.InputChannel;
+import android.view.InputTarget;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.RawInputEvent;
+import android.view.Surface;
+import android.view.WindowManagerPolicy;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/*
+ * Wraps the C++ InputManager and provides its callbacks.
+ * 
+ * XXX Tempted to promote this to a first-class service, ie. InputManagerService, to
+ *     improve separation of concerns with respect to the window manager.
+ */
+public class InputManager {
+    static final String TAG = "InputManager";
+    
+    private final Callbacks mCallbacks;
+    private final Context mContext;
+    private final WindowManagerService mWindowManagerService;
+    private final WindowManagerPolicy mWindowManagerPolicy;
+    private final PowerManager mPowerManager;
+    private final PowerManagerService mPowerManagerService;
+    
+    private int mTouchScreenConfig;
+    private int mKeyboardConfig;
+    private int mNavigationConfig;
+    
+    private static native void nativeInit(Callbacks callbacks);
+    private static native void nativeStart();
+    private static native void nativeSetDisplaySize(int displayId, int width, int height);
+    private static native void nativeSetDisplayOrientation(int displayId, int rotation);
+    
+    private static native int nativeGetScanCodeState(int deviceId, int deviceClasses,
+            int scanCode);
+    private static native int nativeGetKeyCodeState(int deviceId, int deviceClasses,
+            int keyCode);
+    private static native int nativeGetSwitchState(int deviceId, int deviceClasses,
+            int sw);
+    private static native boolean nativeHasKeys(int[] keyCodes, boolean[] keyExists);
+    private static native void nativeRegisterInputChannel(InputChannel inputChannel);
+    private static native void nativeUnregisterInputChannel(InputChannel inputChannel);
+    
+    // Device class as defined by EventHub.
+    private static final int CLASS_KEYBOARD = 0x00000001;
+    private static final int CLASS_ALPHAKEY = 0x00000002;
+    private static final int CLASS_TOUCHSCREEN = 0x00000004;
+    private static final int CLASS_TRACKBALL = 0x00000008;
+    private static final int CLASS_TOUCHSCREEN_MT = 0x00000010;
+    private static final int CLASS_DPAD = 0x00000020;
+    
+    public InputManager(Context context,
+            WindowManagerService windowManagerService,
+            WindowManagerPolicy windowManagerPolicy,
+            PowerManager powerManager,
+            PowerManagerService powerManagerService) {
+        this.mContext = context;
+        this.mWindowManagerService = windowManagerService;
+        this.mWindowManagerPolicy = windowManagerPolicy;
+        this.mPowerManager = powerManager;
+        this.mPowerManagerService = powerManagerService;
+        
+        this.mCallbacks = new Callbacks();
+        
+        mTouchScreenConfig = Configuration.TOUCHSCREEN_NOTOUCH;
+        mKeyboardConfig = Configuration.KEYBOARD_NOKEYS;
+        mNavigationConfig = Configuration.NAVIGATION_NONAV;
+        
+        init();
+    }
+    
+    private void init() {
+        Slog.i(TAG, "Initializing input manager");
+        nativeInit(mCallbacks);
+    }
+    
+    public void start() {
+        Slog.i(TAG, "Starting input manager");
+        nativeStart();
+    }
+    
+    public void setDisplaySize(int displayId, int width, int height) {
+        if (width <= 0 || height <= 0) {
+            throw new IllegalArgumentException("Invalid display id or dimensions.");
+        }
+        
+        Slog.i(TAG, "Setting display #" + displayId + " size to " + width + "x" + height);
+        nativeSetDisplaySize(displayId, width, height);
+    }
+    
+    public void setDisplayOrientation(int displayId, int rotation) {
+        if (rotation < Surface.ROTATION_0 || rotation > Surface.ROTATION_270) {
+            throw new IllegalArgumentException("Invalid rotation.");
+        }
+        
+        Slog.i(TAG, "Setting display #" + displayId + " orientation to " + rotation);
+        nativeSetDisplayOrientation(displayId, rotation);
+    }
+    
+    public void getInputConfiguration(Configuration config) {
+        if (config == null) {
+            throw new IllegalArgumentException("config must not be null.");
+        }
+        
+        config.touchscreen = mTouchScreenConfig;
+        config.keyboard = mKeyboardConfig;
+        config.navigation = mNavigationConfig;
+    }
+    
+    public int getScancodeState(int code) {
+        return nativeGetScanCodeState(0, -1, code);
+    }
+    
+    public int getScancodeState(int deviceId, int code) {
+        return nativeGetScanCodeState(deviceId, -1, code);
+    }
+    
+    public int getTrackballScancodeState(int code) {
+        return nativeGetScanCodeState(-1, CLASS_TRACKBALL, code);
+    }
+    
+    public int getDPadScancodeState(int code) {
+        return nativeGetScanCodeState(-1, CLASS_DPAD, code);
+    }
+    
+    public int getKeycodeState(int code) {
+        return nativeGetKeyCodeState(0, -1, code);
+    }
+    
+    public int getKeycodeState(int deviceId, int code) {
+        return nativeGetKeyCodeState(deviceId, -1, code);
+    }
+    
+    public int getTrackballKeycodeState(int code) {
+        return nativeGetKeyCodeState(-1, CLASS_TRACKBALL, code);
+    }
+    
+    public int getDPadKeycodeState(int code) {
+        return nativeGetKeyCodeState(-1, CLASS_DPAD, code);
+    }
+
+    public int getSwitchState(int sw) {
+        return nativeGetSwitchState(-1, -1, sw);
+    }
+    
+    public int getSwitchState(int deviceId, int sw) {
+        return nativeGetSwitchState(deviceId, -1, sw);
+    }
+
+    public boolean hasKeys(int[] keyCodes, boolean[] keyExists) {
+        if (keyCodes == null) {
+            throw new IllegalArgumentException("keyCodes must not be null.");
+        }
+        if (keyExists == null) {
+            throw new IllegalArgumentException("keyExists must not be null.");
+        }
+        
+        return nativeHasKeys(keyCodes, keyExists);
+    }
+    
+    public void registerInputChannel(InputChannel inputChannel) {
+        if (inputChannel == null) {
+            throw new IllegalArgumentException("inputChannel must not be null.");
+        }
+        
+        nativeRegisterInputChannel(inputChannel);
+    }
+    
+    public void unregisterInputChannel(InputChannel inputChannel) {
+        if (inputChannel == null) {
+            throw new IllegalArgumentException("inputChannel must not be null.");
+        }
+        
+        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.
+     * @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
+     */
+    public int injectKeyEvent(KeyEvent event, int nature, boolean sync, int pid, int uid) {
+        // TODO
+        return INJECT_FAILED;
+    }
+    
+    /**
+     * Injects a motion event into the event system on behalf of an application.
+     * @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
+     */
+    public int injectMotionEvent(MotionEvent event, int nature, boolean sync, int pid, int uid) {
+        // TODO
+        return INJECT_FAILED;
+    }
+    
+    public void dump(PrintWriter pw) {
+        // TODO
+    }
+    
+    private static final class VirtualKeyDefinition {
+        public int scanCode;
+        
+        // configured position data, specified in display coords
+        public int centerX;
+        public int centerY;
+        public int width;
+        public int height;
+    }
+    
+    /*
+     * Callbacks from native.
+     */
+    private class Callbacks {
+        static final String TAG = "InputManager-Callbacks";
+        
+        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();
+        }
+        
+        @SuppressWarnings("unused")
+        public boolean isScreenBright() {
+            return mPowerManagerService.isScreenBright();
+        }
+        
+        @SuppressWarnings("unused")
+        public void virtualKeyFeedback(long whenNanos, int deviceId, int action, int flags,
+                int keyCode, int scanCode, int metaState, long downTimeNanos) {
+            KeyEvent keyEvent = new KeyEvent(downTimeNanos / 1000000,
+                    whenNanos / 1000000, action, keyCode, 0, metaState, scanCode, deviceId,
+                    flags);
+            
+            mWindowManagerService.virtualKeyFeedback(keyEvent);
+        }
+        
+        @SuppressWarnings("unused")
+        public void notifyConfigurationChanged(long whenNanos,
+                int touchScreenConfig, int keyboardConfig, int navigationConfig) {
+            mTouchScreenConfig = touchScreenConfig;
+            mKeyboardConfig = keyboardConfig;
+            mNavigationConfig = navigationConfig;
+            
+            mWindowManagerService.sendNewConfiguration();
+        }
+        
+        @SuppressWarnings("unused")
+        public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
+            mWindowManagerPolicy.notifyLidSwitchChanged(whenNanos, lidOpen);
+        }
+        
+        @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();
+            event.deviceId = deviceId;
+            event.type = type;
+            event.scancode = scanCode;
+            event.keycode = keyCode;
+            event.flags = policyFlags;
+            event.value = value;
+            event.when = whenNanos / 1000000;
+            
+            return mWindowManagerPolicy.interceptKeyTq(event, isScreenOn);
+        }
+        
+        @SuppressWarnings("unused")
+        public void goToSleep(long whenNanos) {
+            long when = whenNanos / 1000000;
+            mPowerManager.goToSleep(when);
+        }
+        
+        @SuppressWarnings("unused")
+        public void pokeUserActivityForKey(long whenNanos) {
+            long when = whenNanos / 1000000;
+            mPowerManagerService.userActivity(when, false,
+                    LocalPowerManager.BUTTON_EVENT, false);
+        }
+        
+        @SuppressWarnings("unused")
+        public void notifyAppSwitchComing() {
+            mWindowManagerService.mKeyWaiter.appSwitchComing();
+        }
+        
+        @SuppressWarnings("unused")
+        public boolean filterTouchEvents() {
+            return mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_filterTouchEvents);
+        }
+        
+        @SuppressWarnings("unused")
+        public boolean filterJumpyTouchEvents() {
+            return mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_filterJumpyTouchEvents);
+        }
+        
+        @SuppressWarnings("unused")
+        public VirtualKeyDefinition[] getVirtualKeyDefinitions(String deviceName) {
+            ArrayList<VirtualKeyDefinition> keys = new ArrayList<VirtualKeyDefinition>();
+            
+            try {
+                FileInputStream fis = new FileInputStream(
+                        "/sys/board_properties/virtualkeys." + deviceName);
+                InputStreamReader isr = new InputStreamReader(fis);
+                BufferedReader br = new BufferedReader(isr, 2048);
+                String str = br.readLine();
+                if (str != null) {
+                    String[] it = str.split(":");
+                    if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "***** VIRTUAL KEYS: " + it);
+                    final int N = it.length-6;
+                    for (int i=0; i<=N; i+=6) {
+                        if (!"0x01".equals(it[i])) {
+                            Slog.w(TAG, "Unknown virtual key type at elem #" + i
+                                    + ": " + it[i]);
+                            continue;
+                        }
+                        try {
+                            VirtualKeyDefinition key = new VirtualKeyDefinition();
+                            key.scanCode = Integer.parseInt(it[i+1]);
+                            key.centerX = Integer.parseInt(it[i+2]);
+                            key.centerY = Integer.parseInt(it[i+3]);
+                            key.width = Integer.parseInt(it[i+4]);
+                            key.height = Integer.parseInt(it[i+5]);
+                            if (DEBUG_VIRTUAL_KEYS) Slog.v(TAG, "Virtual key "
+                                    + key.scanCode + ": center=" + key.centerX + ","
+                                    + key.centerY + " size=" + key.width + "x"
+                                    + key.height);
+                            keys.add(key);
+                        } catch (NumberFormatException e) {
+                            Slog.w(TAG, "Bad number at region " + i + " in: "
+                                    + str, e);
+                        }
+                    }
+                }
+                br.close();
+            } catch (FileNotFoundException e) {
+                Slog.i(TAG, "No virtual keys found");
+            } catch (IOException e) {
+                Slog.w(TAG, "Error reading virtual keys", e);
+            }
+            
+            return keys.toArray(new VirtualKeyDefinition[keys.size()]);
+        }
+        
+        @SuppressWarnings("unused")
+        public String[] getExcludedDeviceNames() {
+            ArrayList<String> names = new ArrayList<String>();
+            
+            // Read partner-provided list of excluded input devices
+            XmlPullParser parser = null;
+            // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system".
+            File confFile = new File(Environment.getRootDirectory(), EXCLUDED_DEVICES_PATH);
+            FileReader confreader = null;
+            try {
+                confreader = new FileReader(confFile);
+                parser = Xml.newPullParser();
+                parser.setInput(confreader);
+                XmlUtils.beginDocument(parser, "devices");
+
+                while (true) {
+                    XmlUtils.nextElement(parser);
+                    if (!"device".equals(parser.getName())) {
+                        break;
+                    }
+                    String name = parser.getAttributeValue(null, "name");
+                    if (name != null) {
+                        names.add(name);
+                    }
+                }
+            } catch (FileNotFoundException e) {
+                // It's ok if the file does not exist.
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception while parsing '" + confFile.getAbsolutePath() + "'", e);
+            } finally {
+                try { if (confreader != null) confreader.close(); } catch (IOException e) { }
+            }
+            
+            return names.toArray(new String[names.size()]);
+        }
+        
+        @SuppressWarnings("unused")
+        public InputTarget[] getKeyEventTargets(KeyEvent event, int nature, int policyFlags) {
+            mReusableInputTargetList.clear();
+            
+            mWindowManagerService.getKeyEventTargets(mReusableInputTargetList,
+                    event, nature, policyFlags);
+            
+            return mReusableInputTargetList.toNullTerminatedArray();
+        }
+        
+        @SuppressWarnings("unused")
+        public InputTarget[] getMotionEventTargets(MotionEvent event, int nature, int policyFlags) {
+            mReusableInputTargetList.clear();
+            
+            mWindowManagerService.getMotionEventTargets(mReusableInputTargetList,
+                    event, nature, policyFlags);
+            
+            return mReusableInputTargetList.toNullTerminatedArray();
+        }
+    }
+}
diff --git a/services/java/com/android/server/InputTargetList.java b/services/java/com/android/server/InputTargetList.java
new file mode 100644
index 0000000..1575612
--- /dev/null
+++ b/services/java/com/android/server/InputTargetList.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.view.InputChannel;
+import android.view.InputTarget;
+
+/**
+ * A specialized list of input targets backed by an array.
+ * 
+ * This class is part of an InputManager optimization to avoid allocating and copying
+ * input target arrays unnecessarily on return from JNI callbacks.  Internally, it keeps
+ * an array full of demand-allocated InputTarget objects that it recycles each time the
+ * list is cleared.  The used portion of the array is padded with a null.
+ * 
+ * @hide
+ */
+public class InputTargetList {
+    private InputTarget[] mArray;
+    private int mCount;
+    
+    /**
+     * Creates an empty input target list.
+     */
+    public InputTargetList() {
+        mArray = new InputTarget[8];
+    }
+    
+    /**
+     * Clears the input target list.
+     */
+    public void clear() {
+        if (mCount == 0) {
+            return;
+        }
+        
+        int count = mCount;
+        mCount = 0;
+        mArray[count] = mArray[0];
+        while (count > 0) {
+            count -= 1;
+            mArray[count].recycle();
+        }
+        // mArray[0] could be set to null here but we do it in toNullTerminatedArray()
+    }
+    
+    /**
+     * Adds a new input target to the input target list.
+     * @param inputChannel The input channel of the target window.
+     * @param flags Input target flags.
+     * @param timeoutNanos The input dispatch timeout (before ANR) in nanoseconds or -1 if none.
+     * @param xOffset An offset to add to motion X coordinates during delivery.
+     * @param yOffset An offset to add to motion Y coordinates during delivery.
+     */
+    public void add(InputChannel inputChannel, int flags, long timeoutNanos,
+            float xOffset, float yOffset) {
+        if (inputChannel == null) {
+            throw new IllegalArgumentException("inputChannel must not be null");
+        }
+        
+        if (mCount + 1 == mArray.length) {
+            InputTarget[] oldArray = mArray;
+            mArray = new InputTarget[oldArray.length * 2];
+            System.arraycopy(oldArray, 0, mArray, 0, mCount);
+        }
+        
+        // Grab InputTarget from tail (after used section) if available.
+        InputTarget inputTarget = mArray[mCount + 1];
+        if (inputTarget == null) {
+            inputTarget = new InputTarget();
+        }
+        inputTarget.mInputChannel = inputChannel;
+        inputTarget.mFlags = flags;
+        inputTarget.mTimeoutNanos = timeoutNanos;
+        inputTarget.mXOffset = xOffset;
+        inputTarget.mYOffset = yOffset;
+        
+        mArray[mCount] = inputTarget;
+        mCount += 1;
+        // mArray[mCount] could be set to null here but we do it in toNullTerminatedArray()
+    }
+    
+    /**
+     * Gets the input targets as a null-terminated array.
+     * @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/KeyInputQueue.java b/services/java/com/android/server/KeyInputQueue.java
index f30346b..f62c7ee 100644
--- a/services/java/com/android/server/KeyInputQueue.java
+++ b/services/java/com/android/server/KeyInputQueue.java
@@ -298,7 +298,9 @@
         
         mHapticFeedbackCallback = hapticFeedbackCallback;
         
-        readExcludedDevices();
+        if (! WindowManagerService.ENABLE_NATIVE_INPUT_DISPATCH) {
+            readExcludedDevices();
+        }
         
         PowerManager pm = (PowerManager)context.getSystemService(
                                                         Context.POWER_SERVICE);
@@ -311,7 +313,9 @@
         mFirst.next = mLast;
         mLast.prev = mFirst;
 
-        mThread.start();
+        if (! WindowManagerService.ENABLE_NATIVE_INPUT_DISPATCH) {
+            mThread.start();
+        }
     }
 
     public void setDisplay(Display display) {
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index ac5e3f1..9bc3931 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -101,6 +101,9 @@
 import android.view.IWindow;
 import android.view.IWindowManager;
 import android.view.IWindowSession;
+import android.view.InputChannel;
+import android.view.InputQueue;
+import android.view.InputTarget;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.RawInputEvent;
@@ -157,6 +160,8 @@
     static final boolean SHOW_TRANSACTIONS = false;
     static final boolean HIDE_STACK_CRAWLS = true;
     static final boolean MEASURE_LATENCY = false;
+    static final boolean ENABLE_NATIVE_INPUT_DISPATCH =
+        WindowManagerPolicy.ENABLE_NATIVE_INPUT_DISPATCH;
     static private LatencyTimer lt;
 
     static final boolean PROFILE_ORIENTATION = false;
@@ -497,10 +502,12 @@
 
     final KeyWaiter mKeyWaiter = new KeyWaiter();
     final KeyQ mQueue;
+    final InputManager mInputManager;
     final InputDispatcherThread mInputThread;
 
     // Who is holding the screen on.
     Session mHoldingScreenOn;
+    PowerManager.WakeLock mHoldingScreenWakeLock;
 
     boolean mTurnOnScreen;
 
@@ -650,8 +657,16 @@
         }
         mMinWaitTimeBetweenTouchEvents = 1000 / max_events_per_sec;
 
-        mQueue = new KeyQ();
+        mHoldingScreenWakeLock = pmc.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
+                "KEEP_SCREEN_ON_FLAG");
+        mHoldingScreenWakeLock.setReferenceCounted(false);
 
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            mInputManager = new InputManager(context, this, mPolicy, pmc, mPowerManager);
+        } else {
+            mInputManager = null;
+        }
+        mQueue = new KeyQ();
         mInputThread = new InputDispatcherThread();
 
         PolicyThread thr = new PolicyThread(mPolicy, this, context, pm);
@@ -666,7 +681,11 @@
             }
         }
 
-        mInputThread.start();
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            mInputManager.start();
+        } else {
+            mInputThread.start();
+        }
 
         // Add ourself to the Watchdog monitors.
         Watchdog.getInstance().addMonitor(this);
@@ -1859,7 +1878,7 @@
     
     public int addWindow(Session session, IWindow client,
             WindowManager.LayoutParams attrs, int viewVisibility,
-            Rect outContentInsets) {
+            Rect outContentInsets, InputChannel outInputChannel) {
         int res = mPolicy.checkAddPermission(attrs);
         if (res != WindowManagerImpl.ADD_OKAY) {
             return res;
@@ -1878,7 +1897,12 @@
                 mDisplay = wm.getDefaultDisplay();
                 mInitialDisplayWidth = mDisplay.getWidth();
                 mInitialDisplayHeight = mDisplay.getHeight();
-                mQueue.setDisplay(mDisplay);
+                if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                    mInputManager.setDisplaySize(0,
+                            mInitialDisplayWidth, mInitialDisplayHeight);
+                } else {
+                    mQueue.setDisplay(mDisplay);
+                }
                 reportNewConfig = true;
             }
 
@@ -1971,6 +1995,17 @@
             if (res != WindowManagerImpl.ADD_OKAY) {
                 return res;
             }
+            
+            if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                if (outInputChannel != null) {
+                    String name = win.makeInputChannelName();
+                    InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
+                    win.mInputChannel = inputChannels[0];
+                    inputChannels[1].transferToBinderOutParameter(outInputChannel);
+                    
+                    mInputManager.registerInputChannel(win.mInputChannel);
+                }
+            }
 
             // From now on, no exceptions or errors allowed!
 
@@ -4354,7 +4389,11 @@
                 "getSwitchState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return KeyInputQueue.getSwitchState(sw);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.getSwitchState(sw);
+        } else {
+            return KeyInputQueue.getSwitchState(sw);
+        }
     }
 
     public int getSwitchStateForDevice(int devid, int sw) {
@@ -4362,7 +4401,11 @@
                 "getSwitchStateForDevice()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return KeyInputQueue.getSwitchState(devid, sw);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.getSwitchState(devid, sw);
+        } else {
+            return KeyInputQueue.getSwitchState(devid, sw);
+        }
     }
 
     public int getScancodeState(int sw) {
@@ -4370,7 +4413,11 @@
                 "getScancodeState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mQueue.getScancodeState(sw);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.getScancodeState(sw);
+        } else {
+            return mQueue.getScancodeState(sw);
+        }
     }
 
     public int getScancodeStateForDevice(int devid, int sw) {
@@ -4378,7 +4425,11 @@
                 "getScancodeStateForDevice()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mQueue.getScancodeState(devid, sw);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.getScancodeState(devid, sw);
+        } else {
+            return mQueue.getScancodeState(devid, sw);
+        }
     }
 
     public int getTrackballScancodeState(int sw) {
@@ -4386,7 +4437,11 @@
                 "getTrackballScancodeState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mQueue.getTrackballScancodeState(sw);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.getTrackballScancodeState(sw);
+        } else {
+            return mQueue.getTrackballScancodeState(sw);
+        }
     }
 
     public int getDPadScancodeState(int sw) {
@@ -4394,7 +4449,11 @@
                 "getDPadScancodeState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mQueue.getDPadScancodeState(sw);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.getDPadScancodeState(sw);
+        } else {
+            return mQueue.getDPadScancodeState(sw);
+        }
     }
 
     public int getKeycodeState(int sw) {
@@ -4402,7 +4461,11 @@
                 "getKeycodeState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mQueue.getKeycodeState(sw);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.getKeycodeState(sw);
+        } else {
+            return mQueue.getKeycodeState(sw);
+        }
     }
 
     public int getKeycodeStateForDevice(int devid, int sw) {
@@ -4410,7 +4473,11 @@
                 "getKeycodeStateForDevice()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mQueue.getKeycodeState(devid, sw);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.getKeycodeState(devid, sw);
+        } else {
+            return mQueue.getKeycodeState(devid, sw);
+        }
     }
 
     public int getTrackballKeycodeState(int sw) {
@@ -4418,7 +4485,11 @@
                 "getTrackballKeycodeState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mQueue.getTrackballKeycodeState(sw);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.getTrackballKeycodeState(sw);
+        } else {
+            return mQueue.getTrackballKeycodeState(sw);
+        }
     }
 
     public int getDPadKeycodeState(int sw) {
@@ -4426,11 +4497,19 @@
                 "getDPadKeycodeState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mQueue.getDPadKeycodeState(sw);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.getDPadKeycodeState(sw);
+        } else {
+            return mQueue.getDPadKeycodeState(sw);
+        }
     }
 
     public boolean hasKeys(int[] keycodes, boolean[] keyExists) {
-        return KeyInputQueue.hasKeys(keycodes, keyExists);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            return mInputManager.hasKeys(keycodes, keyExists);
+        } else {
+            return KeyInputQueue.hasKeys(keycodes, keyExists);
+        }
     }
 
     public void enableScreenAfterBoot() {
@@ -4575,7 +4654,11 @@
             mLayoutNeeded = true;
             startFreezingDisplayLocked();
             Slog.i(TAG, "Setting rotation to " + rotation + ", animFlags=" + animFlags);
-            mQueue.setOrientation(rotation);
+            if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                mInputManager.setDisplayOrientation(0, rotation);
+            } else {
+                mQueue.setOrientation(rotation);
+            }
             if (mDisplayEnabled) {
                 Surface.setOrientation(0, rotation, animFlags);
             }
@@ -4906,7 +4989,11 @@
         if (mDisplay == null) {
             return false;
         }
-        mQueue.getInputConfiguration(config);
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            mInputManager.getInputConfiguration(config);
+        } else {
+            mQueue.getInputConfiguration(config);
+        }
 
         // Use the effective "visual" dimensions based on current rotation
         final boolean rotated = (mRotation == Surface.ROTATION_90
@@ -4989,6 +5076,291 @@
     // -------------------------------------------------------------
     // Input Events and Focus Management
     // -------------------------------------------------------------
+    
+    public void getKeyEventTargets(InputTargetList inputTargets,
+            KeyEvent event, int nature, int policyFlags) {
+        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;
+        }
+        
+        int action = event.getAction();
+
+        // TODO detect cheek presses somewhere... either here or in native code
+        
+        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();
+                }
+
+                // 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;
+                    }
+                    tmpRect.set(child.mFrame);
+                    if (child.mTouchableInsets == ViewTreeObserver
+                                .InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT) {
+                        // The touch is inside of the window if it is
+                        // inside the frame, AND the content part of that
+                        // frame that was given by the application.
+                        tmpRect.left += child.mGivenContentInsets.left;
+                        tmpRect.top += child.mGivenContentInsets.top;
+                        tmpRect.right -= child.mGivenContentInsets.right;
+                        tmpRect.bottom -= child.mGivenContentInsets.bottom;
+                    } else if (child.mTouchableInsets == ViewTreeObserver
+                                .InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE) {
+                        // The touch is inside of the window if it is
+                        // inside the frame, AND the visible part of that
+                        // frame that was given by the application.
+                        tmpRect.left += child.mGivenVisibleInsets.left;
+                        tmpRect.top += child.mGivenVisibleInsets.top;
+                        tmpRect.right -= child.mGivenVisibleInsets.right;
+                        tmpRect.bottom -= child.mGivenVisibleInsets.bottom;
+                    }
+                    final int touchFlags = flags &
+                        (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        |WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
+                    if (tmpRect.contains(x, y) || touchFlags == 0) {
+                        //Slog.i(TAG, "Using this target!");
+                        if (!screenWasOff || (flags &
+                                WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING) != 0) {
+                            mTouchFocus = child;
+                        } else {
+                            //Slog.i(TAG, "Waking, skip!");
+                            mTouchFocus = null;
+                        }
+                        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;
+                }
+            }
+            
+            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 (action == MotionEvent.ACTION_DOWN) {
+                while (mOutsideTouchTargets != null) {
+                    addInputTarget(inputTargets, mOutsideTouchTargets,
+                            InputTarget.FLAG_OUTSIDE | targetFlags);
+                    mOutsideTouchTargets = mOutsideTouchTargets.mNextOutsideTouch;
+                }
+            }
+            
+            // 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)) {
+                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;
+                        }
+                        
+                        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 addInputTarget(InputTargetList inputTargets, WindowState window, int flags) {
+        if (window.mInputChannel == null) {
+            return;
+        }
+        
+        long timeoutNanos = -1;
+        IApplicationToken appToken = window.getAppToken();
+
+        if (appToken != null) {
+            try {
+                timeoutNanos = appToken.getKeyDispatchingTimeout() * 1000000;
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Could not get key dispatching timeout.", ex);
+            }
+        }
+        
+        inputTargets.add(window.mInputChannel, flags, timeoutNanos,
+                - window.mFrame.left, - window.mFrame.top);
+    }
 
     private final void wakeupIfNeeded(WindowState targetWin, int eventType) {
         long curTime = SystemClock.uptimeMillis();
@@ -5499,10 +5871,18 @@
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
         final long ident = Binder.clearCallingIdentity();
-        final int result = dispatchKey(newEvent, pid, uid);
-        if (sync) {
-            mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid);
+        
+        final int result;
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            result = mInputManager.injectKeyEvent(newEvent,
+                    InputQueue.INPUT_EVENT_NATURE_KEY, sync, pid, uid);
+        } else {
+            result = dispatchKey(newEvent, pid, uid);
+            if (sync) {
+                mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid);
+            }
         }
+        
         Binder.restoreCallingIdentity(ident);
         switch (result) {
             case INJECT_NO_PERMISSION:
@@ -5527,10 +5907,18 @@
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
         final long ident = Binder.clearCallingIdentity();
-        final int result = dispatchPointer(null, ev, pid, uid);
-        if (sync) {
-            mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid);
+        
+        final int result;
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            result = mInputManager.injectMotionEvent(ev,
+                    InputQueue.INPUT_EVENT_NATURE_TOUCH, sync, pid, uid);
+        } else {
+            result = dispatchPointer(null, ev, pid, uid);
+            if (sync) {
+                mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid);
+            }
         }
+        
         Binder.restoreCallingIdentity(ident);
         switch (result) {
             case INJECT_NO_PERMISSION:
@@ -5555,10 +5943,18 @@
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
         final long ident = Binder.clearCallingIdentity();
-        final int result = dispatchTrackball(null, ev, pid, uid);
-        if (sync) {
-            mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid);
+        
+        final int result;
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            result = mInputManager.injectMotionEvent(ev,
+                    InputQueue.INPUT_EVENT_NATURE_TRACKBALL, sync, pid, uid);
+        } else {
+            result = dispatchTrackball(null, ev, pid, uid);
+            if (sync) {
+                mKeyWaiter.waitForNextEventTarget(null, null, null, false, true, pid, uid);
+            }
         }
+        
         Binder.restoreCallingIdentity(ident);
         switch (result) {
             case INJECT_NO_PERMISSION:
@@ -6326,14 +6722,8 @@
 
     private class KeyQ extends KeyInputQueue
             implements KeyInputQueue.FilterCallback {
-        PowerManager.WakeLock mHoldingScreen;
-
         KeyQ() {
             super(mContext, WindowManagerService.this);
-            PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
-            mHoldingScreen = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
-                    "KEEP_SCREEN_ON_FLAG");
-            mHoldingScreen.setReferenceCounted(false);
         }
 
         @Override
@@ -6445,21 +6835,6 @@
                     return FILTER_KEEP;
             }
         }
-
-        /**
-         * Must be called with the main window manager lock held.
-         */
-        void setHoldScreenLocked(boolean holding) {
-            boolean state = mHoldingScreen.isHeld();
-            if (holding != state) {
-                if (holding) {
-                    mHoldingScreen.acquire();
-                } else {
-                    mPolicy.screenOnStoppedLw();
-                    mHoldingScreen.release();
-                }
-            }
-        }
     }
 
     public boolean detectSafeMode() {
@@ -6788,8 +7163,14 @@
         }
 
         public int add(IWindow window, WindowManager.LayoutParams attrs,
+                int viewVisibility, Rect outContentInsets, InputChannel outInputChannel) {
+            return addWindow(this, window, attrs, viewVisibility, outContentInsets,
+                    outInputChannel);
+        }
+        
+        public int addWithoutInputChannel(IWindow window, WindowManager.LayoutParams attrs,
                 int viewVisibility, Rect outContentInsets) {
-            return addWindow(this, window, attrs, viewVisibility, outContentInsets);
+            return addWindow(this, window, attrs, viewVisibility, outContentInsets, null);
         }
 
         public void remove(IWindow window) {
@@ -7158,6 +7539,9 @@
         int mSurfaceLayer;
         float mSurfaceAlpha;
         
+        // Input channel
+        InputChannel mInputChannel;
+        
         WindowState(Session s, IWindow c, WindowToken token,
                WindowState attachedWindow, WindowManager.LayoutParams a,
                int viewVisibility) {
@@ -8182,6 +8566,15 @@
                 // Ignore if it has already been removed (usually because
                 // we are doing this as part of processing a death note.)
             }
+            
+            if (ENABLE_NATIVE_INPUT_DISPATCH) {
+                if (mInputChannel != null) {
+                    mInputManager.unregisterInputChannel(mInputChannel);
+                    
+                    mInputChannel.dispose();
+                    mInputChannel = null;
+                }
+            }
         }
 
         private class DeathRecipient implements IBinder.DeathRecipient {
@@ -8424,6 +8817,11 @@
                         pw.print(" mWallpaperYStep="); pw.println(mWallpaperYStep);
             }
         }
+        
+        String makeInputChannelName() {
+            return Integer.toHexString(System.identityHashCode(this))
+                + " " + mAttrs.getTitle();
+        }
 
         @Override
         public String toString() {
@@ -9275,7 +9673,8 @@
             IInputContext inputContext) {
         if (client == null) throw new IllegalArgumentException("null client");
         if (inputContext == null) throw new IllegalArgumentException("null inputContext");
-        return new Session(client, inputContext);
+        Session session = new Session(client, inputContext);
+        return session;
     }
 
     public boolean inputMethodClientHasFocus(IInputMethodClient client) {
@@ -10773,7 +11172,7 @@
         if (DEBUG_FREEZE) Slog.v(TAG, "Layout: mDisplayFrozen=" + mDisplayFrozen
                 + " holdScreen=" + holdScreen);
         if (!mDisplayFrozen) {
-            mQueue.setHoldScreenLocked(holdScreen != null);
+            setHoldScreenLocked(holdScreen != null);
             if (screenBrightness < 0 || screenBrightness > 1.0f) {
                 mPowerManager.setScreenBrightnessOverride(-1);
             } else {
@@ -10804,6 +11203,21 @@
         // be enabled, because the window obscured flags have changed.
         enableScreenIfNeededLocked();
     }
+    
+    /**
+     * Must be called with the main window manager lock held.
+     */
+    void setHoldScreenLocked(boolean holding) {
+        boolean state = mHoldingScreenWakeLock.isHeld();
+        if (holding != state) {
+            if (holding) {
+                mHoldingScreenWakeLock.acquire();
+            } else {
+                mPolicy.screenOnStoppedLw();
+                mHoldingScreenWakeLock.release();
+            }
+        }
+    }
 
     void requestAnimationLocked(long delay) {
         if (!mAnimationPending) {
@@ -11138,8 +11552,13 @@
             return;
         }
 
-        pw.println("Input State:");
-        mQueue.dump(pw, "  ");
+        if (ENABLE_NATIVE_INPUT_DISPATCH) {
+            pw.println("Input Dispatcher State:");
+            mInputManager.dump(pw);
+        } else {
+            pw.println("Input State:");
+            mQueue.dump(pw, "  ");
+        }
         pw.println(" ");
         
         synchronized(mWindowMap) {
diff --git a/services/jni/Android.mk b/services/jni/Android.mk
index b90e327..499ca86 100644
--- a/services/jni/Android.mk
+++ b/services/jni/Android.mk
@@ -5,6 +5,7 @@
     com_android_server_AlarmManagerService.cpp \
     com_android_server_BatteryService.cpp \
     com_android_server_KeyInputQueue.cpp \
+    com_android_server_InputManager.cpp \
     com_android_server_LightsService.cpp \
     com_android_server_SensorService.cpp \
     com_android_server_SystemServer.cpp \
@@ -16,6 +17,7 @@
 	$(JNI_H_INCLUDE)
 
 LOCAL_SHARED_LIBRARIES := \
+    libandroid_runtime \
 	libcutils \
 	libhardware \
 	libhardware_legacy \
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
new file mode 100644
index 0000000..53262ae
--- /dev/null
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -0,0 +1,746 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputManager-JNI"
+
+#include "JNIHelp.h"
+#include "jni.h"
+#include <android_runtime/AndroidRuntime.h>
+#include <ui/InputManager.h>
+#include <ui/InputTransport.h>
+#include <utils/Log.h>
+#include <utils/threads.h>
+#include "../../core/jni/android_view_KeyEvent.h"
+#include "../../core/jni/android_view_MotionEvent.h"
+#include "../../core/jni/android_view_InputChannel.h"
+#include "../../core/jni/android_view_InputTarget.h"
+
+namespace android {
+
+class InputDispatchPolicy : public InputDispatchPolicyInterface {
+public:
+    InputDispatchPolicy(JNIEnv* env, jobject callbacks);
+    virtual ~InputDispatchPolicy();
+
+    void setDisplaySize(int32_t displayId, int32_t width, int32_t height);
+    void setDisplayOrientation(int32_t displayId, int32_t orientation);
+
+    virtual bool getDisplayInfo(int32_t displayId,
+            int32_t* width, int32_t* height, int32_t* orientation);
+
+    virtual void notifyConfigurationChanged(nsecs_t when,
+            int32_t touchScreenConfig, int32_t keyboardConfig, int32_t navigationConfig);
+
+    virtual void notifyLidSwitchChanged(nsecs_t when, bool lidOpen);
+
+    virtual void virtualKeyFeedback(nsecs_t when, int32_t deviceId,
+            int32_t action, int32_t flags, int32_t keyCode,
+            int32_t scanCode, int32_t metaState, nsecs_t downTime);
+
+    virtual int32_t interceptKey(nsecs_t when, int32_t deviceId,
+            bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags);
+    virtual int32_t interceptTrackball(nsecs_t when, bool buttonChanged, bool buttonDown,
+            bool rolled);
+    virtual int32_t interceptTouch(nsecs_t when);
+
+    virtual bool filterTouchEvents();
+    virtual bool filterJumpyTouchEvents();
+    virtual void getVirtualKeyDefinitions(const String8& deviceName,
+            Vector<VirtualKeyDefinition>& outVirtualKeyDefinitions);
+    virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames);
+
+    virtual bool allowKeyRepeat();
+    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);
+
+private:
+    bool isScreenOn();
+    bool isScreenBright();
+
+private:
+    jobject mCallbacks;
+
+    int32_t mFilterTouchEvents;
+    int32_t mFilterJumpyTouchEvents;
+
+    Mutex mDisplayLock;
+    int32_t mDisplayWidth, mDisplayHeight;
+    int32_t mDisplayOrientation;
+
+    inline JNIEnv* threadEnv() const {
+        return AndroidRuntime::getJNIEnv();
+    }
+};
+
+
+// globals
+
+static sp<EventHub> gEventHub;
+static sp<InputDispatchPolicy> gInputDispatchPolicy;
+static sp<InputManager> gInputManager;
+
+// JNI
+
+static struct {
+    jclass clazz;
+
+    jmethodID isScreenOn;
+    jmethodID isScreenBright;
+    jmethodID notifyConfigurationChanged;
+    jmethodID notifyLidSwitchChanged;
+    jmethodID virtualKeyFeedback;
+    jmethodID hackInterceptKey;
+    jmethodID goToSleep;
+    jmethodID pokeUserActivityForKey;
+    jmethodID notifyAppSwitchComing;
+    jmethodID filterTouchEvents;
+    jmethodID filterJumpyTouchEvents;
+    jmethodID getVirtualKeyDefinitions;
+    jmethodID getExcludedDeviceNames;
+    jmethodID getKeyEventTargets;
+    jmethodID getMotionEventTargets;
+} gCallbacksClassInfo;
+
+static struct {
+    jclass clazz;
+
+    jfieldID scanCode;
+    jfieldID centerX;
+    jfieldID centerY;
+    jfieldID width;
+    jfieldID height;
+} gVirtualKeyDefinitionClassInfo;
+
+static bool checkInputManagerUnitialized(JNIEnv* env) {
+    if (gInputManager == NULL) {
+        LOGE("Input manager not initialized.");
+        jniThrowRuntimeException(env, "Input manager not initialized.");
+        return true;
+    }
+    return false;
+}
+
+static void android_server_InputManager_nativeInit(JNIEnv* env, jclass clazz,
+        jobject callbacks) {
+    if (gEventHub == NULL) {
+        gEventHub = new EventHub();
+    }
+
+    if (gInputDispatchPolicy == NULL) {
+        gInputDispatchPolicy = new InputDispatchPolicy(env, callbacks);
+    }
+
+    if (gInputManager == NULL) {
+        gInputManager = new InputManager(gEventHub, gInputDispatchPolicy);
+    }
+}
+
+static void android_server_InputManager_nativeStart(JNIEnv* env, jclass clazz) {
+    if (checkInputManagerUnitialized(env)) {
+        return;
+    }
+
+    status_t result = gInputManager->start();
+    if (result) {
+        jniThrowRuntimeException(env, "Input manager could not be started.");
+    }
+}
+
+static void android_server_InputManager_nativeSetDisplaySize(JNIEnv* env, jclass clazz,
+        jint displayId, jint width, jint height) {
+    if (checkInputManagerUnitialized(env)) {
+        return;
+    }
+
+    // XXX we could get this from the SurfaceFlinger directly instead of requiring it
+    // to be passed in like this, not sure which is better but leaving it like this
+    // keeps the window manager in direct control of when display transitions propagate down
+    // to the input dispatcher
+    gInputDispatchPolicy->setDisplaySize(displayId, width, height);
+}
+
+static void android_server_InputManager_nativeSetDisplayOrientation(JNIEnv* env, jclass clazz,
+        jint displayId, jint orientation) {
+    if (checkInputManagerUnitialized(env)) {
+        return;
+    }
+
+    gInputDispatchPolicy->setDisplayOrientation(displayId, orientation);
+}
+
+static jint android_server_InputManager_nativeGetScanCodeState(JNIEnv* env, jclass clazz,
+        jint deviceId, jint deviceClasses, jint scanCode) {
+    if (checkInputManagerUnitialized(env)) {
+        return KEY_STATE_UNKNOWN;
+    }
+
+    return gInputManager->getScanCodeState(deviceId, deviceClasses, scanCode);
+}
+
+static jint android_server_InputManager_nativeGetKeyCodeState(JNIEnv* env, jclass clazz,
+        jint deviceId, jint deviceClasses, jint keyCode) {
+    if (checkInputManagerUnitialized(env)) {
+        return KEY_STATE_UNKNOWN;
+    }
+
+    return gInputManager->getKeyCodeState(deviceId, deviceClasses, keyCode);
+}
+
+static jint android_server_InputManager_nativeGetSwitchState(JNIEnv* env, jclass clazz,
+        jint deviceId, jint deviceClasses, jint sw) {
+    if (checkInputManagerUnitialized(env)) {
+        return KEY_STATE_UNKNOWN;
+    }
+
+    return gInputManager->getSwitchState(deviceId, deviceClasses, sw);
+}
+
+static jboolean android_server_InputManager_nativeHasKeys(JNIEnv* env, jclass clazz,
+        jintArray keyCodes, jbooleanArray outFlags) {
+    if (checkInputManagerUnitialized(env)) {
+        return JNI_FALSE;
+    }
+
+    int32_t* codes = env->GetIntArrayElements(keyCodes, NULL);
+    uint8_t* flags = env->GetBooleanArrayElements(outFlags, NULL);
+    jsize numCodes = env->GetArrayLength(keyCodes);
+    jboolean result;
+    if (numCodes == env->GetArrayLength(outFlags)) {
+        result = gInputManager->hasKeys(numCodes, codes, flags);
+    } else {
+        result = JNI_FALSE;
+    }
+
+    env->ReleaseBooleanArrayElements(outFlags, flags, 0);
+    env->ReleaseIntArrayElements(keyCodes, codes, 0);
+    return result;
+}
+
+static void throwInputChannelNotInitialized(JNIEnv* env) {
+    jniThrowException(env, "java/lang/IllegalStateException",
+             "inputChannel is not initialized");
+}
+
+static void android_server_InputManager_handleInputChannelDisposed(JNIEnv* env,
+        jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data) {
+    LOGW("Input channel object '%s' was disposed without first being unregistered with "
+            "the input manager!", inputChannel->getName().string());
+
+    gInputManager->unregisterInputChannel(inputChannel);
+}
+
+static void android_server_InputManager_nativeRegisterInputChannel(JNIEnv* env, jclass clazz,
+        jobject inputChannelObj) {
+    if (checkInputManagerUnitialized(env)) {
+        return;
+    }
+
+    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
+            inputChannelObj);
+    if (inputChannel == NULL) {
+        throwInputChannelNotInitialized(env);
+        return;
+    }
+
+    status_t status = gInputManager->registerInputChannel(inputChannel);
+    if (status) {
+        jniThrowRuntimeException(env, "Failed to register input channel.  "
+                "Check logs for details.");
+        return;
+    }
+
+    android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
+            android_server_InputManager_handleInputChannelDisposed, NULL);
+}
+
+static void android_server_InputManager_nativeUnregisterInputChannel(JNIEnv* env, jclass clazz,
+        jobject inputChannelObj) {
+    if (checkInputManagerUnitialized(env)) {
+        return;
+    }
+
+    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
+            inputChannelObj);
+    if (inputChannel == NULL) {
+        throwInputChannelNotInitialized(env);
+        return;
+    }
+
+    android_view_InputChannel_setDisposeCallback(env, inputChannelObj, NULL, NULL);
+
+    status_t status = gInputManager->unregisterInputChannel(inputChannel);
+    if (status) {
+        jniThrowRuntimeException(env, "Failed to unregister input channel.  "
+                "Check logs for details.");
+    }
+}
+
+static JNINativeMethod gInputManagerMethods[] = {
+    /* name, signature, funcPtr */
+    { "nativeInit", "(Lcom/android/server/InputManager$Callbacks;)V",
+            (void*) android_server_InputManager_nativeInit },
+    { "nativeStart", "()V",
+            (void*) android_server_InputManager_nativeStart },
+    { "nativeSetDisplaySize", "(III)V",
+            (void*) android_server_InputManager_nativeSetDisplaySize },
+    { "nativeSetDisplayOrientation", "(II)V",
+            (void*) android_server_InputManager_nativeSetDisplayOrientation },
+    { "nativeGetScanCodeState", "(III)I",
+            (void*) android_server_InputManager_nativeGetScanCodeState },
+    { "nativeGetKeyCodeState", "(III)I",
+            (void*) android_server_InputManager_nativeGetKeyCodeState },
+    { "nativeGetSwitchState", "(III)I",
+            (void*) android_server_InputManager_nativeGetSwitchState },
+    { "nativeHasKeys", "([I[Z)Z",
+            (void*) android_server_InputManager_nativeHasKeys },
+    { "nativeRegisterInputChannel", "(Landroid/view/InputChannel;)V",
+            (void*) android_server_InputManager_nativeRegisterInputChannel },
+    { "nativeUnregisterInputChannel", "(Landroid/view/InputChannel;)V",
+            (void*) android_server_InputManager_nativeUnregisterInputChannel }
+};
+
+#define FIND_CLASS(var, className) \
+        var = env->FindClass(className); \
+        LOG_FATAL_IF(! var, "Unable to find class " className); \
+        var = jclass(env->NewGlobalRef(var));
+
+#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+        var = env->GetMethodID(clazz, methodName, methodDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find method " methodName);
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+        var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+int register_android_server_InputManager(JNIEnv* env) {
+    int res = jniRegisterNativeMethods(env, "com/android/server/InputManager",
+            gInputManagerMethods, NELEM(gInputManagerMethods));
+    LOG_FATAL_IF(res < 0, "Unable to register native methods.");
+
+    // Policy
+
+    FIND_CLASS(gCallbacksClassInfo.clazz, "com/android/server/InputManager$Callbacks");
+
+    GET_METHOD_ID(gCallbacksClassInfo.isScreenOn, gCallbacksClassInfo.clazz,
+            "isScreenOn", "()Z");
+
+    GET_METHOD_ID(gCallbacksClassInfo.isScreenBright, gCallbacksClassInfo.clazz,
+            "isScreenBright", "()Z");
+
+    GET_METHOD_ID(gCallbacksClassInfo.notifyConfigurationChanged, gCallbacksClassInfo.clazz,
+            "notifyConfigurationChanged", "(JIII)V");
+
+    GET_METHOD_ID(gCallbacksClassInfo.notifyLidSwitchChanged, gCallbacksClassInfo.clazz,
+            "notifyLidSwitchChanged", "(JZ)V");
+
+    GET_METHOD_ID(gCallbacksClassInfo.virtualKeyFeedback, gCallbacksClassInfo.clazz,
+            "virtualKeyFeedback", "(JIIIIIIJ)V");
+
+    GET_METHOD_ID(gCallbacksClassInfo.hackInterceptKey, gCallbacksClassInfo.clazz,
+            "hackInterceptKey", "(IIIIIIJZ)I");
+
+    GET_METHOD_ID(gCallbacksClassInfo.goToSleep, gCallbacksClassInfo.clazz,
+            "goToSleep", "(J)V");
+
+    GET_METHOD_ID(gCallbacksClassInfo.pokeUserActivityForKey, gCallbacksClassInfo.clazz,
+            "pokeUserActivityForKey", "(J)V");
+
+    GET_METHOD_ID(gCallbacksClassInfo.notifyAppSwitchComing, gCallbacksClassInfo.clazz,
+            "notifyAppSwitchComing", "()V");
+
+    GET_METHOD_ID(gCallbacksClassInfo.filterTouchEvents, gCallbacksClassInfo.clazz,
+            "filterTouchEvents", "()Z");
+
+    GET_METHOD_ID(gCallbacksClassInfo.filterJumpyTouchEvents, gCallbacksClassInfo.clazz,
+            "filterJumpyTouchEvents", "()Z");
+
+    GET_METHOD_ID(gCallbacksClassInfo.getVirtualKeyDefinitions, gCallbacksClassInfo.clazz,
+            "getVirtualKeyDefinitions",
+            "(Ljava/lang/String;)[Lcom/android/server/InputManager$VirtualKeyDefinition;");
+
+    GET_METHOD_ID(gCallbacksClassInfo.getExcludedDeviceNames, gCallbacksClassInfo.clazz,
+            "getExcludedDeviceNames", "()[Ljava/lang/String;");
+
+    GET_METHOD_ID(gCallbacksClassInfo.getKeyEventTargets, gCallbacksClassInfo.clazz,
+            "getKeyEventTargets", "(Landroid/view/KeyEvent;II)[Landroid/view/InputTarget;");
+
+    GET_METHOD_ID(gCallbacksClassInfo.getMotionEventTargets, gCallbacksClassInfo.clazz,
+            "getMotionEventTargets", "(Landroid/view/MotionEvent;II)[Landroid/view/InputTarget;");
+
+    // VirtualKeyDefinition
+
+    FIND_CLASS(gVirtualKeyDefinitionClassInfo.clazz,
+            "com/android/server/InputManager$VirtualKeyDefinition");
+
+    GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.scanCode, gVirtualKeyDefinitionClassInfo.clazz,
+            "scanCode", "I");
+
+    GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.centerX, gVirtualKeyDefinitionClassInfo.clazz,
+            "centerX", "I");
+
+    GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.centerY, gVirtualKeyDefinitionClassInfo.clazz,
+            "centerY", "I");
+
+    GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.width, gVirtualKeyDefinitionClassInfo.clazz,
+            "width", "I");
+
+    GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.height, gVirtualKeyDefinitionClassInfo.clazz,
+            "height", "I");
+
+    return 0;
+}
+
+// static functions
+
+static bool isAppSwitchKey(int32_t keyCode) {
+    return keyCode == KEYCODE_HOME || keyCode == KEYCODE_ENDCALL;
+}
+
+static bool checkException(JNIEnv* env, const char* methodName) {
+    if (env->ExceptionCheck()) {
+        LOGE("An exception was thrown by an InputDispatchPolicy callback '%s'.", methodName);
+        LOGE_EX(env);
+        env->ExceptionClear();
+        return true;
+    }
+    return false;
+}
+
+
+// InputDispatchPolicy implementation
+
+InputDispatchPolicy::InputDispatchPolicy(JNIEnv* env, jobject callbacks) :
+        mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1),
+        mDisplayWidth(-1), mDisplayHeight(-1), mDisplayOrientation(-1) {
+    mCallbacks = env->NewGlobalRef(callbacks);
+}
+
+InputDispatchPolicy::~InputDispatchPolicy() {
+    JNIEnv* env = threadEnv();
+
+    env->DeleteGlobalRef(mCallbacks);
+}
+
+void InputDispatchPolicy::setDisplaySize(int32_t displayId, int32_t width, int32_t height) {
+    if (displayId == 0) {
+        AutoMutex _l(mDisplayLock);
+
+        mDisplayWidth = width;
+        mDisplayHeight = height;
+    }
+}
+
+void InputDispatchPolicy::setDisplayOrientation(int32_t displayId, int32_t orientation) {
+    if (displayId == 0) {
+        AutoMutex _l(mDisplayLock);
+
+        mDisplayOrientation = orientation;
+    }
+}
+
+bool InputDispatchPolicy::getDisplayInfo(int32_t displayId,
+        int32_t* width, int32_t* height, int32_t* orientation) {
+    bool result = false;
+    if (displayId == 0) {
+        AutoMutex _l(mDisplayLock);
+
+        if (mDisplayWidth > 0) {
+            *width = mDisplayWidth;
+            *height = mDisplayHeight;
+            *orientation = mDisplayOrientation;
+            result = true;
+        }
+    }
+    return result;
+}
+
+bool InputDispatchPolicy::isScreenOn() {
+    JNIEnv* env = threadEnv();
+
+    jboolean result = env->CallBooleanMethod(mCallbacks, gCallbacksClassInfo.isScreenOn);
+    if (checkException(env, "isScreenOn")) {
+        return true;
+    }
+    return result;
+}
+
+bool InputDispatchPolicy::isScreenBright() {
+    JNIEnv* env = threadEnv();
+
+    jboolean result = env->CallBooleanMethod(mCallbacks, gCallbacksClassInfo.isScreenBright);
+    if (checkException(env, "isScreenBright")) {
+        return true;
+    }
+    return result;
+}
+
+void InputDispatchPolicy::notifyConfigurationChanged(nsecs_t when,
+        int32_t touchScreenConfig, int32_t keyboardConfig, int32_t navigationConfig) {
+    JNIEnv* env = threadEnv();
+
+    env->CallVoidMethod(mCallbacks, gCallbacksClassInfo.notifyConfigurationChanged,
+            when, touchScreenConfig, keyboardConfig, navigationConfig);
+    checkException(env, "notifyConfigurationChanged");
+}
+
+void InputDispatchPolicy::notifyLidSwitchChanged(nsecs_t when, bool lidOpen) {
+    JNIEnv* env = threadEnv();
+    env->CallVoidMethod(mCallbacks, gCallbacksClassInfo.notifyLidSwitchChanged,
+            when, lidOpen);
+    checkException(env, "notifyLidSwitchChanged");
+}
+
+void InputDispatchPolicy::virtualKeyFeedback(nsecs_t when, int32_t deviceId,
+        int32_t action, int32_t flags, int32_t keyCode,
+        int32_t scanCode, int32_t metaState, nsecs_t downTime) {
+    JNIEnv* env = threadEnv();
+
+    env->CallVoidMethod(mCallbacks, gCallbacksClassInfo.virtualKeyFeedback,
+            when, deviceId, action, flags, keyCode, scanCode, metaState, downTime);
+    checkException(env, "virtualKeyFeedback");
+}
+
+int32_t InputDispatchPolicy::interceptKey(nsecs_t when,
+        int32_t deviceId, bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags) {
+    const int32_t WM_ACTION_PASS_TO_USER = 1;
+    const int32_t WM_ACTION_POKE_USER_ACTIVITY = 2;
+    const int32_t WM_ACTION_GO_TO_SLEEP = 4;
+
+    JNIEnv* env = threadEnv();
+
+    bool isScreenOn = this->isScreenOn();
+    bool isScreenBright = this->isScreenBright();
+
+    jint wmActions = env->CallIntMethod(mCallbacks, gCallbacksClassInfo.hackInterceptKey,
+            deviceId, EV_KEY, scanCode, keyCode, policyFlags, down ? 1 : 0, when, isScreenOn);
+    if (checkException(env, "hackInterceptKey")) {
+        wmActions = 0;
+    }
+
+    int32_t actions = ACTION_NONE;
+    if (! isScreenOn) {
+        // Key presses and releases wake the device.
+        actions |= ACTION_WOKE_HERE;
+    }
+
+    if (! isScreenBright) {
+        // Key presses and releases brighten the screen if dimmed.
+        actions |= ACTION_BRIGHT_HERE;
+    }
+
+    if (wmActions & WM_ACTION_GO_TO_SLEEP) {
+        env->CallVoidMethod(mCallbacks, gCallbacksClassInfo.goToSleep, when);
+        checkException(env, "goToSleep");
+    }
+
+    if (wmActions & WM_ACTION_POKE_USER_ACTIVITY) {
+        env->CallVoidMethod(mCallbacks, gCallbacksClassInfo.pokeUserActivityForKey, when);
+        checkException(env, "pokeUserActivityForKey");
+    }
+
+    if (wmActions & WM_ACTION_PASS_TO_USER) {
+        actions |= ACTION_DISPATCH;
+    }
+
+    if (! (wmActions & WM_ACTION_PASS_TO_USER)) {
+        if (down && isAppSwitchKey(keyCode)) {
+            env->CallVoidMethod(mCallbacks, gCallbacksClassInfo.notifyAppSwitchComing);
+            checkException(env, "notifyAppSwitchComing");
+
+            actions |= ACTION_APP_SWITCH_COMING;
+        }
+    }
+    return actions;
+}
+
+int32_t InputDispatchPolicy::interceptTouch(nsecs_t when) {
+    if (! isScreenOn()) {
+        // Touch events do not wake the device.
+        return ACTION_NONE;
+    }
+
+    return ACTION_DISPATCH;
+}
+
+int32_t InputDispatchPolicy::interceptTrackball(nsecs_t when,
+        bool buttonChanged, bool buttonDown, bool rolled) {
+    if (! isScreenOn()) {
+        // Trackball motions and button presses do not wake the device.
+        return ACTION_NONE;
+    }
+
+    return ACTION_DISPATCH;
+}
+
+bool InputDispatchPolicy::filterTouchEvents() {
+    if (mFilterTouchEvents < 0) {
+        JNIEnv* env = threadEnv();
+
+        jboolean result = env->CallBooleanMethod(mCallbacks,
+                gCallbacksClassInfo.filterTouchEvents);
+        if (checkException(env, "filterTouchEvents")) {
+            result = false;
+        }
+
+        mFilterTouchEvents = result ? 1 : 0;
+    }
+    return mFilterTouchEvents;
+}
+
+bool InputDispatchPolicy::filterJumpyTouchEvents() {
+    if (mFilterJumpyTouchEvents < 0) {
+        JNIEnv* env = threadEnv();
+
+        jboolean result = env->CallBooleanMethod(mCallbacks,
+                gCallbacksClassInfo.filterJumpyTouchEvents);
+        if (checkException(env, "filterJumpyTouchEvents")) {
+            result = false;
+        }
+
+        mFilterJumpyTouchEvents = result ? 1 : 0;
+    }
+    return mFilterJumpyTouchEvents;
+}
+
+void InputDispatchPolicy::getVirtualKeyDefinitions(const String8& deviceName,
+        Vector<VirtualKeyDefinition>& outVirtualKeyDefinitions) {
+    JNIEnv* env = threadEnv();
+
+    jstring deviceNameStr = env->NewStringUTF(deviceName.string());
+    if (! checkException(env, "getVirtualKeyDefinitions")) {
+        jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacks,
+                gCallbacksClassInfo.getVirtualKeyDefinitions, deviceNameStr));
+        if (! checkException(env, "getVirtualKeyDefinitions") && result) {
+            jsize length = env->GetArrayLength(result);
+            for (jsize i = 0; i < length; i++) {
+                jobject item = env->GetObjectArrayElement(result, i);
+
+                outVirtualKeyDefinitions.add();
+                outVirtualKeyDefinitions.editTop().scanCode =
+                        int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.scanCode));
+                outVirtualKeyDefinitions.editTop().centerX =
+                        int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.centerX));
+                outVirtualKeyDefinitions.editTop().centerY =
+                        int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.centerY));
+                outVirtualKeyDefinitions.editTop().width =
+                        int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.width));
+                outVirtualKeyDefinitions.editTop().height =
+                        int32_t(env->GetIntField(item, gVirtualKeyDefinitionClassInfo.height));
+
+                env->DeleteLocalRef(item);
+            }
+            env->DeleteLocalRef(result);
+        }
+        env->DeleteLocalRef(deviceNameStr);
+    }
+}
+
+void InputDispatchPolicy::getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames) {
+    JNIEnv* env = threadEnv();
+
+    jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacks,
+            gCallbacksClassInfo.getExcludedDeviceNames));
+    if (! checkException(env, "getExcludedDeviceNames") && result) {
+        jsize length = env->GetArrayLength(result);
+        for (jsize i = 0; i < length; i++) {
+            jstring item = jstring(env->GetObjectArrayElement(result, i));
+
+            const char* deviceNameChars = env->GetStringUTFChars(item, NULL);
+            outExcludedDeviceNames.add(String8(deviceNameChars));
+            env->ReleaseStringUTFChars(item, deviceNameChars);
+
+            env->DeleteLocalRef(item);
+        }
+        env->DeleteLocalRef(result);
+    }
+}
+
+bool InputDispatchPolicy::allowKeyRepeat() {
+    // Disable key repeat when the screen is off.
+    return isScreenOn();
+}
+
+nsecs_t InputDispatchPolicy::getKeyRepeatTimeout() {
+    // TODO use ViewConfiguration.getLongPressTimeout()
+    return milliseconds_to_nanoseconds(500);
+}
+
+void InputDispatchPolicy::getKeyEventTargets(KeyEvent* keyEvent, uint32_t policyFlags,
+        Vector<InputTarget>& outTargets) {
+    JNIEnv* env = threadEnv();
+
+    jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
+    if (! keyEventObj) {
+        LOGE("Could not obtain DVM KeyEvent object to get key event targets.");
+    } else {
+        jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacks,
+                gCallbacksClassInfo.getKeyEventTargets,
+                keyEventObj, jint(keyEvent->getNature()), jint(policyFlags)));
+        if (! checkException(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);
+        }
+        env->DeleteLocalRef(keyEventObj);
+    }
+}
+
+void InputDispatchPolicy::getMotionEventTargets(MotionEvent* motionEvent, uint32_t policyFlags,
+        Vector<InputTarget>& outTargets) {
+    JNIEnv* env = threadEnv();
+
+    jobject motionEventObj = android_view_MotionEvent_fromNative(env, motionEvent);
+    if (! motionEventObj) {
+        LOGE("Could not obtain DVM MotionEvent object to get key event targets.");
+    } else {
+        jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacks,
+                gCallbacksClassInfo.getMotionEventTargets,
+                motionEventObj, jint(motionEvent->getNature()), jint(policyFlags)));
+        if (! checkException(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);
+        }
+        android_view_MotionEvent_recycle(env, motionEventObj);
+        env->DeleteLocalRef(motionEventObj);
+    }
+}
+
+} /* namespace android */
diff --git a/services/jni/com_android_server_KeyInputQueue.cpp b/services/jni/com_android_server_KeyInputQueue.cpp
index c92f8df..f9e3585 100644
--- a/services/jni/com_android_server_KeyInputQueue.cpp
+++ b/services/jni/com_android_server_KeyInputQueue.cpp
@@ -156,7 +156,7 @@
 {
     jint st = -1;
     gLock.lock();
-    if (gHub != NULL) st = gHub->getSwitchState(sw);
+    if (gHub != NULL) st = gHub->getSwitchState(-1, -1, sw);
     gLock.unlock();
     
     return st;
@@ -168,7 +168,7 @@
 {
     jint st = -1;
     gLock.lock();
-    if (gHub != NULL) st = gHub->getSwitchState(deviceId, sw);
+    if (gHub != NULL) st = gHub->getSwitchState(deviceId, -1, sw);
     gLock.unlock();
     
     return st;
@@ -180,7 +180,7 @@
 {
     jint st = -1;
     gLock.lock();
-    if (gHub != NULL) st = gHub->getScancodeState(sw);
+    if (gHub != NULL) st = gHub->getScanCodeState(0, -1, sw);
     gLock.unlock();
     
     return st;
@@ -192,7 +192,7 @@
 {
     jint st = -1;
     gLock.lock();
-    if (gHub != NULL) st = gHub->getScancodeState(deviceId, sw);
+    if (gHub != NULL) st = gHub->getScanCodeState(deviceId, -1, sw);
     gLock.unlock();
     
     return st;
@@ -204,7 +204,7 @@
 {
     jint st = -1;
     gLock.lock();
-    if (gHub != NULL) st = gHub->getKeycodeState(sw);
+    if (gHub != NULL) st = gHub->getKeyCodeState(0, -1, sw);
     gLock.unlock();
     
     return st;
@@ -216,7 +216,7 @@
 {
     jint st = -1;
     gLock.lock();
-    if (gHub != NULL) st = gHub->getKeycodeState(deviceId, sw);
+    if (gHub != NULL) st = gHub->getKeyCodeState(deviceId,-1, sw);
     gLock.unlock();
     
     return st;
@@ -247,7 +247,7 @@
 
     int32_t* codes = env->GetIntArrayElements(keyCodes, NULL);
     uint8_t* flags = env->GetBooleanArrayElements(outFlags, NULL);
-    size_t numCodes = env->GetArrayLength(keyCodes);
+    jsize numCodes = env->GetArrayLength(keyCodes);
     if (numCodes == env->GetArrayLength(outFlags)) {
         gLock.lock();
         if (gHub != NULL) ret = gHub->hasKeys(numCodes, codes, flags);
diff --git a/services/jni/onload.cpp b/services/jni/onload.cpp
index d11e7e1..a1a6838 100644
--- a/services/jni/onload.cpp
+++ b/services/jni/onload.cpp
@@ -7,6 +7,7 @@
 int register_android_server_AlarmManagerService(JNIEnv* env);
 int register_android_server_BatteryService(JNIEnv* env);
 int register_android_server_KeyInputQueue(JNIEnv* env);
+int register_android_server_InputManager(JNIEnv* env);
 int register_android_server_LightsService(JNIEnv* env);
 int register_android_server_SensorService(JNIEnv* env);
 int register_android_server_VibratorService(JNIEnv* env);
@@ -28,6 +29,7 @@
     LOG_ASSERT(env, "Could not retrieve the env!");
 
     register_android_server_KeyInputQueue(env);
+    register_android_server_InputManager(env);
     register_android_server_LightsService(env);
     register_android_server_AlarmManagerService(env);
     register_android_server_BatteryService(env);
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index 4201e80..f91f601 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -46,6 +46,7 @@
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
 import android.view.BridgeInflater;
+import android.view.InputChannel;
 import android.view.IWindow;
 import android.view.IWindowSession;
 import android.view.KeyEvent;
@@ -990,13 +991,21 @@
     private static final class WindowSession implements IWindowSession {
 
         @SuppressWarnings("unused")
-        public int add(IWindow arg0, LayoutParams arg1, int arg2, Rect arg3)
+        public int add(IWindow arg0, LayoutParams arg1, int arg2, Rect arg3,
+                InputChannel outInputchannel)
                 throws RemoteException {
             // pass for now.
             return 0;
         }
 
         @SuppressWarnings("unused")
+        public int addWithoutInputChannel(IWindow arg0, LayoutParams arg1, int arg2, Rect arg3)
+                throws RemoteException {
+            // pass for now.
+            return 0;
+        }
+        
+        @SuppressWarnings("unused")
         public void finishDrawing(IWindow arg0) throws RemoteException {
             // pass for now.
         }