/*
* 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 "BluetoothHidServiceJni"
#define LOG_NDEBUG 1
#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_hh.h"
#include "utils/Log.h"
#include "android_runtime/AndroidRuntime.h"
#include <string.h>
namespace android {
static jmethodID method_onConnectStateChanged;
static jmethodID method_onGetProtocolMode;
static jmethodID method_onGetReport;
static jmethodID method_onHandshake;
static jmethodID method_onVirtualUnplug;
static const bthh_interface_t *sBluetoothHidInterface = NULL;
static jobject mCallbacksObj = NULL;
static JNIEnv *sCallbackEnv = NULL;
static bool checkCallbackThread() {
// Always fetch the latest callbackEnv from AdapterService.
// Caching this could cause this sCallbackEnv to go out-of-sync
// with the AdapterService's ENV if an ASSOCIATE/DISASSOCIATE event
// is received
sCallbackEnv = getCallbackEnv();
JNIEnv* env = AndroidRuntime::getJNIEnv();
if (sCallbackEnv != env || sCallbackEnv == NULL) return false;
return true;
}
static void connection_state_callback(bt_bdaddr_t *bd_addr, bthh_connection_state_t state) {
jbyteArray addr;
CHECK_CALLBACK_ENV
addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
if (!addr) {
ALOGE("Fail to new jbyteArray bd addr for HID channel state");
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_protocol_mode_callback(bt_bdaddr_t *bd_addr, bthh_status_t hh_status,bthh_protocol_mode_t mode) {
jbyteArray addr;
CHECK_CALLBACK_ENV
if (hh_status != BTHH_OK) {
ALOGE("BTHH Status is not OK!");
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
return;
}
addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
if (!addr) {
ALOGE("Fail to new jbyteArray bd addr for get protocal mode callback");
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGetProtocolMode, addr, (jint) mode);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(addr);
}
static void get_report_callback(bt_bdaddr_t *bd_addr, bthh_status_t hh_status, uint8_t *rpt_data, int rpt_size) {
jbyteArray addr;
jbyteArray data;
CHECK_CALLBACK_ENV
if (hh_status != BTHH_OK) {
ALOGE("BTHH Status is not OK!");
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
return;
}
addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
if (!addr) {
ALOGE("Fail to new jbyteArray bd addr for get report callback");
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
return;
}
data = sCallbackEnv->NewByteArray(rpt_size);
if (!data) {
ALOGE("Fail to new jbyteArray data for get report callback");
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(addr);
return;
}
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
sCallbackEnv->SetByteArrayRegion(data, 0, rpt_size, (jbyte *) rpt_data);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGetReport, addr, data, (jint) rpt_size);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(addr);
sCallbackEnv->DeleteLocalRef(data);
}
static void virtual_unplug_callback(bt_bdaddr_t *bd_addr, bthh_status_t hh_status) {
ALOGV("call to virtual_unplug_callback");
jbyteArray addr;
CHECK_CALLBACK_ENV
addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
if (!addr) {
ALOGE("Fail to new jbyteArray bd addr for HID channel state");
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVirtualUnplug, addr, (jint) hh_status);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(addr);
/*jbyteArray addr;
jint status = hh_status;
CHECK_CALLBACK_ENV
addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
if (!addr) {
ALOGE("Fail to new jbyteArray bd addr for HID report");
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVirtualUnplug, addr, status);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(addr);*/
}
static void handshake_callback(bt_bdaddr_t *bd_addr, bthh_status_t hh_status)
{
jbyteArray addr;
CHECK_CALLBACK_ENV
addr = sCallbackEnv->NewByteArray(sizeof(bt_bdaddr_t));
if (!addr) {
ALOGE("Fail to new jbyteArray bd addr for handshake callback");
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
return;
}
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(bt_bdaddr_t), (jbyte *) bd_addr);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onHandshake, addr, (jint) hh_status);
checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
sCallbackEnv->DeleteLocalRef(addr);
}
static bthh_callbacks_t sBluetoothHidCallbacks = {
sizeof(sBluetoothHidCallbacks),
connection_state_callback,
NULL,
get_protocol_mode_callback,
NULL,
get_report_callback,
virtual_unplug_callback,
handshake_callback
};
// Define native functions
static void classInitNative(JNIEnv* env, jclass clazz) {
method_onConnectStateChanged = env->GetMethodID(clazz, "onConnectStateChanged", "([BI)V");
method_onGetProtocolMode = env->GetMethodID(clazz, "onGetProtocolMode", "([BI)V");
method_onGetReport = env->GetMethodID(clazz, "onGetReport", "([B[BI)V");
method_onHandshake = env->GetMethodID(clazz, "onHandshake", "([BI)V");
method_onVirtualUnplug = env->GetMethodID(clazz, "onVirtualUnplug", "([BI)V");
ALOGI("%s: succeeds", __FUNCTION__);
}
static void initializeNative(JNIEnv *env, jobject object) {
const bt_interface_t* btInf;
bt_status_t status;
if ( (btInf = getBluetoothInterface()) == NULL) {
ALOGE("Bluetooth module is not loaded");
return;
}
if (sBluetoothHidInterface !=NULL) {
ALOGW("Cleaning up Bluetooth HID Interface before initializing...");
sBluetoothHidInterface->cleanup();
sBluetoothHidInterface = NULL;
}
if (mCallbacksObj != NULL) {
ALOGW("Cleaning up Bluetooth GID callback object");
env->DeleteGlobalRef(mCallbacksObj);
mCallbacksObj = NULL;
}
if ( (sBluetoothHidInterface = (bthh_interface_t *)
btInf->get_profile_interface(BT_PROFILE_HIDHOST_ID)) == NULL) {
ALOGE("Failed to get Bluetooth HID Interface");
return;
}
if ( (status = sBluetoothHidInterface->init(&sBluetoothHidCallbacks)) != BT_STATUS_SUCCESS) {
ALOGE("Failed to initialize Bluetooth HID, status: %d", status);
sBluetoothHidInterface = NULL;
return;
}
mCallbacksObj = env->NewGlobalRef(object);
}
static void cleanupNative(JNIEnv *env, jobject object) {
const bt_interface_t* btInf;
if ( (btInf = getBluetoothInterface()) == NULL) {
ALOGE("Bluetooth module is not loaded");
return;
}
if (sBluetoothHidInterface !=NULL) {
ALOGW("Cleaning up Bluetooth HID Interface...");
sBluetoothHidInterface->cleanup();
sBluetoothHidInterface = NULL;
}
if (mCallbacksObj != NULL) {
ALOGW("Cleaning up Bluetooth GID callback object");
env->DeleteGlobalRef(mCallbacksObj);
mCallbacksObj = NULL;
}
env->DeleteGlobalRef(mCallbacksObj);
}
static jboolean connectHidNative(JNIEnv *env, jobject object, jbyteArray address) {
bt_status_t status;
jbyte *addr;
jboolean ret = JNI_TRUE;
if (!sBluetoothHidInterface) return JNI_FALSE;
addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
ALOGE("Bluetooth device address null");
return JNI_FALSE;
}
if ((status = sBluetoothHidInterface->connect((bt_bdaddr_t *) addr)) !=
BT_STATUS_SUCCESS) {
ALOGE("Failed HID channel connection, status: %d", status);
ret = JNI_FALSE;
}
env->ReleaseByteArrayElements(address, addr, 0);
return ret;
}
static jboolean disconnectHidNative(JNIEnv *env, jobject object, jbyteArray address) {
bt_status_t status;
jbyte *addr;
jboolean ret = JNI_TRUE;
if (!sBluetoothHidInterface) return JNI_FALSE;
addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
ALOGE("Bluetooth device address null");
return JNI_FALSE;
}
if ( (status = sBluetoothHidInterface->disconnect((bt_bdaddr_t *) addr)) !=
BT_STATUS_SUCCESS) {
ALOGE("Failed disconnect hid channel, status: %d", status);
ret = JNI_FALSE;
}
env->ReleaseByteArrayElements(address, addr, 0);
return ret;
}
static jboolean getProtocolModeNative(JNIEnv *env, jobject object, jbyteArray address) {
bt_status_t status;
jbyte *addr;
jboolean ret = JNI_TRUE;
bthh_protocol_mode_t protocolMode;
if (!sBluetoothHidInterface) return JNI_FALSE;
addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
ALOGE("Bluetooth device address null");
return JNI_FALSE;
}
if ( (status = sBluetoothHidInterface->get_protocol((bt_bdaddr_t *) addr, (bthh_protocol_mode_t) protocolMode)) !=
BT_STATUS_SUCCESS) {
ALOGE("Failed get protocol mode, status: %d", status);
ret = JNI_FALSE;
}
env->ReleaseByteArrayElements(address, addr, 0);
return ret;
}
static jboolean virtualUnPlugNative(JNIEnv *env, jobject object, jbyteArray address) {
bt_status_t status;
jbyte *addr;
jboolean ret = JNI_TRUE;
if (!sBluetoothHidInterface) return JNI_FALSE;
addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
ALOGE("Bluetooth device address null");
return JNI_FALSE;
}
if ( (status = sBluetoothHidInterface->virtual_unplug((bt_bdaddr_t *) addr)) !=
BT_STATUS_SUCCESS) {
ALOGE("Failed virual unplug, status: %d", status);
ret = JNI_FALSE;
}
env->ReleaseByteArrayElements(address, addr, 0);
return ret;
}
static jboolean setProtocolModeNative(JNIEnv *env, jobject object, jbyteArray address, jint protocolMode) {
bt_status_t status;
jbyte *addr;
jboolean ret = JNI_TRUE;
if (!sBluetoothHidInterface) return JNI_FALSE;
ALOGD("%s: protocolMode = %d", __FUNCTION__, protocolMode);
addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
ALOGE("Bluetooth device address null");
return JNI_FALSE;
}
bthh_protocol_mode_t mode;
switch(protocolMode){
case 0:
mode = BTHH_REPORT_MODE;
break;
case 1:
mode = BTHH_BOOT_MODE;
break;
default:
ALOGE("Unknown HID protocol mode");
return JNI_FALSE;
}
if ( (status = sBluetoothHidInterface->set_protocol((bt_bdaddr_t *) addr, mode)) !=
BT_STATUS_SUCCESS) {
ALOGE("Failed set protocol mode, status: %d", status);
ret = JNI_FALSE;
}
env->ReleaseByteArrayElements(address, addr, 0);
return JNI_TRUE;
}
static jboolean getReportNative(JNIEnv *env, jobject object, jbyteArray address, jbyte reportType, jbyte reportId, jint bufferSize) {
ALOGV("%s: reportType = %d, reportId = %d, bufferSize = %d", __FUNCTION__, reportType, reportId, bufferSize);
bt_status_t status;
jbyte *addr;
jboolean ret = JNI_TRUE;
if (!sBluetoothHidInterface) return JNI_FALSE;
addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
ALOGE("Bluetooth device address null");
return JNI_FALSE;
}
jint rType = reportType;
jint rId = reportId;
if ( (status = sBluetoothHidInterface->get_report((bt_bdaddr_t *) addr, (bthh_report_type_t) rType, (uint8_t) rId, bufferSize)) !=
BT_STATUS_SUCCESS) {
ALOGE("Failed get report, status: %d", status);
ret = JNI_FALSE;
}
env->ReleaseByteArrayElements(address, addr, 0);
return ret;
}
static jboolean setReportNative(JNIEnv *env, jobject object, jbyteArray address, jbyte reportType, jstring report) {
ALOGV("%s: reportType = %d", __FUNCTION__, reportType);
bt_status_t status;
jbyte *addr;
jboolean ret = JNI_TRUE;
if (!sBluetoothHidInterface) return JNI_FALSE;
addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
ALOGE("Bluetooth device address null");
return JNI_FALSE;
}
jint rType = reportType;
const char *c_report = env->GetStringUTFChars(report, NULL);
if ( (status = sBluetoothHidInterface->set_report((bt_bdaddr_t *) addr, (bthh_report_type_t)rType, (char*) c_report)) !=
BT_STATUS_SUCCESS) {
ALOGE("Failed set report, status: %d", status);
ret = JNI_FALSE;
}
env->ReleaseStringUTFChars(report, c_report);
env->ReleaseByteArrayElements(address, addr, 0);
return ret;
}
static jboolean sendDataNative(JNIEnv *env, jobject object, jbyteArray address, jstring report) {
ALOGV("%s", __FUNCTION__);
bt_status_t status;
jbyte *addr;
jboolean ret = JNI_TRUE;
if (!sBluetoothHidInterface) return JNI_FALSE;
addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
ALOGE("Bluetooth device address null");
return JNI_FALSE;
}
const char *c_report = env->GetStringUTFChars(report, NULL);
if ( (status = sBluetoothHidInterface->send_data((bt_bdaddr_t *) addr, (char*) c_report)) !=
BT_STATUS_SUCCESS) {
ALOGE("Failed set report, status: %d", status);
ret = JNI_FALSE;
}
env->ReleaseStringUTFChars(report, c_report);
env->ReleaseByteArrayElements(address, addr, 0);
return ret;
}
static JNINativeMethod sMethods[] = {
{"classInitNative", "()V", (void *) classInitNative},
{"initializeNative", "()V", (void *) initializeNative},
{"cleanupNative", "()V", (void *) cleanupNative},
{"connectHidNative", "([B)Z", (void *) connectHidNative},
{"disconnectHidNative", "([B)Z", (void *) disconnectHidNative},
{"getProtocolModeNative", "([B)Z", (void *) getProtocolModeNative},
{"virtualUnPlugNative", "([B)Z", (void *) virtualUnPlugNative},
{"setProtocolModeNative", "([BB)Z", (void *) setProtocolModeNative},
{"getReportNative", "([BBBI)Z", (void *) getReportNative},
{"setReportNative", "([BBLjava/lang/String;)Z", (void *) setReportNative},
{"sendDataNative", "([BLjava/lang/String;)Z", (void *) sendDataNative},
};
int register_com_android_bluetooth_hid(JNIEnv* env)
{
return jniRegisterNativeMethods(env, "com/android/bluetooth/hid/HidService",
sMethods, NELEM(sMethods));
}
}