Sketch of Native input for MessageQueue / Looper / ViewRoot

MessageQueue now uses a socket for internal signalling, and is prepared
to also handle any number of event input pipes, once the plumbing is
set up with ViewRoot / Looper to tell it about them as appropriate.

Change-Id: If9eda174a6c26887dc51b12b14b390e724e73ab3
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index bc653d6..d394a46 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -36,8 +36,9 @@
     Message mMessages;
     private final ArrayList mIdleHandlers = new ArrayList();
     private boolean mQuiting = false;
+    private int mObject = 0;    // used by native code
     boolean mQuitAllowed = true;
-    
+
     /**
      * Callback interface for discovering when a thread is going to block
      * waiting for more messages.
@@ -85,16 +86,49 @@
         }
     }
 
-    MessageQueue() {
+    // 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);
+
     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;
 
         while (true) {
             long now;
             Object[] idlers = null;
-    
+
+            nativeWaitForNext(timeToNextEventMillis);
+
             // Try to retrieve the next message, returning if found.
             synchronized (this) {
                 now = SystemClock.uptimeMillis();
@@ -135,20 +169,17 @@
 
             synchronized (this) {
                 // No messages, nobody to tell about it...  time to wait!
-                try {
-                    if (mMessages != null) {
-                        if (mMessages.when-now > 0) {
-                            Binder.flushPendingCommands();
-                            this.wait(mMessages.when-now);
-                        }
-                    } else {
+                if (mMessages != null) {
+                    if (mMessages.when - now > 0) {
                         Binder.flushPendingCommands();
-                        this.wait();
+                        timeToNextEventMillis = mMessages.when - now;
                     }
-                }
-                catch (InterruptedException e) {
+                } else {
+                    Binder.flushPendingCommands();
+                    timeToNextEventMillis = -1;
                 }
             }
+            // loop to the while(true) and do the appropriate nativeWait(when)
         }
     }
 
@@ -190,7 +221,6 @@
             if (p == null || when == 0 || when < p.when) {
                 msg.next = p;
                 mMessages = msg;
-                this.notify();
             } else {
                 Message prev = null;
                 while (p != null && p.when <= when) {
@@ -199,8 +229,8 @@
                 }
                 msg.next = prev.next;
                 prev.next = msg;
-                this.notify();
             }
+            nativeSignal();
         }
         return true;
     }
@@ -321,7 +351,7 @@
     void poke()
     {
         synchronized (this) {
-            this.notify();
+            nativeSignal();
         }
     }
 }
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 0a3b2cf..d26f066 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -179,7 +179,7 @@
     /**
      * An InputStream you can create on a ParcelFileDescriptor, which will
      * take care of calling {@link ParcelFileDescriptor#close
-     * ParcelFileDescritor.close()} for you when the stream is closed.
+     * ParcelFileDescriptor.close()} for you when the stream is closed.
      */
     public static class AutoCloseInputStream extends FileInputStream {
         private final ParcelFileDescriptor mFd;
@@ -198,7 +198,7 @@
     /**
      * An OutputStream you can create on a ParcelFileDescriptor, which will
      * take care of calling {@link ParcelFileDescriptor#close
-     * ParcelFileDescritor.close()} for you when the stream is closed.
+     * ParcelFileDescriptor.close()} for you when the stream is closed.
      */
     public static class AutoCloseOutputStream extends FileOutputStream {
         private final ParcelFileDescriptor mFd;
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 1daf580..a9dd844 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -26,12 +26,12 @@
 import android.graphics.Region;
 import android.os.*;
 import android.os.Process;
-import android.os.SystemProperties;
 import android.util.AndroidRuntimeException;
 import android.util.Config;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.EventLog;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.view.View.MeasureSpec;
 import android.view.accessibility.AccessibilityEvent;
@@ -50,6 +50,7 @@
 import android.media.AudioManager;
 
 import java.lang.ref.WeakReference;
+import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.ArrayList;
@@ -76,6 +77,7 @@
     /** @noinspection PointlessBooleanExpression*/
     private static final boolean DEBUG_DRAW = false || LOCAL_LOGV;
     private static final boolean DEBUG_LAYOUT = false || LOCAL_LOGV;
+    private static final boolean DEBUG_INPUT = true || LOCAL_LOGV;
     private static final boolean DEBUG_INPUT_RESIZE = false || LOCAL_LOGV;
     private static final boolean DEBUG_ORIENTATION = false || LOCAL_LOGV;
     private static final boolean DEBUG_TRACKBALL = false || LOCAL_LOGV;
@@ -425,6 +427,9 @@
         }
     }
 
