/*
 * 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.
 */

#include <stdio.h>

//#define LOG_NDEBUG 0
#define LOG_TAG "AudioEffects-JNI"

#include <utils/Log.h>
#include <nativehelper/jni.h>
#include <nativehelper/JNIHelp.h>
#include <android_runtime/AndroidRuntime.h>
#include "media/AudioEffect.h"

using namespace android;

#define AUDIOEFFECT_SUCCESS                      0
#define AUDIOEFFECT_ERROR                       -1
#define AUDIOEFFECT_ERROR_ALREADY_EXISTS        -2
#define AUDIOEFFECT_ERROR_NO_INIT               -3
#define AUDIOEFFECT_ERROR_BAD_VALUE             -4
#define AUDIOEFFECT_ERROR_INVALID_OPERATION     -5
#define AUDIOEFFECT_ERROR_NO_MEMORY             -6
#define AUDIOEFFECT_ERROR_DEAD_OBJECT           -7

// ----------------------------------------------------------------------------
static const char* const kClassPathName = "android/media/AudioEffect";

struct fields_t {
    // these fields provide access from C++ to the...
    jclass    clazzEffect;          // AudioEffect class
    jmethodID midPostNativeEvent;   // event post callback method
    jfieldID  fidNativeAudioEffect; // stores in Java the native AudioEffect object
    jfieldID  fidJniData;           // stores in Java additional resources used by the native AudioEffect
    jclass    clazzDesc;            // AudioEffect.Descriptor class
    jmethodID midDescCstor;         // AudioEffect.Descriptor class constructor
};
static fields_t fields;

struct effect_callback_cookie {
    jclass      audioEffect_class;  // AudioEffect class
    jobject     audioEffect_ref;    // AudioEffect object instance
 };

// ----------------------------------------------------------------------------
class AudioEffectJniStorage {
    public:
        effect_callback_cookie mCallbackData;

    AudioEffectJniStorage() {
    }

    ~AudioEffectJniStorage() {
    }

};


static jint translateError(int code) {
    switch(code) {
    case NO_ERROR:
        return AUDIOEFFECT_SUCCESS;
    case ALREADY_EXISTS:
        return AUDIOEFFECT_ERROR_ALREADY_EXISTS;
    case NO_INIT:
        return AUDIOEFFECT_ERROR_NO_INIT;
    case BAD_VALUE:
        return AUDIOEFFECT_ERROR_BAD_VALUE;
    case INVALID_OPERATION:
        return AUDIOEFFECT_ERROR_INVALID_OPERATION;
    case NO_MEMORY:
        return AUDIOEFFECT_ERROR_NO_MEMORY;
    case DEAD_OBJECT:
        return AUDIOEFFECT_ERROR_DEAD_OBJECT;
    default:
        return AUDIOEFFECT_ERROR;
    }
}


