/*
 * Copyright (C) 2012 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 "BluetoothServiceJni"
#include "com_android_bluetooth.h"
#include "hardware/bt_sock.h"
#include "utils/Log.h"
#include "utils/misc.h"
#include "cutils/properties.h"
#include "android_runtime/AndroidRuntime.h"
#include "android_runtime/Log.h"

#include <string.h>
#include <pthread.h>

#include <sys/stat.h>
#include <fcntl.h>

namespace android {

#define OOB_TK_SIZE 16

#define ADDITIONAL_NREFS 50
static jmethodID method_stateChangeCallback;
static jmethodID method_adapterPropertyChangedCallback;
static jmethodID method_devicePropertyChangedCallback;
static jmethodID method_deviceFoundCallback;
static jmethodID method_pinRequestCallback;
static jmethodID method_sspRequestCallback;
static jmethodID method_bondStateChangeCallback;
static jmethodID method_aclStateChangeCallback;
static jmethodID method_discoveryStateChangeCallback;
static jmethodID method_setWakeAlarm;
static jmethodID method_acquireWakeLock;
static jmethodID method_releaseWakeLock;
static jmethodID method_energyInfo;

static struct {
    jclass clazz;
    jmethodID constructor;
} android_bluetooth_UidTraffic;

static const bt_interface_t *sBluetoothInterface = NULL;
static const btsock_interface_t *sBluetoothSocketInterface = NULL;
static JNIEnv *callbackEnv = NULL;

static jobject sJniAdapterServiceObj;
static jobject sJniCallbacksObj;
static jfieldID sJniCallbacksField;


const bt_interface_t* getBluetoothInterface() {
    return sBluetoothInterface;
}

JNIEnv* getCallbackEnv() {
    return callbackEnv;
}

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

static bool checkCallbackThread() {
    JNIEnv* env = AndroidRuntime::getJNIEnv();
    if (callbackEnv != env || callbackEnv == NULL) {
        ALOGE("Callback env check fail: env: %p, callback: %p", env, callbackEnv);
        return false;
    }
    return true;
}

static void adapter_state_change_callback(bt_state_t status) {
    if (!checkCallbackThread()) {
       ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
       return;
    }
    ALOGV("%s: Status is: %d", __FUNCTION__, status);

    callbackEnv->CallVoidMethod(sJniCallbacksObj, method_stateChangeCallback, (jint)status);

    checkAndClearExceptionFromCallback(callbackEnv, __FUNCTION__);
}

static int get_properties(int num_properties, bt_property_t *properties, jintArray *types,
                        jobjectArray *props) {
    jbyteArray propVal;
    for (int i = 0; i < num_properties; i++) {
        propVal = callbackEnv->NewByteArray(properties[i].len);
        if (propVal == NULL) goto Fail;

        callbackEnv->SetByteArrayRegion(propVal, 0, properties[i].len,
                                             (jbyte*)properties[i].val);
        callbackEnv->SetObjectArrayElement(*props, i, propVal);
        // Delete reference to propVal
        callbackEnv->DeleteLocalRef(propVal);
        callbackEnv->SetIntArrayRegion(*types, i, 1, (jint *)&properties[i].type);
    }
    return 0;
Fail:
    if (propVal) callbackEnv->DeleteLocalRef(propVal);
    ALOGE("Error while allocation of array in %s", __FUNCTION__);
    return -1;
}

static void adapter_properties_callback(bt_status_t status, int num_properties,
                                        bt_property_t *properties) {
    jobjectArray props;
    jintArray types;
    jbyteArray val;
    jclass mclass;

    if (!checkCallbackThread()) {
       ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
       return;
    }

    ALOGV("%s: Status is: %d, Properties: %d", __FUNCTION__, status, num_properties);

    if (status != BT_STATUS_SUCCESS) {
        ALOGE("%s: Status %d is incorrect", __FUNCTION__, status);
        return;
    }

    val = (jbyteArray) callbackEnv->NewByteArray(num_properties);
    if (val == NULL) {
        ALOGE("%s: Error allocating byteArray", __FUNCTION__);
        return;
    }

    mclass = callbackEnv->GetObjectClass(val);

    /* (BT) Initialize the jobjectArray and jintArray here itself and send the
     initialized array pointers alone to get_properties */

    props = callbackEnv->NewObjectArray(num_properties, mclass,
                                             NULL);
    if (props == NULL) {
        ALOGE("%s: Error allocating object Array for properties", __FUNCTION__);
        return;
    }

    types = (jintArray)callbackEnv->NewIntArray(num_properties);

    if (types == NULL) {
        ALOGE("%s: Error allocating int Array for values", __FUNCTION__);
        return;
    }
    // Delete the reference to val and mclass
    callbackEnv->DeleteLocalRef(mclass);
    callbackEnv->DeleteLocalRef(val);

    if (get_properties(num_properties, properties, &types, &props) < 0) {
        if (props) callbackEnv->DeleteLocalRef(props);
        if (types) callbackEnv->DeleteLocalRef(types);
        return;
    }

    callbackEnv->CallVoidMethod(sJniCallbacksObj, method_adapterPropertyChangedCallback, types,
                                props);
    checkAndClearExceptionFromCallback(callbackEnv, __FUNCTION__);
    callbackEnv->DeleteLocalRef(props);
    callbackEnv->DeleteLocalRef(types);
    return;

}

