diff options
author | Christopher Tate <ctate@google.com> | 2010-05-06 12:07:10 -0700 |
---|---|---|
committer | Christopher Tate <ctate@google.com> | 2010-05-06 17:00:54 -0700 |
commit | fa9e7c05c7be6891a6cf85a11dc635a6e6853078 (patch) | |
tree | 7a93833cff0b6f1b6b25c3cb77145b6a43ed431a /core/jni | |
parent | ab4f3c60daf6c5c0a5171550b526cafda20b7ca7 (diff) | |
download | frameworks_base-fa9e7c05c7be6891a6cf85a11dc635a6e6853078.zip frameworks_base-fa9e7c05c7be6891a6cf85a11dc635a6e6853078.tar.gz frameworks_base-fa9e7c05c7be6891a6cf85a11dc635a6e6853078.tar.bz2 |
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
Diffstat (limited to 'core/jni')
-rw-r--r-- | core/jni/Android.mk | 1 | ||||
-rw-r--r-- | core/jni/AndroidRuntime.cpp | 2 | ||||
-rw-r--r-- | core/jni/android_os_MessageQueue.cpp | 338 | ||||
-rw-r--r-- | core/jni/android_view_ViewRoot.cpp | 39 |
4 files changed, 379 insertions, 1 deletions
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 @@ LOCAL_SRC_FILES:= \ 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_nio_utils(JNIEnv* env); 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 @@ static const RegJNIRec gRegJNI[] = { 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 @@ static void android_view_ViewRoot_abandonGlCaches(JNIEnv* env, jobject) { 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 @@ static JNINativeMethod gMethods[] = { { "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) { |