// ----------------------------------------------------------------------------
static void effectCallback(int event, void* user, void *info) {

    effect_param_t *p;
    int arg1 = 0;
    int arg2 = 0;
    jobject obj = NULL;
    jbyteArray array = NULL;
    jbyte *bytes;
    bool param;
    size_t size;

    effect_callback_cookie *callbackInfo = (effect_callback_cookie *)user;
    JNIEnv *env = AndroidRuntime::getJNIEnv();

    LOGV("effectCallback: callbackInfo %p, audioEffect_ref %p audioEffect_class %p",
            callbackInfo,
            callbackInfo->audioEffect_ref,
            callbackInfo->audioEffect_class);

    if (!user || !env) {
        LOGW("effectCallback error user %p, env %p", user, env);
        return;
    }

    switch (event) {
    case AudioEffect::EVENT_CONTROL_STATUS_CHANGED:
        if (info == 0) {
            LOGW("EVENT_CONTROL_STATUS_CHANGED info == NULL");
            goto effectCallback_Exit;
        }
        param = *(bool *)info;
        arg1 = (int)param;
        LOGV("EVENT_CONTROL_STATUS_CHANGED");
        break;
    case AudioEffect::EVENT_ENABLE_STATUS_CHANGED:
        if (info == 0) {
            LOGW("EVENT_ENABLE_STATUS_CHANGED info == NULL");
            goto effectCallback_Exit;
        }
        param = *(bool *)info;
        arg1 = (int)param;
        LOGV("EVENT_ENABLE_STATUS_CHANGED");
        break;
    case AudioEffect::EVENT_PARAMETER_CHANGED:
        if (info == 0) {
            LOGW("EVENT_PARAMETER_CHANGED info == NULL");
            goto effectCallback_Exit;
        }
        p = (effect_param_t *)info;
        if (p->psize == 0 || p->vsize == 0) {
            goto effectCallback_Exit;
        }
        // arg1 contains offset of parameter value from start of byte array
        arg1 = sizeof(effect_param_t) + ((p->psize - 1) / sizeof(int) + 1) * sizeof(int);
        size = arg1 + p->vsize;
        array = env->NewByteArray(size);
        if (array == NULL) {
            LOGE("effectCallback: Couldn't allocate byte array for parameter data");
            goto effectCallback_Exit;
        }
        bytes = env->GetByteArrayElements(array, NULL);
        memcpy(bytes, p, size);
        env->ReleaseByteArrayElements(array, bytes, 0);
        obj = array;
        LOGV("EVENT_PARAMETER_CHANGED");
       break;
    case AudioEffect::EVENT_ERROR:
        LOGW("EVENT_ERROR");
        break;
    }

    env->CallStaticVoidMethod(
        callbackInfo->audioEffect_class,
        fields.midPostNativeEvent,
        callbackInfo->audioEffect_ref, event, arg1, arg2, obj);

effectCallback_Exit:
    if (array) {
        env->DeleteLocalRef(array);
    }

    if (env->ExceptionCheck()) {
        env->ExceptionDescribe();
        env->ExceptionClear();
    }
}

