/* * 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 0 // Log debug messages about registrations. #define DEBUG_REGISTRATION 0 #include "JNIHelp.h" #include #include #include #include #include #include #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(); ~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(uint16_t id, const sp& inputChannel, const sp& pollLoop); inline const char* getInputChannelName() const { return inputChannel->getName().string(); } // A unique id for this connection. uint16_t id; Status status; sp inputChannel; InputConsumer inputConsumer; sp 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. uint16_t messageSeqNum; // True if a message has been received from the publisher but not yet finished. bool messageInProgress; }; Mutex mLock; uint16_t mNextConnectionId; KeyedVector > mConnectionsByReceiveFd; ssize_t getConnectionIndex(const sp& inputChannel); static void handleInputChannelDisposed(JNIEnv* env, jobject inputChannelObj, const sp& inputChannel, void* data); static bool handleReceiveCallback(int receiveFd, int events, void* data); static jlong generateFinishedToken(int32_t receiveFd, uint16_t connectionId, uint16_t messageSeqNum); static void parseFinishedToken(jlong finishedToken, int32_t* outReceiveFd, uint16_t* outConnectionId, uint16_t* outMessageIndex); }; // ---------------------------------------------------------------------------- NativeInputQueue::NativeInputQueue() : mNextConnectionId(0) { } NativeInputQueue::~NativeInputQueue() { } status_t NativeInputQueue::registerInputChannel(JNIEnv* env, jobject inputChannelObj, jobject inputHandlerObj, jobject messageQueueObj) { sp inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj); if (inputChannel == NULL) { LOGW("Input channel is not initialized."); return BAD_VALUE; } #if DEBUG_REGISTRATION LOGD("channel '%s' - Registered", inputChannel->getName().string()); #endif sp pollLoop = android_os_MessageQueue_getPollLoop(env, messageQueueObj); { // acquire lock AutoMutex _l(mLock); if (getConnectionIndex(inputChannel) >= 0) { LOGW("Attempted to register already registered input channel '%s'", inputChannel->getName().string()); return BAD_VALUE; } uint16_t connectionId = mNextConnectionId++; sp connection = new Connection(connectionId, 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); int32_t receiveFd = inputChannel->getReceivePipeFd(); mConnectionsByReceiveFd.add(receiveFd, connection); pollLoop->setCallback(receiveFd, POLLIN, handleReceiveCallback, this); } // release lock android_view_InputChannel_setDisposeCallback(env, inputChannelObj, handleInputChannelDisposed, this); return OK; } status_t NativeInputQueue::unregisterInputChannel(JNIEnv* env, jobject inputChannelObj) { sp inputChannel = android_view_InputChannel_getInputChannel(env, inputChannelObj); if (inputChannel == NULL) { LOGW("Input channel is not initialized."); return BAD_VALUE; } #if DEBUG_REGISTRATION LOGD("channel '%s' - Unregistered", inputChannel->getName().string()); #endif { // acquire lock AutoMutex _l(mLock); ssize_t connectionIndex = getConnectionIndex(inputChannel); if (connectionIndex < 0) { LOGW("Attempted to unregister already unregistered input channel '%s'", inputChannel->getName().string()); return BAD_VALUE; } sp connection = mConnectionsByReceiveFd.valueAt(connectionIndex); mConnectionsByReceiveFd.removeItemsAt(connectionIndex); connection->status = Connection::STATUS_ZOMBIE; connection->pollLoop->removeCallback(inputChannel->getReceivePipeFd()); env->DeleteGlobalRef(connection->inputHandlerObjGlobal); connection->inputHandlerObjGlobal = NULL; if (connection->messageInProgress) { LOGI("Sending finished signal for input channel '%s' since it is being unregistered " "while an input message is still in progress.", connection->getInputChannelName()); connection->messageInProgress = false; connection->inputConsumer.sendFinishedSignal(); // ignoring result } } // release lock android_view_InputChannel_setDisposeCallback(env, inputChannelObj, NULL, NULL); return OK; } ssize_t NativeInputQueue::getConnectionIndex(const sp& inputChannel) { ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(inputChannel->getReceivePipeFd()); if (connectionIndex >= 0) { sp connection = mConnectionsByReceiveFd.valueAt(connectionIndex); if (connection->inputChannel.get() == inputChannel.get()) { return connectionIndex; } } return -1; } status_t NativeInputQueue::finished(JNIEnv* env, jlong finishedToken, bool ignoreSpuriousFinish) { int32_t receiveFd; uint16_t connectionId; uint16_t messageSeqNum; parseFinishedToken(finishedToken, &receiveFd, &connectionId, &messageSeqNum); { // acquire lock AutoMutex _l(mLock); ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd); if (connectionIndex < 0) { if (! ignoreSpuriousFinish) { LOGI("Ignoring finish signal on channel that is no longer registered."); } return DEAD_OBJECT; } sp connection = mConnectionsByReceiveFd.valueAt(connectionIndex); if (connectionId != connection->id) { if (! ignoreSpuriousFinish) { LOGI("Ignoring finish signal on channel that is no longer registered."); } return DEAD_OBJECT; } if (messageSeqNum != connection->messageSeqNum || ! connection->messageInProgress) { if (! ignoreSpuriousFinish) { LOGW("Attempted to finish input twice on channel '%s'. " "finished messageSeqNum=%d, current messageSeqNum=%d, messageInProgress=%d", connection->getInputChannelName(), messageSeqNum, connection->messageSeqNum, connection->messageInProgress); } 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, void* data) { LOGW("Input channel object '%s' was disposed without first being unregistered with " "the input queue!", inputChannel->getName().string()); NativeInputQueue* q = static_cast(data); q->unregisterInputChannel(env, inputChannelObj); } bool NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* data) { NativeInputQueue* q = static_cast(data); JNIEnv* env = AndroidRuntime::getJNIEnv(); sp 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->id, 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(); jobject inputEventObj; jmethodID dispatchMethodId; switch (inputEventType) { case AINPUT_EVENT_TYPE_KEY: #if DEBUG_DISPATCH_CYCLE LOGD("channel '%s' ~ Received key event.", connection->getInputChannelName()); #endif inputEventObj = android_view_KeyEvent_fromNative(env, static_cast(inputEvent)); dispatchMethodId = gInputQueueClassInfo.dispatchKeyEvent; break; case AINPUT_EVENT_TYPE_MOTION: #if DEBUG_DISPATCH_CYCLE LOGD("channel '%s' ~ Received motion event.", connection->getInputChannelName()); #endif inputEventObj = android_view_MotionEvent_fromNative(env, static_cast(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, 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, uint16_t connectionId, uint16_t messageSeqNum) { return (jlong(receiveFd) << 32) | (jlong(connectionId) << 16) | jlong(messageSeqNum); } void NativeInputQueue::parseFinishedToken(jlong finishedToken, int32_t* outReceiveFd, uint16_t* outConnectionId, uint16_t* outMessageIndex) { *outReceiveFd = int32_t(finishedToken >> 32); *outConnectionId = uint16_t(finishedToken >> 16); *outMessageIndex = uint16_t(finishedToken); } // ---------------------------------------------------------------------------- NativeInputQueue::Connection::Connection(uint16_t id, const sp& inputChannel, const sp& pollLoop) : id(id), 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*/); // We ignore the case where an event could not be finished because the input channel // was no longer registered (DEAD_OBJECT) since it is a common race that can occur // during application shutdown. The input dispatcher recovers gracefully anyways. if (status != OK && status != DEAD_OBJECT) { 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;J)V"); GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchMotionEvent, gInputQueueClassInfo.clazz, "dispatchMotionEvent", "(Landroid/view/InputHandler;Landroid/view/MotionEvent;J)V"); return 0; } } // namespace android