static void remote_device_properties_callback(bt_status_t status, bt_bdaddr_t *bd_addr,
                                              int num_properties, bt_property_t *properties) {
    if (!checkCallbackThread()) {
       ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
       return;
    }

    ALOGV("%s: Status is: %d, Properties: %d", __FUNCTION__, status, num_properties);

    if (status != BT_STATUS_SUCCESS) {
        ALOGE("%s: Status %d is incorrect", __FUNCTION__, status);
        return;
    }

    callbackEnv->PushLocalFrame(ADDITIONAL_NREFS);

    jobjectArray props;
    jbyteArray addr;
    jintArray types;
    jbyteArray val;
    jclass mclass;

    val = (jbyteArray) callbackEnv->NewByteArray(num_properties);
    if (val == NULL) {
        ALOGE("%s: Error allocating byteArray", __FUNCTION__);
        return;
    }

    mclass = callbackEnv->GetObjectClass(val);

    /* Initialize the jobjectArray and jintArray here itself and send the
     initialized array pointers alone to get_properties */

    props = callbackEnv->NewObjectArray(num_properties, mclass,
                                             NULL);
    if (props == NULL) {
        ALOGE("%s: Error allocating object Array for properties", __FUNCTION__);
        return;
    }

    types = (jintArray)callbackEnv->NewIntArray(num_properties);

    if (types == NULL) {
        ALOGE("%s: Error allocating int Array for values", __FUNCTION__);
        return;
    }
    // Delete the reference to val and mclass
    callbackEnv->DeleteLocalRef(mclass);
    callbackEnv->DeleteLocalRef(val);

    addr = callbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
    if (addr == NULL) goto Fail;
    if (addr) callbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*)bd_addr);

    if (get_properties(num_properties, properties, &types, &props) < 0) {
        if (props) callbackEnv->DeleteLocalRef(props);
        if (types) callbackEnv->DeleteLocalRef(types);
        callbackEnv->PopLocalFrame(NULL);
        return;
    }

    callbackEnv->CallVoidMethod(sJniCallbacksObj, method_devicePropertyChangedCallback, addr,
                                types, props);
    checkAndClearExceptionFromCallback(callbackEnv, __FUNCTION__);
    callbackEnv->DeleteLocalRef(props);
    callbackEnv->DeleteLocalRef(types);
    callbackEnv->DeleteLocalRef(addr);
    callbackEnv->PopLocalFrame(NULL);
    return;

Fail:
    ALOGE("Error while allocation byte array in %s", __FUNCTION__);
}


static void device_found_callback(int num_properties, bt_property_t *properties) {
    jbyteArray addr = NULL;
    int addr_index;

    for (int i = 0; i < num_properties; i++) {
        if (properties[i].type == BT_PROPERTY_BDADDR) {
            addr = callbackEnv->NewByteArray(properties[i].len);
            if (addr) {
                callbackEnv->SetByteArrayRegion(addr, 0, properties[i].len,
                                                (jbyte*)properties[i].val);
                addr_index = i;
            } else {
                ALOGE("Address is NULL (unable to allocate) in %s", __FUNCTION__);
                return;
            }
        }
    }
    if (addr == NULL) {
        ALOGE("Address is NULL in %s", __FUNCTION__);
        return;
    }

    ALOGV("%s: Properties: %d, Address: %s", __FUNCTION__, num_properties,
        (const char *)properties[addr_index].val);

    remote_device_properties_callback(BT_STATUS_SUCCESS, (bt_bdaddr_t *)properties[addr_index].val,
                                      num_properties, properties);

    callbackEnv->CallVoidMethod(sJniCallbacksObj, method_deviceFoundCallback, addr);
    checkAndClearExceptionFromCallback(callbackEnv, __FUNCTION__);
    callbackEnv->DeleteLocalRef(addr);
}

static void bond_state_changed_callback(bt_status_t status, bt_bdaddr_t *bd_addr,
                                        bt_bond_state_t state) {
    jbyteArray addr;
    if (!checkCallbackThread()) {
       ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
       return;
    }
    if (!bd_addr) {
        ALOGE("Address is null in %s", __FUNCTION__);
        return;
    }
    addr = callbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
    if (addr == NULL) {
       ALOGE("Address allocation failed in %s", __FUNCTION__);
       return;
    }
    callbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *)bd_addr);

    callbackEnv->CallVoidMethod(sJniCallbacksObj, method_bondStateChangeCallback, (jint) status,
                                addr, (jint)state);
    checkAndClearExceptionFromCallback(callbackEnv, __FUNCTION__);
    callbackEnv->DeleteLocalRef(addr);
}

static void acl_state_changed_callback(bt_status_t status, bt_bdaddr_t *bd_addr,
                                       bt_acl_state_t state)
{
    jbyteArray addr;
    if (!checkCallbackThread()) {
       ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
       return;
    }
    if (!bd_addr) {
        ALOGE("Address is null in %s", __FUNCTION__);
        return;
    }
    addr = callbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
    if (addr == NULL) {
       ALOGE("Address allocation failed in %s", __FUNCTION__);
       return;
    }
    callbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *)bd_addr);

    callbackEnv->CallVoidMethod(sJniCallbacksObj, method_aclStateChangeCallback, (jint) status,
                                addr, (jint)state);
    checkAndClearExceptionFromCallback(callbackEnv, __FUNCTION__);
    callbackEnv->DeleteLocalRef(addr);
}

static void discovery_state_changed_callback(bt_discovery_state_t state) {
    if (!checkCallbackThread()) {
       ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
       return;
    }

    ALOGV("%s: DiscoveryState:%d ", __FUNCTION__, state);

    callbackEnv->CallVoidMethod(sJniCallbacksObj, method_discoveryStateChangeCallback,
                                (jint)state);

    checkAndClearExceptionFromCallback(callbackEnv, __FUNCTION__);
}

static void pin_request_callback(bt_bdaddr_t *bd_addr, bt_bdname_t *bdname, uint32_t cod,
        bool min_16_digits) {
    jbyteArray addr = NULL;
    jbyteArray devname = NULL;
    if (!checkCallbackThread()) {
       ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
       return;
    }
    if (!bd_addr) {
        ALOGE("Address is null in %s", __FUNCTION__);
        return;
    }

    addr = callbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
    if (addr == NULL) goto Fail;
    callbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte*)bd_addr);

    devname = callbackEnv->NewByteArray(sizeof(bt_bdname_t));
    if (devname == NULL) goto Fail;

    callbackEnv->SetByteArrayRegion(devname, 0, sizeof(bt_bdname_t), (jbyte*)bdname);

    callbackEnv->CallVoidMethod(sJniCallbacksObj, method_pinRequestCallback, addr, devname, cod,
            min_16_digits);

    checkAndClearExceptionFromCallback(callbackEnv, __FUNCTION__);
    callbackEnv->DeleteLocalRef(addr);
    callbackEnv->DeleteLocalRef(devname);
    return;

Fail:
    if (addr) callbackEnv->DeleteLocalRef(addr);
    if (devname) callbackEnv->DeleteLocalRef(devname);
    ALOGE("Error while allocating in: %s", __FUNCTION__);
}