// ----------------------------------------------------------------------------
// This function gets some field IDs, which in turn causes class initialization.
// It is called from a static block in AudioEffect, which won't run until the
// first time an instance of this class is used.
static void
android_media_AudioEffect_native_init(JNIEnv *env)
{

    LOGV("android_media_AudioEffect_native_init");

    fields.clazzEffect = NULL;
    fields.clazzDesc = NULL;

    // Get the AudioEffect class
    jclass clazz = env->FindClass(kClassPathName);
    if (clazz == NULL) {
        LOGE("Can't find %s", kClassPathName);
        return;
    }

    fields.clazzEffect = (jclass)env->NewGlobalRef(clazz);

    // Get the postEvent method
    fields.midPostNativeEvent = env->GetStaticMethodID(
            fields.clazzEffect,
            "postEventFromNative", "(Ljava/lang/Object;IIILjava/lang/Object;)V");
    if (fields.midPostNativeEvent == NULL) {
        LOGE("Can't find AudioEffect.%s", "postEventFromNative");
        return;
    }

    // Get the variables fields
    //      nativeTrackInJavaObj
    fields.fidNativeAudioEffect = env->GetFieldID(
            fields.clazzEffect,
            "mNativeAudioEffect", "I");
    if (fields.fidNativeAudioEffect == NULL) {
        LOGE("Can't find AudioEffect.%s", "mNativeAudioEffect");
        return;
    }
    //      fidJniData;
    fields.fidJniData = env->GetFieldID(
            fields.clazzEffect,
            "mJniData", "I");
    if (fields.fidJniData == NULL) {
        LOGE("Can't find AudioEffect.%s", "mJniData");
        return;
    }

    clazz = env->FindClass("android/media/AudioEffect$Descriptor");
    if (clazz == NULL) {
        LOGE("Can't find android/media/AudioEffect$Descriptor class");
        return;
    }
    fields.clazzDesc = (jclass)env->NewGlobalRef(clazz);

    fields.midDescCstor
            = env->GetMethodID(
                    fields.clazzDesc,
                    "<init>",
                    "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
    if (fields.midDescCstor == NULL) {
        LOGE("Can't find android/media/AudioEffect$Descriptor class constructor");
        return;
    }
}


static jint
android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
        jstring type, jstring uuid, jint priority, jint sessionId, jintArray jId, jobjectArray javadesc)
{
    LOGV("android_media_AudioEffect_native_setup");
    AudioEffectJniStorage* lpJniStorage = NULL;
    int lStatus = AUDIOEFFECT_ERROR_NO_MEMORY;
    AudioEffect* lpAudioEffect = NULL;
    jint* nId = NULL;
    const char *typeStr = NULL;
    const char *uuidStr = NULL;
    effect_descriptor_t desc;
    jobject jdesc;
    char str[EFFECT_STRING_LEN_MAX];
    jstring jdescType;
    jstring jdescUuid;
    jstring jdescConnect;
    jstring jdescName;
    jstring jdescImplementor;

    if (type != NULL) {
        typeStr = env->GetStringUTFChars(type, NULL);
        if (typeStr == NULL) {  // Out of memory
            jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
            goto setup_failure;
        }
    }

    if (uuid != NULL) {
        uuidStr = env->GetStringUTFChars(uuid, NULL);
        if (uuidStr == NULL) {  // Out of memory
            jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
            goto setup_failure;
        }
    }

    if (typeStr == NULL && uuidStr == NULL) {
        lStatus = AUDIOEFFECT_ERROR_BAD_VALUE;
        goto setup_failure;
    }

    lpJniStorage = new AudioEffectJniStorage();
    if (lpJniStorage == NULL) {
        LOGE("setup: Error creating JNI Storage");
        goto setup_failure;
    }

    lpJniStorage->mCallbackData.audioEffect_class = (jclass)env->NewGlobalRef(fields.clazzEffect);
    // we use a weak reference so the AudioEffect object can be garbage collected.
    lpJniStorage->mCallbackData.audioEffect_ref = env->NewGlobalRef(weak_this);

    LOGV("setup: lpJniStorage: %p audioEffect_ref %p audioEffect_class %p, &mCallbackData %p",
            lpJniStorage,
            lpJniStorage->mCallbackData.audioEffect_ref,
            lpJniStorage->mCallbackData.audioEffect_class,
            &lpJniStorage->mCallbackData);

    if (jId) {
        nId = (jint *) env->GetPrimitiveArrayCritical(jId, NULL);
        if (nId == NULL) {
            LOGE("setup: Error retrieving id pointer");
            lStatus = AUDIOEFFECT_ERROR_BAD_VALUE;
            goto setup_failure;
        }
    } else {
        LOGE("setup: NULL java array for id pointer");
        lStatus = AUDIOEFFECT_ERROR_BAD_VALUE;
        goto setup_failure;
    }

    // create the native AudioEffect object
    lpAudioEffect = new AudioEffect(typeStr,
                                    uuidStr,
                                    priority,
                                    effectCallback,
                                    &lpJniStorage->mCallbackData,
                                    sessionId,
                                    0);
    if (lpAudioEffect == NULL) {
        LOGE("Error creating AudioEffect");
        goto setup_failure;
    }

    lStatus = translateError(lpAudioEffect->initCheck());
    if (lStatus != AUDIOEFFECT_SUCCESS && lStatus != AUDIOEFFECT_ERROR_ALREADY_EXISTS) {
        LOGE("AudioEffect initCheck failed %d", lStatus);
        goto setup_failure;
    }

    nId[0] = lpAudioEffect->id();

    env->ReleasePrimitiveArrayCritical(jId, nId, 0);
    nId = NULL;

    if (typeStr) {
        env->ReleaseStringUTFChars(type, typeStr);
        typeStr = NULL;
    }

    if (uuidStr) {
        env->ReleaseStringUTFChars(uuid, uuidStr);
        uuidStr = NULL;
    }

    // get the effect descriptor
    desc = lpAudioEffect->descriptor();

    AudioEffect::guidToString(&desc.type, str, EFFECT_STRING_LEN_MAX);
    jdescType = env->NewStringUTF(str);

    AudioEffect::guidToString(&desc.uuid, str, EFFECT_STRING_LEN_MAX);
    jdescUuid = env->NewStringUTF(str);

    if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) {
        jdescConnect = env->NewStringUTF("Auxiliary");
    } else {
        jdescConnect = env->NewStringUTF("Insert");
    }

    jdescName = env->NewStringUTF(desc.name);
    jdescImplementor = env->NewStringUTF(desc.implementor);

    jdesc = env->NewObject(fields.clazzDesc,
                           fields.midDescCstor,
                           jdescType,
                           jdescUuid,
                           jdescConnect,
                           jdescName,
                           jdescImplementor);
    env->DeleteLocalRef(jdescType);
    env->DeleteLocalRef(jdescUuid);
    env->DeleteLocalRef(jdescConnect);
    env->DeleteLocalRef(jdescName);
    env->DeleteLocalRef(jdescImplementor);
    if (jdesc == NULL) {
        LOGE("env->NewObject(fields.clazzDesc, fields.midDescCstor)");
        goto setup_failure;
    }

    env->SetObjectArrayElement(javadesc, 0, jdesc);

    env->SetIntField(thiz, fields.fidNativeAudioEffect, (int)lpAudioEffect);

    env->SetIntField(thiz, fields.fidJniData, (int)lpJniStorage);

    return AUDIOEFFECT_SUCCESS;

    // failures:
setup_failure:

    if (nId != NULL) {
        env->ReleasePrimitiveArrayCritical(jId, nId, 0);
    }

    if (lpAudioEffect) {
        delete lpAudioEffect;
    }
    env->SetIntField(thiz, fields.fidNativeAudioEffect, 0);

    if (lpJniStorage) {
        delete lpJniStorage;
    }
    env->SetIntField(thiz, fields.fidJniData, 0);

    if (uuidStr != NULL) {
        env->ReleaseStringUTFChars(uuid, uuidStr);
    }

    if (typeStr != NULL) {
        env->ReleaseStringUTFChars(type, typeStr);
    }

    return lStatus;
}


// ----------------------------------------------------------------------------
static void android_media_AudioEffect_native_finalize(JNIEnv *env,  jobject thiz) {
    LOGV("android_media_AudioEffect_native_finalize jobject: %x\n", (int)thiz);

    // delete the AudioEffect object
    AudioEffect* lpAudioEffect = (AudioEffect *)env->GetIntField(
        thiz, fields.fidNativeAudioEffect);
    if (lpAudioEffect) {
        LOGV("deleting AudioEffect: %x\n", (int)lpAudioEffect);
        delete lpAudioEffect;
    }

    // delete the JNI data
    AudioEffectJniStorage* lpJniStorage = (AudioEffectJniStorage *)env->GetIntField(
        thiz, fields.fidJniData);
    if (lpJniStorage) {
        LOGV("deleting pJniStorage: %x\n", (int)lpJniStorage);
        delete lpJniStorage;
    }
}

// ----------------------------------------------------------------------------
static void android_media_AudioEffect_native_release(JNIEnv *env,  jobject thiz) {

    // do everything a call to finalize would
    android_media_AudioEffect_native_finalize(env, thiz);
    // + reset the native resources in the Java object so any attempt to access
    // them after a call to release fails.
    env->SetIntField(thiz, fields.fidNativeAudioEffect, 0);
    env->SetIntField(thiz, fields.fidJniData, 0);
}

static jint
android_media_AudioEffect_native_setEnabled(JNIEnv *env, jobject thiz, jboolean enabled)
{
    // retrieve the AudioEffect object
    AudioEffect* lpAudioEffect = (AudioEffect *)env->GetIntField(
        thiz, fields.fidNativeAudioEffect);

    if (lpAudioEffect == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve AudioEffect pointer for enable()");
        return AUDIOEFFECT_ERROR_NO_INIT;
    }

    return translateError(lpAudioEffect->setEnabled(enabled));
}

static jboolean
android_media_AudioEffect_native_getEnabled(JNIEnv *env, jobject thiz)
{
    // retrieve the AudioEffect object
    AudioEffect* lpAudioEffect = (AudioEffect *)env->GetIntField(
        thiz, fields.fidNativeAudioEffect);

    if (lpAudioEffect == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve AudioEffect pointer for getEnabled()");
        return false;
    }

    return (jboolean)lpAudioEffect->getEnabled();
}


static jboolean
android_media_AudioEffect_native_hasControl(JNIEnv *env, jobject thiz)
{
    // retrieve the AudioEffect object
    AudioEffect* lpAudioEffect = (AudioEffect *)env->GetIntField(
        thiz, fields.fidNativeAudioEffect);

    if (lpAudioEffect == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
            "Unable to retrieve AudioEffect pointer for hasControl()");
        return false;
    }

    if (lpAudioEffect->initCheck() == NO_ERROR) {
        return true;
    } else {
        return false;
    }
}

