/* * 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)); } }