/*
* 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 "BluetoothHidHostServiceJni"
#define LOG_NDEBUG 1
#include "android_runtime/AndroidRuntime.h"
#include "com_android_bluetooth.h"
#include "hardware/bt_hh.h"
#include "utils/Log.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 jmethodID method_onGetIdleTime;
static const bthh_interface_t* sBluetoothHidInterface = NULL;
static jobject mCallbacksObj = NULL;
static jbyteArray marshall_bda(RawAddress* bd_addr) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return NULL;
jbyteArray addr = sCallbackEnv->NewByteArray(sizeof(RawAddress));
if (!addr) {
ALOGE("Fail to new jbyteArray bd addr");
return NULL;
}
sCallbackEnv->SetByteArrayRegion(addr, 0, sizeof(RawAddress),
(jbyte*)bd_addr);
return addr;
}
static void connection_state_callback(RawAddress* bd_addr,
bthh_connection_state_t state) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
ALOGE("%s: mCallbacksObj is null", __func__);
return;
}
ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
if (!addr.get()) {
ALOGE("Fail to new jbyteArray bd addr for HID channel state");
return;
}
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectStateChanged,
addr.get(), (jint)state);
}
static void get_protocol_mode_callback(RawAddress* bd_addr,
bthh_status_t hh_status,
bthh_protocol_mode_t mode) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
ALOGE("%s: mCallbacksObj is null", __func__);
return;
}
if (hh_status != BTHH_OK) {
ALOGE("BTHH Status is not OK!");
return;
}
ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
if (!addr.get()) {
ALOGE("Fail to new jbyteArray bd addr for get protocal mode callback");
return;
}
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGetProtocolMode,
addr.get(), (jint)mode);
}
static void get_report_callback(RawAddress* bd_addr, bthh_status_t hh_status,
uint8_t* rpt_data, int rpt_size) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
ALOGE("%s: mCallbacksObj is null", __func__);
return;
}
if (hh_status != BTHH_OK) {
ALOGE("BTHH Status is not OK!");
return;
}
ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
if (!addr.get()) {
ALOGE("Fail to new jbyteArray bd addr for get report callback");
return;
}
ScopedLocalRef<jbyteArray> data(sCallbackEnv.get(),
sCallbackEnv->NewByteArray(rpt_size));
if (!data.get()) {
ALOGE("Fail to new jbyteArray data for get report callback");
return;
}
sCallbackEnv->SetByteArrayRegion(data.get(), 0, rpt_size, (jbyte*)rpt_data);
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGetReport, addr.get(),
data.get(), (jint)rpt_size);
}
static void virtual_unplug_callback(RawAddress* bd_addr,
bthh_status_t hh_status) {
ALOGV("call to virtual_unplug_callback");
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
ALOGE("%s: mCallbacksObj is null", __func__);
return;
}
ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
if (!addr.get()) {
ALOGE("Fail to new jbyteArray bd addr for HID channel state");
return;
}
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVirtualUnplug,
addr.get(), (jint)hh_status);
}
static void handshake_callback(RawAddress* bd_addr, bthh_status_t hh_status) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
if (!mCallbacksObj) {
ALOGE("%s: mCallbacksObj is null", __func__);
return;
}
ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
if (!addr.get()) {
ALOGE("Fail to new jbyteArray bd addr for handshake callback");
return;
}
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onHandshake, addr.get(),
(jint)hh_status);
}
static void get_idle_time_callback(RawAddress* bd_addr, bthh_status_t hh_status,
int idle_time) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr));
if (!addr.get()) {
ALOGE("%s: Fail to new jbyteArray bd addr", __func__);
return;
}
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGetIdleTime, addr.get(),
(jint)idle_time);
}
static bthh_callbacks_t sBluetoothHidCallbacks = {
sizeof(sBluetoothHidCallbacks),
connection_state_callback,
NULL,
get_protocol_mode_callback,
get_idle_time_callback,
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");
method_onGetIdleTime = env->GetMethodID(clazz, "onGetIdleTime", "([BI)V");
ALOGI("%s: succeeds", __func__);
}
static void initializeNative(JNIEnv* env, jobject object) {
const bt_interface_t* btInf = getBluetoothInterface();
if (btInf == 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;
}
sBluetoothHidInterface =
(bthh_interface_t*)btInf->get_profile_interface(BT_PROFILE_HIDHOST_ID);
if (sBluetoothHidInterface == NULL) {
ALOGE("Failed to get Bluetooth HID Interface");
return;
}
bt_status_t status = sBluetoothHidInterface->init(&sBluetoothHidCallbacks);
if (status != 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 = getBluetoothInterface();
if (btInf == 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;
}
}
static jboolean connectHidNative(JNIEnv* env, jobject object,
jbyteArray address) {
if (!sBluetoothHidInterface) return JNI_FALSE;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
ALOGE("Bluetooth device address null");
return JNI_FALSE;
}
jboolean ret = JNI_TRUE;
bt_status_t status = sBluetoothHidInterface->connect((RawAddress*)addr);
if (status != 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) {
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;
}
bt_status_t status = sBluetoothHidInterface->disconnect((RawAddress*)addr);
if (status != 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) {
if (!sBluetoothHidInterface) return JNI_FALSE;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
ALOGE("Bluetooth device address null");
return JNI_FALSE;
}
jboolean ret = JNI_TRUE;
// TODO: protocolMode is unused by the backend: see b/28908173
bthh_protocol_mode_t protocolMode = BTHH_UNSUPPORTED_MODE;
bt_status_t status = sBluetoothHidInterface->get_protocol(
(RawAddress*)addr, (bthh_protocol_mode_t)protocolMode);
if (status != 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) {
if (!sBluetoothHidInterface) return JNI_FALSE;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
ALOGE("Bluetooth device address null");
return JNI_FALSE;
}
jboolean ret = JNI_TRUE;
bt_status_t status =
sBluetoothHidInterface->virtual_unplug((RawAddress*)addr);
if (status != 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) {
if (!sBluetoothHidInterface) return JNI_FALSE;
ALOGD("%s: protocolMode = %d", __func__, protocolMode);
jbyte* 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;
}
jboolean ret = JNI_TRUE;
bt_status_t status =
sBluetoothHidInterface->set_protocol((RawAddress*)addr, mode);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed set protocol mode, status: %d", status);
ret = JNI_FALSE;
}
env->ReleaseByteArrayElements(address, addr, 0);
return ret;
}
static jboolean getReportNative(JNIEnv* env, jobject object, jbyteArray address,
jbyte reportType, jbyte reportId,
jint bufferSize) {
ALOGV("%s: reportType = %d, reportId = %d, bufferSize = %d", __func__,
reportType, reportId, bufferSize);
if (!sBluetoothHidInterface) return JNI_FALSE;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
ALOGE("Bluetooth device address null");
return JNI_FALSE;
}
jint rType = reportType;
jint rId = reportId;
bt_status_t status = sBluetoothHidInterface->get_report(
(RawAddress*)addr, (bthh_report_type_t)rType, (uint8_t)rId, bufferSize);
jboolean ret = JNI_TRUE;
if (status != 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", __func__, reportType);
if (!sBluetoothHidInterface) return JNI_FALSE;
jbyte* 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);
jboolean ret = JNI_TRUE;
bt_status_t status = sBluetoothHidInterface->set_report(
(RawAddress*)addr, (bthh_report_type_t)rType, (char*)c_report);
if (status != 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", __func__);
jboolean ret = JNI_TRUE;
if (!sBluetoothHidInterface) return JNI_FALSE;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
ALOGE("Bluetooth device address null");
return JNI_FALSE;
}
const char* c_report = env->GetStringUTFChars(report, NULL);
bt_status_t status =
sBluetoothHidInterface->send_data((RawAddress*)addr, (char*)c_report);
if (status != BT_STATUS_SUCCESS) {
ALOGE("Failed set data, status: %d", status);
ret = JNI_FALSE;
}
env->ReleaseStringUTFChars(report, c_report);
env->ReleaseByteArrayElements(address, addr, 0);
return ret;
}
static jboolean getIdleTimeNative(JNIEnv* env, jobject object,
jbyteArray address) {
if (!sBluetoothHidInterface) return JNI_FALSE;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
ALOGE("%s: Bluetooth device address null", __func__);
return JNI_FALSE;
}
bt_status_t status = sBluetoothHidInterface->get_idle_time((RawAddress*)addr);
if (status != BT_STATUS_SUCCESS) {
ALOGE("%s: Failed get idle time, status: %d", __func__, status);
}
env->ReleaseByteArrayElements(address, addr, 0);
return status == BT_STATUS_SUCCESS ? JNI_TRUE : JNI_FALSE;
}
static jboolean setIdleTimeNative(JNIEnv* env, jobject object,
jbyteArray address, jbyte idle_time) {
if (!sBluetoothHidInterface) return JNI_FALSE;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (!addr) {
ALOGE("%s: Bluetooth device address null", __func__);
return JNI_FALSE;
}
bt_status_t status =
sBluetoothHidInterface->set_idle_time((RawAddress*)addr, idle_time);
if (status != BT_STATUS_SUCCESS) {
ALOGE("%s: Failed set idle time, status: %d", __func__, status);
}
env->ReleaseByteArrayElements(address, addr, 0);
return status == BT_STATUS_SUCCESS ? JNI_TRUE : JNI_FALSE;
}
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},
{"getIdleTimeNative", "([B)Z", (void*)getIdleTimeNative},
{"setIdleTimeNative", "([BB)Z", (void*)setIdleTimeNative},
};
int register_com_android_bluetooth_hid_host(JNIEnv* env) {
return jniRegisterNativeMethods(env,
"com/android/bluetooth/hid/HidHostService",
sMethods, NELEM(sMethods));
}
} // namespace android