static void ssp_request_callback(bt_bdaddr_t *bd_addr, bt_bdname_t *bdname, uint32_t cod,
                                 bt_ssp_variant_t pairing_variant, uint32_t pass_key) {
    jbyteArray addr = NULL;
    jbyteArray devname = NULL;
    if (!checkCallbackThread()) {
       ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
       return;
    }
    if (!bd_addr) {
        ALOGE("Address is null in %s", __FUNCTION__);
        return;
    }

    addr = callbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
    if (addr == NULL) goto Fail;
    callbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *)bd_addr);

    devname = callbackEnv->NewByteArray(sizeof(bt_bdname_t));
    if (devname == NULL) goto Fail;
    callbackEnv->SetByteArrayRegion(devname, 0, sizeof(bt_bdname_t), (jbyte*)bdname);

    callbackEnv->CallVoidMethod(sJniCallbacksObj, method_sspRequestCallback, addr, devname, cod,
                                (jint) pairing_variant, pass_key);

    checkAndClearExceptionFromCallback(callbackEnv, __FUNCTION__);
    callbackEnv->DeleteLocalRef(addr);
    callbackEnv->DeleteLocalRef(devname);
    return;

Fail:
    if (addr) callbackEnv->DeleteLocalRef(addr);
    if (devname) callbackEnv->DeleteLocalRef(devname);

    ALOGE("Error while allocating in: %s", __FUNCTION__);
}

static void callback_thread_event(bt_cb_thread_evt event) {
    JavaVM* vm = AndroidRuntime::getJavaVM();
    if (event  == ASSOCIATE_JVM) {
        JavaVMAttachArgs args;
        char name[] = "BT Service Callback Thread";
        args.version = JNI_VERSION_1_6;
        args.name = name;
        args.group = NULL;
        vm->AttachCurrentThread(&callbackEnv, &args);
        ALOGV("Callback thread attached: %p", callbackEnv);
    } else if (event == DISASSOCIATE_JVM) {
        if (!checkCallbackThread()) {
            ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
            return;
        }
        vm->DetachCurrentThread();
    }
}

static void dut_mode_recv_callback (uint16_t opcode, uint8_t *buf, uint8_t len) {

}
static void le_test_mode_recv_callback (bt_status_t status, uint16_t packet_count) {

    ALOGV("%s: status:%d packet_count:%d ", __FUNCTION__, status, packet_count);
}

static void energy_info_recv_callback(bt_activity_energy_info *p_energy_info,
                                      bt_uid_traffic_t* uid_data)
{
    if (!checkCallbackThread()) {
       ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);
       return;
    }

    jsize len = 0;
    for (bt_uid_traffic_t* data = uid_data; data->app_uid != -1; data++) {
        len++;
    }

    jobjectArray array = callbackEnv->NewObjectArray(len, android_bluetooth_UidTraffic.clazz, NULL);
    jsize i = 0;
    for (bt_uid_traffic_t* data = uid_data; data->app_uid != -1; data++) {
        jobject uidObj = callbackEnv->NewObject(android_bluetooth_UidTraffic.clazz,
                                                android_bluetooth_UidTraffic.constructor,
                                                (jint) data->app_uid, (jlong) data->rx_bytes,
                                                (jlong) data->tx_bytes);
        callbackEnv->SetObjectArrayElement(array, i++, uidObj);
        callbackEnv->DeleteLocalRef(uidObj);
    }

    callbackEnv->CallVoidMethod(sJniAdapterServiceObj, method_energyInfo, p_energy_info->status,
        p_energy_info->ctrl_state, p_energy_info->tx_time, p_energy_info->rx_time,
        p_energy_info->idle_time, p_energy_info->energy_used, array);

    checkAndClearExceptionFromCallback(callbackEnv, __FUNCTION__);
    callbackEnv->DeleteLocalRef(array);
}

static bt_callbacks_t sBluetoothCallbacks = {
    sizeof(sBluetoothCallbacks),
    adapter_state_change_callback,
    adapter_properties_callback,
    remote_device_properties_callback,
    device_found_callback,
    discovery_state_changed_callback,
    pin_request_callback,
    ssp_request_callback,
    bond_state_changed_callback,
    acl_state_changed_callback,
    callback_thread_event,
    dut_mode_recv_callback,
    le_test_mode_recv_callback,
    energy_info_recv_callback
};

// The callback to call when the wake alarm fires.
static alarm_cb sAlarmCallback;

// The data to pass to the wake alarm callback.
static void *sAlarmCallbackData;

static JavaVMAttachArgs sAttachArgs = {
  .version = JNI_VERSION_1_6,
  .name = "bluetooth wake",
  .group = NULL
};

static bool set_wake_alarm_callout(uint64_t delay_millis, bool should_wake,
        alarm_cb cb, void *data) {
    JNIEnv *env;
    JavaVM *vm = AndroidRuntime::getJavaVM();
    jint status = vm->GetEnv((void **)&env, JNI_VERSION_1_6);

    if (status != JNI_OK && status != JNI_EDETACHED) {
        ALOGE("%s unable to get environment for JNI call", __func__);
        return false;
    }

    if (status == JNI_EDETACHED && vm->AttachCurrentThread(&env, &sAttachArgs) != 0) {
        ALOGE("%s unable to attach thread to VM", __func__);
        return false;
    }

    sAlarmCallback = cb;
    sAlarmCallbackData = data;

    jboolean jshould_wake = should_wake ? JNI_TRUE : JNI_FALSE;
    jboolean ret = env->CallBooleanMethod(sJniAdapterServiceObj, method_setWakeAlarm,
            (jlong)delay_millis, jshould_wake);
    if (!ret) {
        sAlarmCallback = NULL;
        sAlarmCallbackData = NULL;
    }

    if (status == JNI_EDETACHED) {
        vm->DetachCurrentThread();
    }

    return !!ret;
}

