am 46b9ac0a: Native input dispatch rewrite work in progress.

Merge commit '46b9ac0ae2162309774a7478cd9d4e578747bfc2' into gingerbread

* commit '46b9ac0ae2162309774a7478cd9d4e578747bfc2':
  Native input dispatch rewrite work in progress.
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.
         }