static jint android_media_AudioEffect_native_setParameter(JNIEnv *env,
        jobject thiz, int psize, jbyteArray pJavaParam, int vsize,
        jbyteArray pJavaValue) {
    // retrieve the AudioEffect object
    jbyte* lpValue = NULL;
    jbyte* lpParam = NULL;
    jint lStatus = AUDIOEFFECT_ERROR_BAD_VALUE;
    effect_param_t *p;
    int voffset;

    AudioEffect* lpAudioEffect = (AudioEffect *) env->GetIntField(thiz,
            fields.fidNativeAudioEffect);

    if (lpAudioEffect == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
                "Unable to retrieve AudioEffect pointer for setParameter()");
        return AUDIOEFFECT_ERROR_NO_INIT;
    }

    if (psize == 0 || vsize == 0 || pJavaParam == NULL || pJavaValue == NULL) {
        return AUDIOEFFECT_ERROR_BAD_VALUE;
    }

    // get the pointer for the param from the java array
    lpParam = (jbyte *) env->GetPrimitiveArrayCritical(pJavaParam, NULL);
    if (lpParam == NULL) {
        LOGE("setParameter: Error retrieving param pointer");
        goto setParameter_Exit;
    }

    // get the pointer for the value from the java array
    lpValue = (jbyte *) env->GetPrimitiveArrayCritical(pJavaValue, NULL);
    if (lpValue == NULL) {
        LOGE("setParameter: Error retrieving value pointer");
        goto setParameter_Exit;
    }

    voffset = ((psize - 1) / sizeof(int) + 1) * sizeof(int);
    p = (effect_param_t *) malloc(sizeof(effect_param_t) + voffset + vsize);
    memcpy(p->data, lpParam, psize);
    p->psize = psize;
    memcpy(p->data + voffset, lpValue, vsize);
    p->vsize = vsize;

    lStatus = lpAudioEffect->setParameter(p);
    if (lStatus == NO_ERROR) {
        lStatus = p->status;
    }

    free(p);

setParameter_Exit:

    if (lpParam != NULL) {
        env->ReleasePrimitiveArrayCritical(pJavaParam, lpParam, 0);
    }
    if (lpValue != NULL) {
        env->ReleasePrimitiveArrayCritical(pJavaValue, lpValue, 0);
    }
    return translateError(lStatus);
}

static jint
android_media_AudioEffect_native_getParameter(JNIEnv *env,
        jobject thiz, int psize, jbyteArray pJavaParam,
        jintArray pJavaValueSize, jbyteArray pJavaValue) {
    // retrieve the AudioEffect object
    jbyte* lpParam = NULL;
    jbyte* lpValue = NULL;
    jbyte* lpValueSize = NULL;
    jint lStatus = AUDIOEFFECT_ERROR_BAD_VALUE;
    effect_param_t *p;
    int voffset;

    AudioEffect* lpAudioEffect = (AudioEffect *) env->GetIntField(thiz,
            fields.fidNativeAudioEffect);

    if (lpAudioEffect == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
                "Unable to retrieve AudioEffect pointer for getParameter()");
        return AUDIOEFFECT_ERROR_NO_INIT;
    }

    if (psize == 0 || pJavaValueSize == NULL || pJavaParam == NULL || pJavaValue == NULL) {
        return AUDIOEFFECT_ERROR_BAD_VALUE;
    }

    // get the pointer for the param from the java array
    lpParam = (jbyte *) env->GetPrimitiveArrayCritical(pJavaParam, NULL);
    if (lpParam == NULL) {
        LOGE("getParameter: Error retrieving param pointer");
        goto getParameter_Exit;
    }

    // get the pointer for the value from the java array
    lpValue = (jbyte *) env->GetPrimitiveArrayCritical(pJavaValue, NULL);
    if (lpValue == NULL) {
        LOGE("getParameter: Error retrieving value pointer");
        goto getParameter_Exit;
    }

    // get the pointer for the value size from the java array
    lpValueSize = (jbyte *) env->GetPrimitiveArrayCritical(pJavaValueSize, NULL);
    if (lpValueSize == NULL) {
        LOGE("getParameter: Error retrieving value size pointer");
        goto getParameter_Exit;
    }

    voffset = ((psize - 1) / sizeof(int) + 1) * sizeof(int);
    p = (effect_param_t *) malloc(sizeof(effect_param_t) + voffset
            + lpValueSize[0]);
    memcpy(p->data, lpParam, psize);
    p->psize = psize;
    p->vsize = lpValueSize[0];

    lStatus = lpAudioEffect->getParameter(p);
    if (lStatus == NO_ERROR) {
        lStatus = p->status;
        if (lStatus == NO_ERROR) {
            memcpy(lpValue, p->data + voffset, p->vsize);
            lpValueSize[0] = p->vsize;
        }
    }

    free(p);