static int acquire_wake_lock_callout(const char *lock_name) {
    JNIEnv *env;
    JavaVM *vm = AndroidRuntime::getJavaVM();
    jint status = vm->GetEnv((void **)&env, JNI_VERSION_1_6);
    if (status != JNI_OK && status != JNI_EDETACHED) {
        ALOGE("%s unable to get environment for JNI call", __func__);
        return BT_STATUS_JNI_ENVIRONMENT_ERROR;
    }
    if (status == JNI_EDETACHED && vm->AttachCurrentThread(&env, &sAttachArgs) != 0) {
        ALOGE("%s unable to attach thread to VM", __func__);
        return BT_STATUS_JNI_THREAD_ATTACH_ERROR;
    }

    jint ret = BT_STATUS_SUCCESS;
    jstring lock_name_jni = env->NewStringUTF(lock_name);
    if (lock_name_jni) {
        bool acquired = env->CallBooleanMethod(sJniAdapterServiceObj,
                            method_acquireWakeLock, lock_name_jni);
        if (!acquired) ret = BT_STATUS_WAKELOCK_ERROR;
        env->DeleteLocalRef(lock_name_jni);
    } else {
        ALOGE("%s unable to allocate string: %s", __func__, lock_name);
        ret = BT_STATUS_NOMEM;
    }

    if (status == JNI_EDETACHED) {
        vm->DetachCurrentThread();
    }

    return ret;
}

static int release_wake_lock_callout(const char *lock_name) {
    JNIEnv *env;
    JavaVM *vm = AndroidRuntime::getJavaVM();
    jint status = vm->GetEnv((void **)&env, JNI_VERSION_1_6);
    if (status != JNI_OK && status != JNI_EDETACHED) {
        ALOGE("%s unable to get environment for JNI call", __func__);
        return BT_STATUS_JNI_ENVIRONMENT_ERROR;
    }
    if (status == JNI_EDETACHED && vm->AttachCurrentThread(&env, &sAttachArgs) != 0) {
        ALOGE("%s unable to attach thread to VM", __func__);
        return BT_STATUS_JNI_THREAD_ATTACH_ERROR;
    }

    jint ret = BT_STATUS_SUCCESS;
    jstring lock_name_jni = env->NewStringUTF(lock_name);
    if (lock_name_jni) {
        bool released = env->CallBooleanMethod(sJniAdapterServiceObj,
                            method_releaseWakeLock, lock_name_jni);
        if (!released) ret = BT_STATUS_WAKELOCK_ERROR;
        env->DeleteLocalRef(lock_name_jni);
    } else {
        ALOGE("%s unable to allocate string: %s", __func__, lock_name);
        ret = BT_STATUS_NOMEM;
    }

    if (status == JNI_EDETACHED) {
        vm->DetachCurrentThread();
    }

    return ret;
}

// Called by Java code when alarm is fired. A wake lock is held by the caller
// over the duration of this callback.
static void alarmFiredNative(JNIEnv *env, jobject obj) {
    if (sAlarmCallback) {
        sAlarmCallback(sAlarmCallbackData);
    } else {
        ALOGE("%s() - Alarm fired with callback not set!", __FUNCTION__);
    }
}

static bt_os_callouts_t sBluetoothOsCallouts = {
    sizeof(sBluetoothOsCallouts),
    set_wake_alarm_callout,
    acquire_wake_lock_callout,
    release_wake_lock_callout,
};



static void classInitNative(JNIEnv* env, jclass clazz) {
    int err;
    hw_module_t* module;


    jclass jniUidTrafficClass = env->FindClass("android/bluetooth/UidTraffic");
    android_bluetooth_UidTraffic.constructor = env->GetMethodID(jniUidTrafficClass,
                                                                "<init>", "(IJJ)V");

    jclass jniCallbackClass =
        env->FindClass("com/android/bluetooth/btservice/JniCallbacks");
    sJniCallbacksField = env->GetFieldID(clazz, "mJniCallbacks",
        "Lcom/android/bluetooth/btservice/JniCallbacks;");

    method_stateChangeCallback = env->GetMethodID(jniCallbackClass, "stateChangeCallback", "(I)V");

    method_adapterPropertyChangedCallback = env->GetMethodID(jniCallbackClass,
                                                             "adapterPropertyChangedCallback",
                                                             "([I[[B)V");
    method_discoveryStateChangeCallback = env->GetMethodID(jniCallbackClass,
                                                           "discoveryStateChangeCallback", "(I)V");

    method_devicePropertyChangedCallback = env->GetMethodID(jniCallbackClass,
                                                            "devicePropertyChangedCallback",
                                                            "([B[I[[B)V");
    method_deviceFoundCallback = env->GetMethodID(jniCallbackClass, "deviceFoundCallback", "([B)V");
    method_pinRequestCallback = env->GetMethodID(jniCallbackClass, "pinRequestCallback",
                                                 "([B[BIZ)V");
    method_sspRequestCallback = env->GetMethodID(jniCallbackClass, "sspRequestCallback",
                                                 "([B[BIII)V");

    method_bondStateChangeCallback = env->GetMethodID(jniCallbackClass,
                                                     "bondStateChangeCallback", "(I[BI)V");

    method_aclStateChangeCallback = env->GetMethodID(jniCallbackClass,
                                                    "aclStateChangeCallback", "(I[BI)V");

    method_setWakeAlarm = env->GetMethodID(clazz, "setWakeAlarm", "(JZ)Z");
    method_acquireWakeLock = env->GetMethodID(clazz, "acquireWakeLock", "(Ljava/lang/String;)Z");
    method_releaseWakeLock = env->GetMethodID(clazz, "releaseWakeLock", "(Ljava/lang/String;)Z");
    method_energyInfo = env->GetMethodID(clazz, "energyInfoCallback", "(IIJJJJ[Landroid/bluetooth/UidTraffic;)V");

    char value[PROPERTY_VALUE_MAX];
    property_get("bluetooth.mock_stack", value, "");

    const char *id = (strcmp(value, "1")? BT_STACK_MODULE_ID : BT_STACK_TEST_MODULE_ID);

    err = hw_get_module(id, (hw_module_t const**)&module);

    if (err == 0) {
        hw_device_t* abstraction;
        err = module->methods->open(module, id, &abstraction);
        if (err == 0) {
            bluetooth_module_t* btStack = (bluetooth_module_t *)abstraction;
            sBluetoothInterface = btStack->get_bluetooth_interface();
        } else {
           ALOGE("Error while opening Bluetooth library");
        }
    } else {
        ALOGE("No Bluetooth Library found");
    }
}

