blob: 8984057423be708db300b18a800f0cb769408c3d [file] [log] [blame]
/*
* 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