/* * Copyright (C) 2013 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" #include #include #include #include #include #include #include #include #include #include #include "JNIHelp.h" #include "android_os_MessageQueue.h" #include "android_view_KeyEvent.h" #include "android_view_MotionEvent.h" namespace android { static struct { jmethodID finishInputEvent; } gInputQueueClassInfo; enum { MSG_FINISH_INPUT = 1, }; InputQueue::InputQueue(jobject inputQueueObj, const sp& looper, int dispatchReadFd, int dispatchWriteFd) : mDispatchReadFd(dispatchReadFd), mDispatchWriteFd(dispatchWriteFd), mDispatchLooper(looper), mHandler(new WeakMessageHandler(this)) { JNIEnv* env = AndroidRuntime::getJNIEnv(); mInputQueueWeakGlobal = env->NewGlobalRef(inputQueueObj); } InputQueue::~InputQueue() { mDispatchLooper->removeMessages(mHandler); JNIEnv* env = AndroidRuntime::getJNIEnv(); env->DeleteGlobalRef(mInputQueueWeakGlobal); close(mDispatchReadFd); close(mDispatchWriteFd); } void InputQueue::attachLooper(Looper* looper, int ident, ALooper_callbackFunc callback, void* data) { Mutex::Autolock _l(mLock); for (size_t i = 0; i < mAppLoopers.size(); i++) { if (looper == mAppLoopers[i]) { return; } } mAppLoopers.push(looper); looper->addFd(mDispatchReadFd, ident, ALOOPER_EVENT_INPUT, callback, data); } void InputQueue::detachLooper() { Mutex::Autolock _l(mLock); detachLooperLocked(); } void InputQueue::detachLooperLocked() { for (size_t i = 0; i < mAppLoopers.size(); i++) { mAppLoopers[i]->removeFd(mDispatchReadFd); } mAppLoopers.clear(); } bool InputQueue::hasEvents() { Mutex::Autolock _l(mLock); return mPendingEvents.size() > 0; } status_t InputQueue::getEvent(InputEvent** outEvent) { Mutex::Autolock _l(mLock); *outEvent = NULL; if (!mPendingEvents.isEmpty()) { *outEvent = mPendingEvents[0]; mPendingEvents.removeAt(0); } if (mPendingEvents.isEmpty()) { char byteread[16]; ssize_t nRead; do { nRead = TEMP_FAILURE_RETRY(read(mDispatchReadFd, &byteread, sizeof(byteread))); if (nRead < 0 && errno != EAGAIN) { ALOGW("Failed to read from native dispatch pipe: %s", strerror(errno)); } } while (nRead > 0); } return *outEvent != NULL ? OK : WOULD_BLOCK; } bool InputQueue::preDispatchEvent(InputEvent* e) { if (e->getType() == AINPUT_EVENT_TYPE_KEY) { KeyEvent* keyEvent = static_cast(e); if (keyEvent->getFlags() & AKEY_EVENT_FLAG_PREDISPATCH) { finishEvent(e, false); return true; } } return false; } void InputQueue::finishEvent(InputEvent* event, bool handled) { Mutex::Autolock _l(mLock); mFinishedEvents.push(key_value_pair_t(event, handled)); if (mFinishedEvents.size() == 1) { mDispatchLooper->sendMessage(this, Message(MSG_FINISH_INPUT)); } } void InputQueue::handleMessage(const Message& message) { switch(message.what) { case MSG_FINISH_INPUT: JNIEnv* env = AndroidRuntime::getJNIEnv(); ScopedLocalRef inputQueueObj(env, jniGetReferent(env, mInputQueueWeakGlobal)); if (!inputQueueObj.get()) { ALOGW("InputQueue was finalized without being disposed"); return; } while (true) { InputEvent* event; bool handled; { Mutex::Autolock _l(mLock); if (mFinishedEvents.isEmpty()) { break; } event = mFinishedEvents[0].getKey(); handled = mFinishedEvents[0].getValue(); mFinishedEvents.removeAt(0); } env->CallVoidMethod(inputQueueObj.get(), gInputQueueClassInfo.finishInputEvent, reinterpret_cast(event), handled); recycleInputEvent(event); } break; } } void InputQueue::recycleInputEvent(InputEvent* event) { mPooledInputEventFactory.recycle(event); } KeyEvent* InputQueue::createKeyEvent() { return mPooledInputEventFactory.createKeyEvent(); } MotionEvent* InputQueue::createMotionEvent() { return mPooledInputEventFactory.createMotionEvent(); } void InputQueue::enqueueEvent(InputEvent* event) { Mutex::Autolock _l(mLock); mPendingEvents.push(event); if (mPendingEvents.size() == 1) { char dummy = 0; int res = TEMP_FAILURE_RETRY(write(mDispatchWriteFd, &dummy, sizeof(dummy))); if (res < 0 && errno != EAGAIN) { ALOGW("Failed writing to dispatch fd: %s", strerror(errno)); } } } InputQueue* InputQueue::createQueue(jobject inputQueueObj, const sp& looper) { int pipeFds[2]; if (pipe(pipeFds)) { ALOGW("Could not create native input dispatching pipe: %s", strerror(errno)); return NULL; } fcntl(pipeFds[0], F_SETFL, O_NONBLOCK); fcntl(pipeFds[1], F_SETFL, O_NONBLOCK); return new InputQueue(inputQueueObj, looper, pipeFds[0], pipeFds[1]); } static jlong nativeInit(JNIEnv* env, jobject clazz, jobject queueWeak, jobject jMsgQueue) { sp messageQueue = android_os_MessageQueue_getMessageQueue(env, jMsgQueue); if (messageQueue == NULL) { jniThrowRuntimeException(env, "MessageQueue is not initialized."); return 0; } sp queue = InputQueue::createQueue(queueWeak, messageQueue->getLooper()); if (!queue.get()) { jniThrowRuntimeException(env, "InputQueue failed to initialize"); return 0; } queue->incStrong(&gInputQueueClassInfo); return reinterpret_cast(queue.get()); } static void nativeDispose(JNIEnv* env, jobject clazz, jlong ptr) { sp queue = reinterpret_cast(ptr); queue->detachLooper(); queue->decStrong(&gInputQueueClassInfo); } static jlong nativeSendKeyEvent(JNIEnv* env, jobject clazz, jlong ptr, jobject eventObj, jboolean predispatch) { InputQueue* queue = reinterpret_cast(ptr); KeyEvent* event = queue->createKeyEvent(); status_t status = android_view_KeyEvent_toNative(env, eventObj, event); if (status) { queue->recycleInputEvent(event); jniThrowRuntimeException(env, "Could not read contents of KeyEvent object."); return -1; } if (predispatch) { event->setFlags(event->getFlags() | AKEY_EVENT_FLAG_PREDISPATCH); } queue->enqueueEvent(event); return reinterpret_cast(event); } static jlong nativeSendMotionEvent(JNIEnv* env, jobject clazz, jlong ptr, jobject eventObj) { sp queue = reinterpret_cast(ptr); MotionEvent* originalEvent = android_view_MotionEvent_getNativePtr(env, eventObj); if (!originalEvent) { jniThrowRuntimeException(env, "Could not obtain MotionEvent pointer."); return -1; } MotionEvent* event = queue->createMotionEvent(); event->copyFrom(originalEvent, true /* keepHistory */); queue->enqueueEvent(event); return reinterpret_cast(event); } static const JNINativeMethod g_methods[] = { { "nativeInit", "(Ljava/lang/ref/WeakReference;Landroid/os/MessageQueue;)J", (void*) nativeInit }, { "nativeDispose", "(J)V", (void*) nativeDispose }, { "nativeSendKeyEvent", "(JLandroid/view/KeyEvent;Z)J", (void*) nativeSendKeyEvent }, { "nativeSendMotionEvent", "(JLandroid/view/MotionEvent;)J", (void*) nativeSendMotionEvent }, }; static const char* const kInputQueuePathName = "android/view/InputQueue"; #define FIND_CLASS(var, className) \ do { \ var = env->FindClass(className); \ LOG_FATAL_IF(! var, "Unable to find class %s", className); \ } while(0) #define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \ do { \ var = env->GetMethodID(clazz, methodName, fieldDescriptor); \ LOG_FATAL_IF(! var, "Unable to find method" methodName); \ } while(0) int register_android_view_InputQueue(JNIEnv* env) { jclass clazz; FIND_CLASS(clazz, kInputQueuePathName); GET_METHOD_ID(gInputQueueClassInfo.finishInputEvent, clazz, "finishInputEvent", "(JZ)V"); return AndroidRuntime::registerNativeMethods( env, kInputQueuePathName, g_methods, NELEM(g_methods)); } } // namespace android