static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
    if (env->ExceptionCheck()) {
        ALOGE("An exception was thrown by callback '%s'.", methodName);
        LOGE_EX(env);
        env->ExceptionClear();
        return true;
    }
    return false;
}
static inline void CheckExceptions(JNIEnv* env, const char* methodName) {
  if(!env->ExceptionCheck()) {
    return;
  }

  ALOGE("An exception was thrown by '%s'.", methodName);
  LOGE_EX(env);
  env->ExceptionClear();
}
jobject android_hardware_HardwareBuffer_createFromAHardwareBuffer(
        JNIEnv* env, AHardwareBuffer* hardwareBuffer) {
    GraphicBuffer* buffer = AHardwareBuffer_to_GraphicBuffer(hardwareBuffer);
    GraphicBufferWrapper* wrapper = new GraphicBufferWrapper(buffer);
    jobject hardwareBufferObj = env->NewObject(gHardwareBufferClassInfo.clazz,
            gHardwareBufferClassInfo.ctor, reinterpret_cast<jlong>(wrapper));
    if (hardwareBufferObj == NULL) {
        delete wrapper;
        if (env->ExceptionCheck()) {
            ALOGE("Could not create instance of HardwareBuffer from AHardwareBuffer.");
            LOGE_EX(env);
            env->ExceptionClear();
        }
        return nullptr;
    }
    return hardwareBufferObj;
}
jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent* event) {
    jobject eventObj = env->CallStaticObjectMethod(gMotionEventClassInfo.clazz,
            gMotionEventClassInfo.obtain);
    if (env->ExceptionCheck() || !eventObj) {
        ALOGE("An exception occurred while obtaining a motion event.");
        LOGE_EX(env);
        env->ExceptionClear();
        return NULL;
    }

    MotionEvent* destEvent = android_view_MotionEvent_getNativePtr(env, eventObj);
    if (!destEvent) {
        destEvent = new MotionEvent();
        android_view_MotionEvent_setNativePtr(env, eventObj, destEvent);
    }

    destEvent->copyFrom(event, true);
    return eventObj;
}
// Called each time a custom function is evaluated.
static void sqliteCustomFunctionCallback(sqlite3_context *context,
        int argc, sqlite3_value **argv) {
    JNIEnv* env = AndroidRuntime::getJNIEnv();

    // Get the callback function object.
    // Create a new local reference to it in case the callback tries to do something
    // dumb like unregister the function (thereby destroying the global ref) while it is running.
    jobject functionObjGlobal = reinterpret_cast<jobject>(sqlite3_user_data(context));
    jobject functionObj = env->NewLocalRef(functionObjGlobal);

    jobjectArray argsArray = env->NewObjectArray(argc, gStringClassInfo.clazz, NULL);
    if (argsArray) {
        for (int i = 0; i < argc; i++) {
            const jchar* arg = static_cast<const jchar*>(sqlite3_value_text16(argv[i]));
            if (!arg) {
                ALOGW("NULL argument in custom_function_callback.  This should not happen.");
            } else {
                size_t argLen = sqlite3_value_bytes16(argv[i]) / sizeof(jchar);
                jstring argStr = env->NewString(arg, argLen);
                if (!argStr) {
                    goto error; // out of memory error
                }
                env->SetObjectArrayElement(argsArray, i, argStr);
                env->DeleteLocalRef(argStr);
            }
        }

        // TODO: Support functions that return values.
        env->CallVoidMethod(functionObj,
                gSQLiteCustomFunctionClassInfo.dispatchCallback, argsArray);

error:
        env->DeleteLocalRef(argsArray);
    }

    env->DeleteLocalRef(functionObj);

    if (env->ExceptionCheck()) {
        ALOGE("An exception was thrown by custom SQLite function.");
        LOGE_EX(env);
        env->ExceptionClear();
    }
}
jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent* event) {
    jobject eventObj = env->CallStaticObjectMethod(gKeyEventClassInfo.clazz,
            gKeyEventClassInfo.obtain,
            nanoseconds_to_milliseconds(event->getDownTime()),
            nanoseconds_to_milliseconds(event->getEventTime()),
            event->getAction(),
            event->getKeyCode(),
            event->getRepeatCount(),
            event->getMetaState(),
            event->getDeviceId(),
            event->getScanCode(),
            event->getFlags(),
            event->getSource(),
            NULL);
    if (env->ExceptionCheck()) {
        LOGE("An exception occurred while obtaining a key event.");
        LOGE_EX(env);
        env->ExceptionClear();
        return NULL;
    }
    return eventObj;
}
jobject android_view_Surface_createFromIGraphicBufferProducer(JNIEnv* env,
        const sp<IGraphicBufferProducer>& bufferProducer) {
    if (bufferProducer == NULL) {
        return NULL;
    }

    sp<Surface> surface(new Surface(bufferProducer, true));
    if (surface == NULL) {
        return NULL;
    }

    jobject surfaceObj = env->NewObject(gSurfaceClassInfo.clazz, gSurfaceClassInfo.ctor, surface.get());
    if (surfaceObj == NULL) {
        if (env->ExceptionCheck()) {
            ALOGE("Could not create instance of Surface from IGraphicBufferProducer.");
            LOGE_EX(env);
            env->ExceptionClear();
        }
        return NULL;
    }
    surface->incStrong(&sRefBaseOwner);
    return surfaceObj;
}
static void custom_function_callback(sqlite3_context * context, int argc, sqlite3_value ** argv) {
    JNIEnv* env = AndroidRuntime::getJNIEnv();
    if (!env) {
        LOGE("custom_function_callback cannot call into Java on this thread");
        return;
    }
    // get global ref to CustomFunction object from our user data
    jobject function = (jobject)sqlite3_user_data(context);

    // pack up the arguments into a string array
    jobjectArray strArray = env->NewObjectArray(argc, string_class, NULL);
    if (!strArray)
        goto done;
    for (int i = 0; i < argc; i++) {
        char* arg = (char *)sqlite3_value_text(argv[i]);
        if (!arg) {
            LOGE("NULL argument in custom_function_callback.  This should not happen.");
            return;
        }
        jobject obj = env->NewStringUTF(arg);
        if (!obj)
            goto done;
        env->SetObjectArrayElement(strArray, i, obj);
        env->DeleteLocalRef(obj);
    }

    env->CallVoidMethod(function, method_custom_function_callback, strArray);
    env->DeleteLocalRef(strArray);

done:
    if (env->ExceptionCheck()) {
        LOGE("An exception was thrown by custom sqlite3 function.");
        LOGE_EX(env);
        env->ExceptionClear();
    }
}
int NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* data) {
    NativeInputQueue* q = static_cast<NativeInputQueue*>(data);
    JNIEnv* env = AndroidRuntime::getJNIEnv();

    sp<Connection> connection;
    InputEvent* inputEvent;
    jobject inputHandlerObjLocal;
    jlong finishedToken;
    { // acquire lock
        AutoMutex _l(q->mLock);

        ssize_t connectionIndex = q->mConnectionsByReceiveFd.indexOfKey(receiveFd);
        if (connectionIndex < 0) {
            ALOGE("Received spurious receive callback for unknown input channel.  "
                    "fd=%d, events=0x%x", receiveFd, events);
            return 0; // remove the callback
        }

        connection = q->mConnectionsByReceiveFd.valueAt(connectionIndex);
        if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
            ALOGE("channel '%s' ~ Publisher closed input channel or an error occurred.  "
                    "events=0x%x", connection->getInputChannelName(), events);
            return 0; // remove the callback
        }

        if (! (events & ALOOPER_EVENT_INPUT)) {
            ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event.  "
                    "events=0x%x", connection->getInputChannelName(), events);
            return 1;
        }

        status_t status = connection->inputConsumer.receiveDispatchSignal();
        if (status) {
            ALOGE("channel '%s' ~ Failed to receive dispatch signal.  status=%d",
                    connection->getInputChannelName(), status);
            return 0; // remove the callback
        }

        if (connection->messageInProgress) {
            ALOGW("channel '%s' ~ Publisher sent spurious dispatch signal.",
                    connection->getInputChannelName());
            return 1;
        }

        status = connection->inputConsumer.consume(& connection->inputEventFactory, & inputEvent);
        if (status) {
            ALOGW("channel '%s' ~ Failed to consume input event.  status=%d",
                    connection->getInputChannelName(), status);
            connection->inputConsumer.sendFinishedSignal(false);
            return 1;
        }

        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
        ALOGD("channel '%s' ~ Received key event.", connection->getInputChannelName());