+    // fd [0] is the receiver, [1] is the sender
+    private native int[] makeInputChannel();
+
     /**
      * We have one child
      */
@@ -469,6 +474,14 @@
                 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.
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 37b5873..dbad7e9 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -51,6 +51,7 @@
 	android_os_Debug.cpp \
 	android_os_FileUtils.cpp \
 	android_os_MemoryFile.cpp \
+	android_os_MessageQueue.cpp \
 	android_os_ParcelFileDescriptor.cpp \
 	android_os_Power.cpp \
 	android_os_StatFs.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 4039039..76df9db 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -128,6 +128,7 @@
 extern int register_android_pim_EventRecurrence(JNIEnv* env);
 extern int register_android_text_format_Time(JNIEnv* env);
 extern int register_android_os_Debug(JNIEnv* env);
+extern int register_android_os_MessageQueue(JNIEnv* env);
 extern int register_android_os_ParcelFileDescriptor(JNIEnv *env);
 extern int register_android_os_Power(JNIEnv *env);
 extern int register_android_os_StatFs(JNIEnv *env);
@@ -1249,6 +1250,7 @@
     REG_JNI(register_android_os_Debug),
     REG_JNI(register_android_os_FileObserver),
     REG_JNI(register_android_os_FileUtils),
+    REG_JNI(register_android_os_MessageQueue),
     REG_JNI(register_android_os_ParcelFileDescriptor),
     REG_JNI(register_android_os_Power),
     REG_JNI(register_android_os_StatFs),
diff --git a/core/jni/android_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp
new file mode 100644
index 0000000..8984057
--- /dev/null
+++ b/core/jni/android_os_MessageQueue.cpp
@@ -0,0 +1,338 @@
+/*
+ * 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 "MQNative"
+
+#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/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;
+}
+
+// ----------------------------------------------------------------------------
+
+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);
+
+    env->SetIntField(obj, gMessageQueueOffsets.mObject, (jint)mqn);
+}
+
+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");
+    }
+}
+
+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;
+}
+
+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");
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+const char* const kKeyEventPathName = "android/view/KeyEvent";
+const char* const kMessagePathName = "android/os/Message";
+const char* const kMessageQueuePathName = "android/os/MessageQueue";
+
+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 },
+};
+
+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,
+            gMessageQueueMethods, NELEM(gMessageQueueMethods));
+}
+
+
+}; // end of namespace android
diff --git a/core/jni/android_view_ViewRoot.cpp b/core/jni/android_view_ViewRoot.cpp
index 9d62d89..70ad8c5 100644
--- a/core/jni/android_view_ViewRoot.cpp
+++ b/core/jni/android_view_ViewRoot.cpp
@@ -16,6 +16,7 @@
 
 #include <stdio.h>
 #include <assert.h>
+#include <sys/socket.h>
 
 #include <core/SkCanvas.h>
 #include <core/SkDevice.h>
@@ -24,6 +25,7 @@
 #include "GraphicsJNI.h"
 
 #include "jni.h"
+#include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/misc.h>
 
@@ -78,6 +80,39 @@
     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;
+}
+
 // ----------------------------------------------------------------------------
 
 const char* const kClassPathName = "android/view/ViewRoot";
@@ -86,7 +121,9 @@
     {   "nativeShowFPS", "(Landroid/graphics/Canvas;I)V",
                                         (void*)android_view_ViewRoot_showFPS },
     {   "nativeAbandonGlCaches", "()V", 
-                                (void*)android_view_ViewRoot_abandonGlCaches }
+                                (void*)android_view_ViewRoot_abandonGlCaches },
+    {   "makeInputChannel", "()[I",
+                                        (void*)android_view_ViewRoot_makeInputChannel }
 };
 
 int register_android_view_ViewRoot(JNIEnv* env) {