blob: 0a51157ff4cd8c1b2dd49c59cc5e769f693ce605 [file] [log] [blame]
/*
* Copyright (C) 2013 The Linux Foundation. All rights reserved
* Not a Contribution.
* 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 "BluetoothHidDevServiceJni"
#define LOG_NDEBUG 0
#define CHECK_CALLBACK_ENV \
if (!checkCallbackThread()) { \
ALOGE("Callback: '%s' is not called on the correct thread", __FUNCTION__);\
return; \
}
#include "com_android_bluetooth.h"
#include "hardware/bt_hd.h"
#include "utils/Log.h"
#include "android_runtime/AndroidRuntime.h"
#include <string.h>
namespace android {
static jmethodID method_onApplicationStateChanged;
static jmethodID method_onConnectStateChanged;
static jmethodID method_onGetReport;
static jmethodID method_onSetReport;
static jmethodID method_onSetProtocol;
static jmethodID method_onIntrData;
static jmethodID method_onVirtualCableUnplug;
static const bthd_interface_t *sHiddIf = NULL;
static jobject mCallbacksObj = NULL;
static JNIEnv *sCallbackEnv = NULL;
static bool checkCallbackThread() {
sCallbackEnv = getCallbackEnv();
if (sCallbackEnv != AndroidRuntime::getJNIEnv() || sCallbackEnv == NULL) {
return false;
}
return true;
}
static void application_state_callback(bt_bdaddr_t *bd_addr, bthd_application_state_t state) {
jboolean registered = JNI_FALSE;
jbyteArray addr;
CHECK_CALLBACK_ENV
if (state == BTHD_APP_STATE_REGISTERED) {
registered = JNI_TRUE;
}
if (bd_addr) {
addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
if (!addr) {
ALOGE("%s: failed to allocate storage for bt_addr", __FUNCTION__);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
} else {
addr = NULL;
}
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onApplicationStateChanged, addr, registered);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
if (addr) {
sCallbackEnv->DeleteLocalRef(addr);
}
}
static void connection_state_callback(bt_bdaddr_t *bd_addr, bthd_connection_state_t state) {
jbyteArray addr;
CHECK_CALLBACK_ENV
addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
if (!addr) {
ALOGE("%s: failed to allocate storage for bt_addr", __FUNCTION__);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectStateChanged, addr, (jint) state);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(addr);
}
static void get_report_callback(uint8_t type, uint8_t id, uint16_t buffer_size) {
CHECK_CALLBACK_ENV
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGetReport, type, id, buffer_size);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
}
static void set_report_callback(uint8_t type, uint8_t id, uint16_t len, uint8_t *p_data) {
jbyteArray data;
CHECK_CALLBACK_ENV
data = sCallbackEnv->NewByteArray(len);
if (!data) {
ALOGE("%s: failed to allocate storage for report data", __FUNCTION__);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
return;
}
sCallbackEnv->SetByteArrayRegion(data, 0, len, (jbyte *) p_data);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSetReport, (jbyte) type, (jbyte) id, data);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(data);
}
static void set_protocol_callback(uint8_t protocol) {
CHECK_CALLBACK_ENV
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSetProtocol, protocol);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
}
static void intr_data_callback(uint8_t report_id, uint16_t len, uint8_t *p_data) {
jbyteArray data;
CHECK_CALLBACK_ENV
data = sCallbackEnv->NewByteArray(len);
if (!data) {
ALOGE("%s: failed to allocate storage for report data", __FUNCTION__);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
return;
}
sCallbackEnv->SetByteArrayRegion(data, 0, len, (jbyte *) p_data);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onIntrData, (jbyte) report_id, data);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(data);
}
static void vc_unplug_callback(void) {
CHECK_CALLBACK_ENV
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVirtualCableUnplug);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
}
static bthd_callbacks_t sHiddCb = {
sizeof(sHiddCb),
application_state_callback,
connection_state_callback,
get_report_callback,
set_report_callback,
set_protocol_callback,
intr_data_callback,
vc_unplug_callback,
};
static void classInitNative(JNIEnv* env, jclass clazz) {
ALOGV("%s: done", __FUNCTION__);
method_onApplicationStateChanged = env->GetMethodID(clazz, "onApplicationStateChanged", "([BZ)V");
method_onConnectStateChanged = env->GetMethodID(clazz, "onConnectStateChanged", "([BI)V");
method_onGetReport = env->GetMethodID(clazz, "onGetReport", "(BBS)V");
method_onSetReport = env->GetMethodID(clazz, "onSetReport", "(BB[B)V");
method_onSetProtocol = env->GetMethodID(clazz, "onSetProtocol", "(B)V");
method_onIntrData = env->GetMethodID(clazz, "onIntrData", "(B[B)V");
method_onVirtualCableUnplug = env->GetMethodID(clazz, "onVirtualCableUnplug", "()V");
}
static void initNative(JNIEnv *env, jobject object) {
const bt_interface_t* btif;
bt_status_t status;
ALOGV("%s enter", __FUNCTION__);
if ((btif = getBluetoothInterface()) == NULL) {
ALOGE("Cannot obtain BT interface");
return;
}
if (sHiddIf != NULL) {
ALOGW("Cleaning up interface");
sHiddIf->cleanup();
sHiddIf = NULL;
}
if (mCallbacksObj != NULL) {
ALOGW("Cleaning up callback object");
env->DeleteGlobalRef(mCallbacksObj);
mCallbacksObj = NULL;
}
if ((sHiddIf = (bthd_interface_t *) btif->get_profile_interface(BT_PROFILE_HIDDEV_ID))
== NULL) {
ALOGE("Cannot obtain interface");
return;
}
if ((status = sHiddIf->init(&sHiddCb)) != BT_STATUS_SUCCESS) {
ALOGE("Failed to initialize interface (%d)", status);
sHiddIf = NULL;
return;
}
mCallbacksObj = env->NewGlobalRef(object);
ALOGV("%s done", __FUNCTION__);
}
static void cleanupNative(JNIEnv *env, jobject object) {
ALOGV("%s enter", __FUNCTION__);
if (sHiddIf !=NULL) {
ALOGI("Cleaning up interface");
sHiddIf->cleanup();
sHiddIf = NULL;
}
if (mCallbacksObj != NULL) {
ALOGI("Cleaning up callback object");
env->DeleteGlobalRef(mCallbacksObj);
mCallbacksObj = NULL;
}
ALOGV("%s done", __FUNCTION__);
}
static void fill_qos(JNIEnv *env, jintArray in, bthd_qos_param_t *out) {
// set default values
out->service_type = 0x01; // best effort
out->token_rate = out->token_bucket_size = out->peak_bandwidth = 0; // don't care
out->access_latency = out->delay_variation = 0xffffffff; // don't care
if (in == NULL)
return;
jsize len = env->GetArrayLength(in);
if (len != 6)
return;
uint32_t *buf = (uint32_t *) calloc(len, sizeof(uint32_t));
if (buf == NULL)
return;
env->GetIntArrayRegion(in, 0, len, (jint *) buf);
out->service_type = (uint8_t) buf[0];
out->token_rate = buf[1];
out->token_bucket_size = buf[2];
out->peak_bandwidth = buf[3];
out->access_latency = buf[4];
out->delay_variation = buf[5];
free(buf);
}
static jboolean registerAppNative(JNIEnv *env, jobject thiz, jstring name, jstring description,
jstring provider, jbyte subclass, jbyteArray descriptors, jintArray p_in_qos, jintArray p_out_qos) {
ALOGV("%s enter", __FUNCTION__);
jboolean result = JNI_FALSE;
bthd_app_param_t app_param;
bthd_qos_param_t in_qos;
bthd_qos_param_t out_qos;
jsize size;
uint8_t *data;
if (sHiddIf == NULL) {
ALOGV("%s: HID Device interface not initialized", __FUNCTION__);
return JNI_FALSE;
}
size = env->GetArrayLength(descriptors);
data = (uint8_t *) malloc(size);
if (data != NULL) {
env->GetByteArrayRegion(descriptors, 0, size, (jbyte *) data);
app_param.name = env->GetStringUTFChars(name, NULL);
app_param.description = env->GetStringUTFChars(description, NULL);
app_param.provider = env->GetStringUTFChars(provider, NULL);
app_param.subclass = subclass;
app_param.desc_list = data;
app_param.desc_list_len = size;
fill_qos(env, p_in_qos, &in_qos);
fill_qos(env, p_out_qos, &out_qos);
bt_status_t ret = sHiddIf->register_app(&app_param, &in_qos, &out_qos);
ALOGV("%s: register_app() returned %d", __FUNCTION__, ret);
if (ret == BT_STATUS_SUCCESS)
{
result = JNI_TRUE;
}
env->ReleaseStringUTFChars(name, app_param.name);
env->ReleaseStringUTFChars(description, app_param.description);
env->ReleaseStringUTFChars(provider, app_param.provider);
free(data);
}
ALOGV("%s done (%d)", __FUNCTION__, result);
return result;
}
static jboolean unregisterAppNative(JNIEnv *env, jobject thiz) {
ALOGV("%s enter", __FUNCTION__);
if (sHiddIf == NULL) {
ALOGV("%s: HID Device interface not initialized", __FUNCTION__);
return JNI_FALSE;
}
jboolean result = JNI_FALSE;
bt_status_t ret = sHiddIf->unregister_app();
ALOGV("%s: unregister_app() returned %d", __FUNCTION__, ret);
if (ret == BT_STATUS_SUCCESS)
{
result = JNI_TRUE;
}
ALOGV("%s done (%d)", __FUNCTION__, result);
return result;
}
static jboolean sendReportNative(JNIEnv *env, jobject thiz, jint id, jbyteArray data) {
ALOGV("%s enter", __FUNCTION__);
if (sHiddIf == NULL) {
ALOGV("%s: HID Device interface not initialized", __FUNCTION__);
return JNI_FALSE;
}
jboolean result = JNI_FALSE;
jsize size;
uint8_t *buf;
size = env->GetArrayLength(data);
buf = (uint8_t *) malloc(size);
if (buf != NULL) {
env->GetByteArrayRegion(data, 0, size, (jbyte *) buf);
bt_status_t ret = sHiddIf->send_report(BTHD_REPORT_TYPE_INTRDATA, id, size, buf);
ALOGV("%s: send_report() returned %d", __FUNCTION__, ret);
if (ret == BT_STATUS_SUCCESS)
{
result = JNI_TRUE;
}
free(buf);
}
ALOGV("%s done (%d)", __FUNCTION__, result);
return result;
}
static jboolean replyReportNative(JNIEnv *env, jobject thiz, jbyte type, jbyte id, jbyteArray data) {
ALOGV("%s enter", __FUNCTION__);
if (sHiddIf == NULL) {
ALOGV("%s: HID Device interface not initialized", __FUNCTION__);
return JNI_FALSE;
}
jboolean result = JNI_FALSE;
jsize size;
uint8_t *buf;
size = env->GetArrayLength(data);
buf = (uint8_t *) malloc(size);
if (buf != NULL) {
int report_type = (type & 0x03);
env->GetByteArrayRegion(data, 0, size, (jbyte *) buf);
bt_status_t ret = sHiddIf->send_report((bthd_report_type_t) report_type, id, size, buf);
ALOGV("%s: send_report() returned %d", __FUNCTION__, ret);
if (ret == BT_STATUS_SUCCESS)
{
result = JNI_TRUE;
}
free(buf);
}
ALOGV("%s done (%d)", __FUNCTION__, result);
return result;
}
static jboolean reportErrorNative(JNIEnv *env, jobject thiz, jbyte error) {
ALOGV("%s enter", __FUNCTION__);
if (sHiddIf == NULL) {
ALOGV("%s: HID Device interface not initialized", __FUNCTION__);
return JNI_FALSE;
}
jboolean result = JNI_FALSE;
bt_status_t ret = sHiddIf->report_error(error);
ALOGV("%s: report_error() returned %d", __FUNCTION__, ret);
if (ret == BT_STATUS_SUCCESS)
{
result = JNI_TRUE;
}
ALOGV("%s done (%d)", __FUNCTION__, result);
return result;
}
static jboolean unplugNative(JNIEnv *env, jobject thiz) {
ALOGV("%s enter", __FUNCTION__);
if (sHiddIf == NULL) {
ALOGV("%s: HID Device interface not initialized", __FUNCTION__);
return JNI_FALSE;
}
jboolean result = JNI_FALSE;
bt_status_t ret = sHiddIf->virtual_cable_unplug();
ALOGV("%s: virtual_cable_unplug() returned %d", __FUNCTION__, ret);
if (ret == BT_STATUS_SUCCESS)
{
result = JNI_TRUE;
}
ALOGV("%s done (%d)", __FUNCTION__, result);
return result;
}
static jboolean connectNative(JNIEnv *env, jobject thiz) {
ALOGV("%s enter", __FUNCTION__);
if (sHiddIf == NULL) {
ALOGV("%s: HID Device interface not initialized", __FUNCTION__);
return JNI_FALSE;
}
jboolean result = JNI_FALSE;
bt_status_t ret = sHiddIf->connect();
ALOGV("%s: connect() returned %d", __FUNCTION__, ret);
if (ret == BT_STATUS_SUCCESS)
{
result = JNI_TRUE;
}
ALOGV("%s done (%d)", __FUNCTION__, result);
return result;
}
static jboolean disconnectNative(JNIEnv *env, jobject thiz) {
ALOGV("%s enter", __FUNCTION__);
if (sHiddIf == NULL) {
ALOGV("%s: HID Device interface not initialized", __FUNCTION__);
return JNI_FALSE;
}
jboolean result = JNI_FALSE;
bt_status_t ret = sHiddIf->disconnect();
ALOGV("%s: disconnect() returned %d", __FUNCTION__, ret);
if (ret == BT_STATUS_SUCCESS)
{
result = JNI_TRUE;
}
ALOGV("%s done (%d)", __FUNCTION__, result);
return result;
}
static JNINativeMethod sMethods[] = {
{"classInitNative", "()V", (void *) classInitNative},
{"initNative", "()V", (void *) initNative},
{"cleanupNative", "()V", (void *) cleanupNative},
{"registerAppNative", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;B[B[I[I)Z", (void *) registerAppNative},
{"unregisterAppNative", "()Z", (void *) unregisterAppNative},
{"sendReportNative", "(I[B)Z", (void *) sendReportNative},
{"replyReportNative", "(BB[B)Z", (void *) replyReportNative},
{"reportErrorNative", "(B)Z", (void *) reportErrorNative},
{"unplugNative", "()Z", (void *) unplugNative},
{"connectNative", "()Z", (void *) connectNative},
{"disconnectNative", "()Z", (void *) disconnectNative},
};
int register_com_android_bluetooth_hidd(JNIEnv* env)
{
return jniRegisterNativeMethods(env, "com/android/bluetooth/hid/HidDevService",
sMethods, NELEM(sMethods));
}
}