static bool initNative(JNIEnv* env, jobject obj) {
    ALOGV("%s:",__FUNCTION__);

    android_bluetooth_UidTraffic.clazz = (jclass) env->NewGlobalRef(
            env->FindClass("android/bluetooth/UidTraffic"));

    sJniAdapterServiceObj = env->NewGlobalRef(obj);
    sJniCallbacksObj = env->NewGlobalRef(env->GetObjectField(obj, sJniCallbacksField));

    if (sBluetoothInterface) {
        int ret = sBluetoothInterface->init(&sBluetoothCallbacks);
        if (ret != BT_STATUS_SUCCESS) {
            ALOGE("Error while setting the callbacks: %d\n", ret);
            sBluetoothInterface = NULL;
            return JNI_FALSE;
        }
        ret = sBluetoothInterface->set_os_callouts(&sBluetoothOsCallouts);
        if (ret != BT_STATUS_SUCCESS) {
            ALOGE("Error while setting Bluetooth callouts: %d\n", ret);
            sBluetoothInterface->cleanup();
            sBluetoothInterface = NULL;
            return JNI_FALSE;
        }

        if ( (sBluetoothSocketInterface = (btsock_interface_t *)
                  sBluetoothInterface->get_profile_interface(BT_PROFILE_SOCKETS_ID)) == NULL) {
                ALOGE("Error getting socket interface");
        }

        return JNI_TRUE;
    }
    return JNI_FALSE;
}

static bool cleanupNative(JNIEnv *env, jobject obj) {
    ALOGV("%s:",__FUNCTION__);

    jboolean result = JNI_FALSE;
    if (!sBluetoothInterface) return result;

    sBluetoothInterface->cleanup();
    ALOGI("%s: return from cleanup",__FUNCTION__);

    env->DeleteGlobalRef(sJniCallbacksObj);
    env->DeleteGlobalRef(sJniAdapterServiceObj);
    env->DeleteGlobalRef(android_bluetooth_UidTraffic.clazz);
    android_bluetooth_UidTraffic.clazz = NULL;
    return JNI_TRUE;
}

static jboolean enableNative(JNIEnv* env, jobject obj, jboolean isGuest) {
    ALOGV("%s:",__FUNCTION__);

    jboolean result = JNI_FALSE;
    if (!sBluetoothInterface) return result;
    int ret = sBluetoothInterface->enable(isGuest == JNI_TRUE ? 1 : 0);
    result = (ret == BT_STATUS_SUCCESS || ret == BT_STATUS_DONE) ? JNI_TRUE : JNI_FALSE;
    return result;
}

static jboolean disableNative(JNIEnv* env, jobject obj) {
    ALOGV("%s:",__FUNCTION__);

    jboolean result = JNI_FALSE;
    if (!sBluetoothInterface) return result;

    int ret = sBluetoothInterface->disable();
    /* Retrun JNI_FALSE only when BTIF explicitly reports
       BT_STATUS_FAIL. It is fine for the BT_STATUS_NOT_READY
       case which indicates that stack had not been enabled.
    */
    result = (ret == BT_STATUS_FAIL) ? JNI_FALSE : JNI_TRUE;
    return result;
}

static jboolean startDiscoveryNative(JNIEnv* env, jobject obj) {
    ALOGV("%s:",__FUNCTION__);

    jboolean result = JNI_FALSE;
    if (!sBluetoothInterface) return result;

    int ret = sBluetoothInterface->start_discovery();
    result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
    return result;
}

static jboolean cancelDiscoveryNative(JNIEnv* env, jobject obj) {
    ALOGV("%s:",__FUNCTION__);

    jboolean result = JNI_FALSE;
    if (!sBluetoothInterface) return result;

    int ret = sBluetoothInterface->cancel_discovery();
    result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
    return result;
}

static jboolean createBondNative(JNIEnv* env, jobject obj, jbyteArray address, jint transport) {
    ALOGV("%s:",__FUNCTION__);

    jbyte *addr;
    jboolean result = JNI_FALSE;

    if (!sBluetoothInterface) return result;

    addr = env->GetByteArrayElements(address, NULL);
    if (addr == NULL) {
        jniThrowIOException(env, EINVAL);
        return result;
    }

    int ret = sBluetoothInterface->create_bond((bt_bdaddr_t *)addr, transport);
    env->ReleaseByteArrayElements(address, addr, 0);
    result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;

    return result;
}

static jbyteArray callByteArrayGetter(JNIEnv* env, jobject object,
                                      const char* className,
                                      const char* methodName) {
    jclass myClass = env->FindClass(className);
    jmethodID myMethod = env->GetMethodID(myClass, methodName, "()[B");
    return (jbyteArray) env->CallObjectMethod(object, myMethod);
}

static jboolean createBondOutOfBandNative(JNIEnv* env, jobject obj, jbyteArray address,
                jint transport, jobject oobData) {
    jbyte *addr;
    jboolean result = JNI_FALSE;
    bt_out_of_band_data_t oob_data;

    memset(&oob_data, 0, sizeof(oob_data));

    if (!sBluetoothInterface) return result;

    addr = env->GetByteArrayElements(address, NULL);
    if (addr == NULL) {
        jniThrowIOException(env, EINVAL);
        return result;
    }

    jbyte* smTKBytes = NULL;
    jbyteArray smTK = callByteArrayGetter(env, oobData, "android/bluetooth/OobData", "getSecurityManagerTk");
    if (smTK != NULL) {
        smTKBytes = env->GetByteArrayElements(smTK, NULL);
        int len = env->GetArrayLength(smTK);
        if (len != OOB_TK_SIZE) {
            ALOGI("%s: wrong length of smTK, should be empty or %d bytes.", __FUNCTION__, OOB_TK_SIZE);
            jniThrowIOException(env, EINVAL);
            goto done;
        }
        memcpy(oob_data.sm_tk, smTKBytes, len);
    }

    if (sBluetoothInterface->create_bond_out_of_band((bt_bdaddr_t *)addr, transport, &oob_data)
        == BT_STATUS_SUCCESS)
        result = JNI_TRUE;

done:
    env->ReleaseByteArrayElements(address, addr, 0);

    if (smTK != NULL)
        env->ReleaseByteArrayElements(smTK, smTKBytes, 0);

    return result;
}