getParameter_Exit:

    if (lpParam != NULL) {
        env->ReleasePrimitiveArrayCritical(pJavaParam, lpParam, 0);
    }
    if (lpValue != NULL) {
        env->ReleasePrimitiveArrayCritical(pJavaValue, lpValue, 0);
    }
    if (lpValueSize != NULL) {
        env->ReleasePrimitiveArrayCritical(pJavaValueSize, lpValueSize, 0);
    }

    return translateError(lStatus);
}

static jint android_media_AudioEffect_native_command(JNIEnv *env, jobject thiz,
        jint cmdCode, jint cmdSize, jbyteArray jCmdData, jintArray jReplySize,
        jbyteArray jReplyData) {
    jbyte* pCmdData = NULL;
    jbyte* pReplyData = NULL;
    jint* pReplySize = NULL;
    jint lStatus = AUDIOEFFECT_ERROR_BAD_VALUE;

    // retrieve the AudioEffect object
    AudioEffect* lpAudioEffect = (AudioEffect *) env->GetIntField(thiz,
            fields.fidNativeAudioEffect);

    if (lpAudioEffect == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
                "Unable to retrieve AudioEffect pointer for setParameter()");
        return AUDIOEFFECT_ERROR_NO_INIT;
    }

    if ((cmdSize != 0 && jCmdData == NULL) || (jReplySize != NULL && jReplyData == NULL)) {
        return AUDIOEFFECT_ERROR_BAD_VALUE;
    }

    // get the pointer for the command from the java array
    if (cmdSize != 0) {
        pCmdData = (jbyte *) env->GetPrimitiveArrayCritical(jCmdData, NULL);
        if (pCmdData == NULL) {
            LOGE("setParameter: Error retrieving command pointer");
            goto command_Exit;
        }
    }

    // get the pointer for the reply size from the java array
    if (jReplySize != NULL) {
        pReplySize = (jint *) env->GetPrimitiveArrayCritical(jReplySize, NULL);
        if (pReplySize == NULL) {
            LOGE("setParameter: Error retrieving reply pointer");
            goto command_Exit;
        }
    }

    // get the pointer for the reply from the java array
    if (pReplySize != NULL && pReplySize[0] != 0 && jReplyData != NULL) {
        pReplyData = (jbyte *) env->GetPrimitiveArrayCritical(jReplyData, NULL);
        if (pReplyData == NULL) {
            LOGE("setParameter: Error retrieving reply pointer");
            goto command_Exit;
        }
    }

    lStatus = translateError(lpAudioEffect->command((uint32_t)cmdCode,
                                                    (uint32_t)cmdSize,
                                                    pCmdData,
                                                    (uint32_t *)pReplySize,
                                                    pReplyData));

command_Exit:

    if (pCmdData != NULL) {
        env->ReleasePrimitiveArrayCritical(jCmdData, pCmdData, 0);
    }
    if (pReplyData != NULL) {
        env->ReleasePrimitiveArrayCritical(jReplyData, pReplyData, 0);
    }
    if (pReplySize != NULL) {
        env->ReleasePrimitiveArrayCritical(jReplySize, pReplySize, 0);
    }

    return lStatus;
}

