Native input dispatch rewrite work in progress.

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

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

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

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

End-to-end event dispatch mostly works!

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

Change-Id: I8c36b2b9e0a2d27392040ecda0f51b636456de25
diff --git a/core/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) {