static jboolean removeBondNative(JNIEnv* env, jobject obj, jbyteArray address) {
    ALOGV("%s:",__FUNCTION__);

    jbyte *addr;
    jboolean result;
    if (!sBluetoothInterface) return JNI_FALSE;

    addr = env->GetByteArrayElements(address, NULL);
    if (addr == NULL) {
        jniThrowIOException(env, EINVAL);
        return JNI_FALSE;
    }

    int ret = sBluetoothInterface->remove_bond((bt_bdaddr_t *)addr);
    env->ReleaseByteArrayElements(address, addr, 0);
    result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;

    return result;
}

static jboolean cancelBondNative(JNIEnv* env, jobject obj, jbyteArray address) {
    ALOGV("%s:",__FUNCTION__);

    jbyte *addr;
    jboolean result;
    if (!sBluetoothInterface) return JNI_FALSE;

    addr = env->GetByteArrayElements(address, NULL);
    if (addr == NULL) {
        jniThrowIOException(env, EINVAL);
        return JNI_FALSE;
    }

    int ret = sBluetoothInterface->cancel_bond((bt_bdaddr_t *)addr);
    env->ReleaseByteArrayElements(address, addr, 0);
    result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;

    return result;
}

static int getConnectionStateNative(JNIEnv* env, jobject obj, jbyteArray address) {
    ALOGV("%s:",__FUNCTION__);
    if (!sBluetoothInterface) return JNI_FALSE;

    jbyte *addr = env->GetByteArrayElements(address, NULL);
    if (addr == NULL) {
        jniThrowIOException(env, EINVAL);
        return JNI_FALSE;
    }

    int ret = sBluetoothInterface->get_connection_state((bt_bdaddr_t *)addr);
    env->ReleaseByteArrayElements(address, addr, 0);

    return ret;
}

static jboolean pinReplyNative(JNIEnv *env, jobject obj, jbyteArray address, jboolean accept,
                               jint len, jbyteArray pinArray) {
    ALOGV("%s:",__FUNCTION__);

    jbyte *addr, *pinPtr = NULL;
    jboolean result = JNI_FALSE;
    if (!sBluetoothInterface) return result;

    addr = env->GetByteArrayElements(address, NULL);
    if (addr == NULL) {
        jniThrowIOException(env, EINVAL);
        return result;
    }

    if (accept) {
        pinPtr = env->GetByteArrayElements(pinArray, NULL);
        if (pinPtr == NULL) {
           jniThrowIOException(env, EINVAL);
           env->ReleaseByteArrayElements(address, addr, 0);
           return result;
        }
    }

    int ret = sBluetoothInterface->pin_reply((bt_bdaddr_t*)addr, accept, len,
                                              (bt_pin_code_t *) pinPtr);
    env->ReleaseByteArrayElements(address, addr, 0);
    env->ReleaseByteArrayElements(pinArray, pinPtr, 0);
    result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;

    return result;
}

static jboolean sspReplyNative(JNIEnv *env, jobject obj, jbyteArray address,
                               jint type, jboolean accept, jint passkey) {
    ALOGV("%s:",__FUNCTION__);

    jbyte *addr;
    jboolean result = JNI_FALSE;
    if (!sBluetoothInterface) return result;

    addr = env->GetByteArrayElements(address, NULL);
    if (addr == NULL) {
        jniThrowIOException(env, EINVAL);
        return result;
    }

    int ret = sBluetoothInterface->ssp_reply((bt_bdaddr_t *)addr,
         (bt_ssp_variant_t) type, accept, passkey);
    env->ReleaseByteArrayElements(address, addr, 0);
    result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;

    return result;
}

static jboolean setAdapterPropertyNative(JNIEnv *env, jobject obj, jint type, jbyteArray value) {
    ALOGV("%s:",__FUNCTION__);

    jbyte *val;
    jboolean result = JNI_FALSE;
    if (!sBluetoothInterface) return result;

    val = env->GetByteArrayElements(value, NULL);
    bt_property_t prop;
    prop.type = (bt_property_type_t) type;
    prop.len = env->GetArrayLength(value);
    prop.val = val;

    int ret = sBluetoothInterface->set_adapter_property(&prop);
    env->ReleaseByteArrayElements(value, val, 0);
    result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;

    return result;
}

static jboolean getAdapterPropertiesNative(JNIEnv *env, jobject obj) {
    ALOGV("%s:",__FUNCTION__);

    jboolean result = JNI_FALSE;
    if (!sBluetoothInterface) return result;

    int ret = sBluetoothInterface->get_adapter_properties();
    result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;

    return result;
}

static jboolean getAdapterPropertyNative(JNIEnv *env, jobject obj, jint type) {
    ALOGV("%s:",__FUNCTION__);

    jboolean result = JNI_FALSE;
    if (!sBluetoothInterface) return result;

    int ret = sBluetoothInterface->get_adapter_property((bt_property_type_t) type);
    result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;

    return result;
}

static jboolean getDevicePropertyNative(JNIEnv *env, jobject obj, jbyteArray address, jint type) {
    ALOGV("%s:",__FUNCTION__);

    jbyte *addr = NULL;
    jboolean result = JNI_FALSE;
    if (!sBluetoothInterface) return result;

    addr = env->GetByteArrayElements(address, NULL);
    if (addr == NULL) {
        jniThrowIOException(env, EINVAL);
        return result;
    }

    int ret = sBluetoothInterface->get_remote_device_property((bt_bdaddr_t *)addr,
                                                              (bt_property_type_t) type);
    env->ReleaseByteArrayElements(address, addr, 0);
    result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;

    return result;
}