#endif
        inputEventObj = android_view_KeyEvent_fromNative(env,
                static_cast<KeyEvent*>(inputEvent));
        dispatchMethodId = gInputQueueClassInfo.dispatchKeyEvent;
        break;

    case AINPUT_EVENT_TYPE_MOTION:
#if DEBUG_DISPATCH_CYCLE
        ALOGD("channel '%s' ~ Received motion event.", connection->getInputChannelName());
#endif
        inputEventObj = android_view_MotionEvent_obtainAsCopy(env,
                static_cast<MotionEvent*>(inputEvent));
        dispatchMethodId = gInputQueueClassInfo.dispatchMotionEvent;
        break;

    default:
        assert(false); // InputConsumer should prevent this from ever happening
        inputEventObj = NULL;
    }

    if (! inputEventObj) {
        ALOGW("channel '%s' ~ Failed to obtain DVM event object.",
                connection->getInputChannelName());
        env->DeleteLocalRef(inputHandlerObjLocal);
        q->finished(env, finishedToken, false, false);
        return 1;
    }

#if DEBUG_DISPATCH_CYCLE
    ALOGD("Invoking input handler.");
#endif
    env->CallStaticVoidMethod(gInputQueueClassInfo.clazz,
            dispatchMethodId, inputHandlerObjLocal, inputEventObj,
            jlong(finishedToken));
#if DEBUG_DISPATCH_CYCLE
    ALOGD("Returned from input handler.");
#endif

    if (env->ExceptionCheck()) {
        ALOGE("An exception occurred while invoking the input handler for an event.");
        LOGE_EX(env);
        env->ExceptionClear();

        q->finished(env, finishedToken, false, true /*ignoreSpuriousFinish*/);
    }

    env->DeleteLocalRef(inputEventObj);
    env->DeleteLocalRef(inputHandlerObjLocal);
    return 1;
}