From 3bdcdd8531781569d501e7023c22e25e2bae0dd1 Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Tue, 10 Apr 2012 20:36:07 -0700 Subject: [PATCH] Be more careful about exceptions in input callbacks. consumeEvents() may be called reentrantly so we need to be careful when handling exceptions. When called directly through JNI, the exception should be allowed to bubble up to the caller. When called from a Looper callback, the exception should be recorded on the MessageQueue and bubbled when the call to nativePollOnce() returns. Bug: 6312938 Change-Id: Ief5e315802f586aa85af7eef1bd6e9bea4ce24ab --- core/jni/android_view_InputEventReceiver.cpp | 89 +++++++++++--------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp index 348437d0f34ab..8f6f5f4966a3b 100644 --- a/core/jni/android_view_InputEventReceiver.cpp +++ b/core/jni/android_view_InputEventReceiver.cpp @@ -52,8 +52,7 @@ public: status_t initialize(); status_t finishInputEvent(uint32_t seq, bool handled); - status_t consumeEvents(bool consumeBatches); - static int handleReceiveCallback(int receiveFd, int events, void* data); + status_t consumeEvents(JNIEnv* env, bool consumeBatches); protected: virtual ~NativeInputEventReceiver(); @@ -68,6 +67,8 @@ private: const char* getInputChannelName() { return mInputConsumer.getChannel()->getName().string(); } + + static int handleReceiveCallback(int receiveFd, int events, void* data); }; @@ -128,11 +129,13 @@ int NativeInputEventReceiver::handleReceiveCallback(int receiveFd, int events, v return 1; } - status_t status = r->consumeEvents(false /*consumeBatches*/); + JNIEnv* env = AndroidRuntime::getJNIEnv(); + status_t status = r->consumeEvents(env, false /*consumeBatches*/); + r->mMessageQueue->raiseAndClearException(env, "handleReceiveCallback"); return status == OK || status == NO_MEMORY ? 1 : 0; } -status_t NativeInputEventReceiver::consumeEvents(bool consumeBatches) { +status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, bool consumeBatches) { #if DEBUG_DISPATCH_CYCLE ALOGD("channel '%s' ~ Consuming input events, consumeBatches=%s.", getInputChannelName(), consumeBatches ? "true" : "false"); @@ -142,7 +145,7 @@ status_t NativeInputEventReceiver::consumeEvents(bool consumeBatches) { mBatchedInputEventPending = false; } - JNIEnv* env = AndroidRuntime::getJNIEnv(); + bool skipCallbacks = false; for (;;) { uint32_t seq; InputEvent* inputEvent; @@ -150,7 +153,8 @@ status_t NativeInputEventReceiver::consumeEvents(bool consumeBatches) { consumeBatches, &seq, &inputEvent); if (status) { if (status == WOULD_BLOCK) { - if (mInputConsumer.hasPendingBatch() && !mBatchedInputEventPending) { + if (!skipCallbacks && !mBatchedInputEventPending + && mInputConsumer.hasPendingBatch()) { // There is a pending batch. Come back later. mBatchedInputEventPending = true; #if DEBUG_DISPATCH_CYCLE @@ -159,8 +163,8 @@ status_t NativeInputEventReceiver::consumeEvents(bool consumeBatches) { #endif env->CallVoidMethod(mReceiverObjGlobal, gInputEventReceiverClassInfo.dispatchBatchedInputEventPending); - if (mMessageQueue->raiseAndClearException( - env, "dispatchBatchedInputEventPending")) { + if (env->ExceptionCheck()) { + ALOGE("Exception dispatching batched input events."); mBatchedInputEventPending = false; // try again later } } @@ -172,46 +176,47 @@ status_t NativeInputEventReceiver::consumeEvents(bool consumeBatches) { } assert(inputEvent); - jobject inputEventObj; - switch (inputEvent->getType()) { - case AINPUT_EVENT_TYPE_KEY: + if (!skipCallbacks) { + jobject inputEventObj; + switch (inputEvent->getType()) { + case AINPUT_EVENT_TYPE_KEY: #if DEBUG_DISPATCH_CYCLE - ALOGD("channel '%s' ~ Received key event.", getInputChannelName()); + ALOGD("channel '%s' ~ Received key event.", getInputChannelName()); #endif - inputEventObj = android_view_KeyEvent_fromNative(env, - static_cast(inputEvent)); - mMessageQueue->raiseAndClearException(env, "new KeyEvent"); - break; + inputEventObj = android_view_KeyEvent_fromNative(env, + static_cast(inputEvent)); + break; - case AINPUT_EVENT_TYPE_MOTION: + case AINPUT_EVENT_TYPE_MOTION: #if DEBUG_DISPATCH_CYCLE - ALOGD("channel '%s' ~ Received motion event.", getInputChannelName()); + ALOGD("channel '%s' ~ Received motion event.", getInputChannelName()); #endif - inputEventObj = android_view_MotionEvent_obtainAsCopy(env, - static_cast(inputEvent)); - mMessageQueue->raiseAndClearException(env, "new MotionEvent"); - break; + inputEventObj = android_view_MotionEvent_obtainAsCopy(env, + static_cast(inputEvent)); + break; - default: - assert(false); // InputConsumer should prevent this from ever happening - inputEventObj = NULL; + default: + assert(false); // InputConsumer should prevent this from ever happening + inputEventObj = NULL; + } + + if (inputEventObj) { +#if DEBUG_DISPATCH_CYCLE + ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName()); +#endif + env->CallVoidMethod(mReceiverObjGlobal, + gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj); + if (env->ExceptionCheck()) { + ALOGE("Exception dispatching input event."); + skipCallbacks = true; + } + } else { + ALOGW("channel '%s' ~ Failed to obtain event object.", getInputChannelName()); + skipCallbacks = true; + } } - if (!inputEventObj) { - ALOGW("channel '%s' ~ Failed to obtain event object.", getInputChannelName()); - mInputConsumer.sendFinishedSignal(seq, false); - continue; - } - -#if DEBUG_DISPATCH_CYCLE - ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName()); -#endif - env->CallVoidMethod(mReceiverObjGlobal, - gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj); - - env->DeleteLocalRef(inputEventObj); - - if (mMessageQueue->raiseAndClearException(env, "dispatchInputEvent")) { + if (skipCallbacks) { mInputConsumer.sendFinishedSignal(seq, false); } } @@ -268,8 +273,8 @@ static void nativeFinishInputEvent(JNIEnv* env, jclass clazz, jint receiverPtr, static void nativeConsumeBatchedInputEvents(JNIEnv* env, jclass clazz, jint receiverPtr) { sp receiver = reinterpret_cast(receiverPtr); - status_t status = receiver->consumeEvents(true /*consumeBatches*/); - if (status && status != DEAD_OBJECT) { + status_t status = receiver->consumeEvents(env, true /*consumeBatches*/); + if (status && status != DEAD_OBJECT && !env->ExceptionCheck()) { String8 message; message.appendFormat("Failed to consume batched input event. status=%d", status); jniThrowRuntimeException(env, message.string());