static jboolean setDevicePropertyNative(JNIEnv *env, jobject obj, jbyteArray address,
                                        jint type, jbyteArray value) {
    ALOGV("%s:",__FUNCTION__);

    jbyte *val, *addr;
    jboolean result = JNI_FALSE;
    if (!sBluetoothInterface) return result;

    val = env->GetByteArrayElements(value, NULL);
    if (val == NULL) {
        jniThrowIOException(env, EINVAL);
        return result;
    }

    addr = env->GetByteArrayElements(address, NULL);
    if (addr == NULL) {
        env->ReleaseByteArrayElements(value, val, 0);
        jniThrowIOException(env, EINVAL);
        return result;
    }


    bt_property_t prop;
    prop.type = (bt_property_type_t) type;
    prop.len = env->GetArrayLength(value);
    prop.val = val;

    int ret = sBluetoothInterface->set_remote_device_property((bt_bdaddr_t *)addr, &prop);
    env->ReleaseByteArrayElements(value, val, 0);
    env->ReleaseByteArrayElements(address, addr, 0);

    result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;

    return result;
}

static jboolean getRemoteServicesNative(JNIEnv *env, jobject obj, jbyteArray address) {
    ALOGV("%s:",__FUNCTION__);

    jbyte *addr = NULL;
    jboolean result = JNI_FALSE;
    if (!sBluetoothInterface) return result;

    addr = env->GetByteArrayElements(address, NULL);
    if (addr == NULL) {
        jniThrowIOException(env, EINVAL);
        return result;
    }

    int ret = sBluetoothInterface->get_remote_services((bt_bdaddr_t *)addr);
    env->ReleaseByteArrayElements(address, addr, 0);
    result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
    return result;
}

static int connectSocketNative(JNIEnv *env, jobject object, jbyteArray address, jint type,
                                   jbyteArray uuidObj, jint channel, jint flag, jint callingUid) {
    jbyte *addr = NULL, *uuid = NULL;
    int socket_fd;
    bt_status_t status;

    if (!sBluetoothSocketInterface) return -1;

    addr = env->GetByteArrayElements(address, NULL);
    if (!addr) {
        ALOGE("failed to get Bluetooth device address");
        goto Fail;
    }

    if(uuidObj != NULL) {
        uuid = env->GetByteArrayElements(uuidObj, NULL);
        if (!uuid) {
            ALOGE("failed to get uuid");
            goto Fail;
        }
    }

    if ( (status = sBluetoothSocketInterface->connect((bt_bdaddr_t *) addr, (btsock_type_t) type,
                       (const uint8_t*) uuid, channel, &socket_fd, flag, callingUid))
            != BT_STATUS_SUCCESS) {
        ALOGE("Socket connection failed: %d", status);
        goto Fail;
    }


    if (socket_fd < 0) {
        ALOGE("Fail to create file descriptor on socket fd");
        goto Fail;
    }
    env->ReleaseByteArrayElements(address, addr, 0);
    env->ReleaseByteArrayElements(uuidObj, uuid, 0);
    return socket_fd;

Fail:
    if (addr) env->ReleaseByteArrayElements(address, addr, 0);
    if (uuid) env->ReleaseByteArrayElements(uuidObj, uuid, 0);

    return -1;
}

static int createSocketChannelNative(JNIEnv *env, jobject object, jint type,
                                     jstring name_str, jbyteArray uuidObj,
                                     jint channel, jint flag, jint callingUid) {
    const char *service_name = NULL;
    jbyte *uuid = NULL;
    int socket_fd;
    bt_status_t status;

    if (!sBluetoothSocketInterface) return -1;

    ALOGV("%s: SOCK FLAG = %x", __FUNCTION__, flag);

    if(name_str != NULL) {
        service_name = env->GetStringUTFChars(name_str, NULL);
    }

    if(uuidObj != NULL) {
        uuid = env->GetByteArrayElements(uuidObj, NULL);
        if (!uuid) {
            ALOGE("failed to get uuid");
            goto Fail;
        }
    }
    if ( (status = sBluetoothSocketInterface->listen((btsock_type_t) type, service_name,
                       (const uint8_t*) uuid, channel, &socket_fd, flag, callingUid))
            != BT_STATUS_SUCCESS) {
        ALOGE("Socket listen failed: %d", status);
        goto Fail;
    }

    if (socket_fd < 0) {
        ALOGE("Fail to creat file descriptor on socket fd");
        goto Fail;
    }
    if (service_name) env->ReleaseStringUTFChars(name_str, service_name);
    if (uuid) env->ReleaseByteArrayElements(uuidObj, uuid, 0);
    return socket_fd;

Fail:
    if (service_name) env->ReleaseStringUTFChars(name_str, service_name);
    if (uuid) env->ReleaseByteArrayElements(uuidObj, uuid, 0);
    return -1;
}

static jboolean configHciSnoopLogNative(JNIEnv* env, jobject obj, jboolean enable) {
    ALOGV("%s:",__FUNCTION__);

    jboolean result = JNI_FALSE;

    if (!sBluetoothInterface) return result;

    int ret = sBluetoothInterface->config_hci_snoop_log(enable);

    result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;

    return result;
}