static jobjectArray
android_media_AudioEffect_native_queryEffects(JNIEnv *env, jclass clazz)
{
    effect_descriptor_t desc;
    char str[EFFECT_STRING_LEN_MAX];
    uint32_t numEffects;
    uint32_t i = 0;
    jstring jdescType;
    jstring jdescUuid;
    jstring jdescConnect;
    jstring jdescName;
    jstring jdescImplementor;
    jobject jdesc;

    AudioEffect::queryNumberEffects(&numEffects);
    jobjectArray ret = env->NewObjectArray(numEffects, fields.clazzDesc, NULL);
    if (ret == NULL) {
        return ret;
    }

    LOGV("queryEffects() numEffects: %d", numEffects);

    for (i = 0; i < numEffects; i++) {
        if (AudioEffect::queryEffect(i, &desc) != NO_ERROR) {
            goto queryEffects_failure;
        }

        AudioEffect::guidToString(&desc.type, str, EFFECT_STRING_LEN_MAX);
        jdescType = env->NewStringUTF(str);

        AudioEffect::guidToString(&desc.uuid, str, EFFECT_STRING_LEN_MAX);
        jdescUuid = env->NewStringUTF(str);

        if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) {
            jdescConnect = env->NewStringUTF("Auxiliary");
        } else {
            jdescConnect = env->NewStringUTF("Insert");
        }

        jdescName = env->NewStringUTF(desc.name);
        jdescImplementor = env->NewStringUTF(desc.implementor);

        jdesc = env->NewObject(fields.clazzDesc,
                               fields.midDescCstor,
                               jdescType,
                               jdescUuid,
                               jdescConnect,
                               jdescName,
                               jdescImplementor);
        env->DeleteLocalRef(jdescType);
        env->DeleteLocalRef(jdescUuid);
        env->DeleteLocalRef(jdescConnect);
        env->DeleteLocalRef(jdescName);
        env->DeleteLocalRef(jdescImplementor);
        if (jdesc == NULL) {
            LOGE("env->NewObject(fields.clazzDesc, fields.midDescCstor)");
            goto queryEffects_failure;
        }

        env->SetObjectArrayElement(ret, i, jdesc);
   }

    return ret;

queryEffects_failure:

    if (ret != NULL) {
        env->DeleteLocalRef(ret);
    }
    return NULL;

}

// ----------------------------------------------------------------------------

// Dalvik VM type signatures
static JNINativeMethod gMethods[] = {
    {"native_init",          "()V",      (void *)android_media_AudioEffect_native_init},
    {"native_setup",         "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;II[I[Ljava/lang/Object;)I",
                                         (void *)android_media_AudioEffect_native_setup},
    {"native_finalize",      "()V",      (void *)android_media_AudioEffect_native_finalize},
    {"native_release",       "()V",      (void *)android_media_AudioEffect_native_release},
    {"native_setEnabled",    "(Z)I",      (void *)android_media_AudioEffect_native_setEnabled},
    {"native_getEnabled",    "()Z",      (void *)android_media_AudioEffect_native_getEnabled},
    {"native_hasControl",    "()Z",      (void *)android_media_AudioEffect_native_hasControl},
    {"native_setParameter",  "(I[BI[B)I",  (void *)android_media_AudioEffect_native_setParameter},
    {"native_getParameter",  "(I[B[I[B)I",  (void *)android_media_AudioEffect_native_getParameter},
    {"native_command",       "(II[B[I[B)I", (void *)android_media_AudioEffect_native_command},
    {"native_query_effects", "()[Ljava/lang/Object;", (void *)android_media_AudioEffect_native_queryEffects},
};


// ----------------------------------------------------------------------------

extern int register_android_media_visualizer(JNIEnv *env);

int register_android_media_AudioEffect(JNIEnv *env)
{
    return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
}

jint JNI_OnLoad(JavaVM* vm, void* reserved)
{

    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("ERROR: GetEnv failed\n");
        goto bail;
    }
    assert(env != NULL);

    if (register_android_media_AudioEffect(env) < 0) {
        LOGE("ERROR: AudioEffect native registration failed\n");
        goto bail;
    }

    if (register_android_media_visualizer(env) < 0) {
        LOGE("ERROR: Visualizer native registration failed\n");
        goto bail;
    }

    /* success -- return valid version number */
    result = JNI_VERSION_1_4;

bail:
    return result;
}