static int readEnergyInfo()
{
    ALOGV("%s:",__FUNCTION__);
    jboolean result = JNI_FALSE;
    if (!sBluetoothInterface) return result;
    int ret = sBluetoothInterface->read_energy_info();
    result = (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
    return result;
}

static void dumpNative(JNIEnv *env, jobject obj, jobject fdObj,
                       jobjectArray argArray)
{
    ALOGV("%s()", __FUNCTION__);
    if (!sBluetoothInterface) return;

    int fd = jniGetFDFromFileDescriptor(env, fdObj);
    if (fd < 0) return;

    int numArgs = env->GetArrayLength(argArray);

    jstring *argObjs = new jstring[numArgs];
    const char **args = nullptr;
    if (numArgs > 0)
      args = new const char*[numArgs];

    for (int i = 0; i < numArgs; i++) {
      argObjs[i] = (jstring) env->GetObjectArrayElement(argArray, i);
      args[i] = env->GetStringUTFChars(argObjs[i], NULL);
    }

    sBluetoothInterface->dump(fd, args);

    for (int i = 0; i < numArgs; i++) {
      env->ReleaseStringUTFChars(argObjs[i], args[i]);
    }

    delete[] args;
    delete[] argObjs;
}

static jboolean factoryResetNative(JNIEnv *env, jobject obj) {
    ALOGV("%s:", __FUNCTION__);
    if (!sBluetoothInterface) return JNI_FALSE;
    int ret = sBluetoothInterface->config_clear();
    return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}

static void interopDatabaseClearNative(JNIEnv *env, jobject obj) {
    ALOGV("%s()", __FUNCTION__);
    if (!sBluetoothInterface) return;
    sBluetoothInterface->interop_database_clear();
}

static void interopDatabaseAddNative(JNIEnv *env, jobject obj, int feature,
                                      jbyteArray address, int length) {
    ALOGV("%s()", __FUNCTION__);
    if (!sBluetoothInterface) return;

    jbyte *addr = env->GetByteArrayElements(address, NULL);
    if (addr == NULL) {
        jniThrowIOException(env, EINVAL);
        return;
    }

    sBluetoothInterface->interop_database_add(feature, (bt_bdaddr_t *)addr, length);
    env->ReleaseByteArrayElements(address, addr, 0);
}

static JNINativeMethod sMethods[] = {
    /* name, signature, funcPtr */
    {"classInitNative", "()V", (void *) classInitNative},
    {"initNative", "()Z", (void *) initNative},
    {"cleanupNative", "()V", (void*) cleanupNative},
    {"enableNative", "(Z)Z",  (void*) enableNative},
    {"disableNative", "()Z",  (void*) disableNative},
    {"setAdapterPropertyNative", "(I[B)Z", (void*) setAdapterPropertyNative},
    {"getAdapterPropertiesNative", "()Z", (void*) getAdapterPropertiesNative},
    {"getAdapterPropertyNative", "(I)Z", (void*) getAdapterPropertyNative},
    {"getDevicePropertyNative", "([BI)Z", (void*) getDevicePropertyNative},
    {"setDevicePropertyNative", "([BI[B)Z", (void*) setDevicePropertyNative},
    {"startDiscoveryNative", "()Z", (void*) startDiscoveryNative},
    {"cancelDiscoveryNative", "()Z", (void*) cancelDiscoveryNative},
    {"createBondNative", "([BI)Z", (void*) createBondNative},
    {"createBondOutOfBandNative", "([BILandroid/bluetooth/OobData;)Z", (void*) createBondOutOfBandNative},
    {"removeBondNative", "([B)Z", (void*) removeBondNative},
    {"cancelBondNative", "([B)Z", (void*) cancelBondNative},
    {"getConnectionStateNative", "([B)I", (void*) getConnectionStateNative},
    {"pinReplyNative", "([BZI[B)Z", (void*) pinReplyNative},
    {"sspReplyNative", "([BIZI)Z", (void*) sspReplyNative},
    {"getRemoteServicesNative", "([B)Z", (void*) getRemoteServicesNative},
    {"connectSocketNative", "([BI[BIII)I", (void*) connectSocketNative},
    {"createSocketChannelNative", "(ILjava/lang/String;[BIII)I",
     (void*) createSocketChannelNative},
    {"configHciSnoopLogNative", "(Z)Z", (void*) configHciSnoopLogNative},
    {"alarmFiredNative", "()V", (void *) alarmFiredNative},
    {"readEnergyInfo", "()I", (void*) readEnergyInfo},
    {"dumpNative", "(Ljava/io/FileDescriptor;[Ljava/lang/String;)V", (void*) dumpNative},
    {"factoryResetNative", "()Z", (void*)factoryResetNative},
    {"interopDatabaseClearNative", "()V", (void*) interopDatabaseClearNative},
    {"interopDatabaseAddNative", "(I[BI)V", (void*) interopDatabaseAddNative}
};

int register_com_android_bluetooth_btservice_AdapterService(JNIEnv* env)
{
    return jniRegisterNativeMethods(env, "com/android/bluetooth/btservice/AdapterService",
                                    sMethods, NELEM(sMethods));
}

} /* namespace android */


/*
 * JNI Initialization
 */
jint JNI_OnLoad(JavaVM *jvm, void *reserved)
{
    JNIEnv *e;
    int status;

    ALOGV("Bluetooth Adapter Service : loading JNI\n");

    // Check JNI version
    if (jvm->GetEnv((void **)&e, JNI_VERSION_1_6)) {
        ALOGE("JNI version mismatch error");
        return JNI_ERR;
    }

    if ((status = android::register_com_android_bluetooth_btservice_AdapterService(e)) < 0) {
        ALOGE("jni adapter service registration failure, status: %d", status);
        return JNI_ERR;
    }

    if ((status = android::register_com_android_bluetooth_hfp(e)) < 0) {
        ALOGE("jni hfp registration failure, status: %d", status);
        return JNI_ERR;
    }

    if ((status = android::register_com_android_bluetooth_hfpclient(e)) < 0) {
        ALOGE("jni hfp client registration failure, status: %d", status);
        return JNI_ERR;
    }

    if ((status = android::register_com_android_bluetooth_a2dp(e)) < 0) {
        ALOGE("jni a2dp source registration failure: %d", status);
        return JNI_ERR;
    }

    if ((status = android::register_com_android_bluetooth_a2dp_sink(e)) < 0) {
        ALOGE("jni a2dp sink registration failure: %d", status);
        return JNI_ERR;
    }

    if ((status = android::register_com_android_bluetooth_avrcp(e)) < 0) {
        ALOGE("jni avrcp target registration failure: %d", status);
        return JNI_ERR;
    }

    if ((status = android::register_com_android_bluetooth_avrcp_controller(e)) < 0) {
        ALOGE("jni avrcp controller registration failure: %d", status);
        return JNI_ERR;
    }

    if ((status = android::register_com_android_bluetooth_hid(e)) < 0) {
        ALOGE("jni hid registration failure: %d", status);
        return JNI_ERR;
    }

    if ((status = android::register_com_android_bluetooth_hdp(e)) < 0) {
        ALOGE("jni hdp registration failure: %d", status);
        return JNI_ERR;
    }

    if ((status = android::register_com_android_bluetooth_pan(e)) < 0) {
        ALOGE("jni pan registration failure: %d", status);
        return JNI_ERR;
    }

    if ((status = android::register_com_android_bluetooth_gatt(e)) < 0) {
        ALOGE("jni gatt registration failure: %d", status);
        return JNI_ERR;
    }

    if ((status = android::register_com_android_bluetooth_sdp(e)) < 0) {
        ALOGE("jni sdp registration failure: %d", status);
        return JNI_ERR;
    }

    return JNI_VERSION_1_6;
}