| /* |
| * Copyright (C) 2016 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 "BluetoothHidDeviceServiceJni" |
| |
| #define LOG_NDEBUG 0 |
| |
| #include "android_runtime/AndroidRuntime.h" |
| #include "com_android_bluetooth.h" |
| #include "hardware/bt_hd.h" |
| #include "utils/Log.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_onInterruptData; |
| static jmethodID method_onVirtualCableUnplug; |
| |
| static const bthd_interface_t* sHiddIf = 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 application_state_callback(RawAddress* bd_addr, |
| bthd_application_state_t state) { |
| jboolean registered = JNI_FALSE; |
| |
| CallbackEnv sCallbackEnv(__func__); |
| |
| if (state == BTHD_APP_STATE_REGISTERED) { |
| registered = JNI_TRUE; |
| } |
| |
| ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), NULL); |
| |
| if (bd_addr) { |
| addr.reset(marshall_bda(bd_addr)); |
| if (!addr.get()) { |
| ALOGE("%s: failed to allocate storage for bt_addr", __FUNCTION__); |
| return; |
| } |
| } |
| |
| sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onApplicationStateChanged, |
| addr.get(), registered); |
| } |
| |
| static void connection_state_callback(RawAddress* bd_addr, |
| bthd_connection_state_t state) { |
| CallbackEnv sCallbackEnv(__func__); |
| |
| ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr)); |
| if (!addr.get()) { |
| ALOGE("%s: failed to allocate storage for bt_addr", __FUNCTION__); |
| return; |
| } |
| |
| sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectStateChanged, |
| addr.get(), (jint)state); |
| } |
| |
| static void get_report_callback(uint8_t type, uint8_t id, |
| uint16_t buffer_size) { |
| CallbackEnv sCallbackEnv(__func__); |
| |
| sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGetReport, type, id, |
| buffer_size); |
| } |
| |
| static void set_report_callback(uint8_t type, uint8_t id, uint16_t len, |
| uint8_t* p_data) { |
| CallbackEnv sCallbackEnv(__func__); |
| |
| ScopedLocalRef<jbyteArray> data(sCallbackEnv.get(), |
| sCallbackEnv->NewByteArray(len)); |
| if (!data.get()) { |
| ALOGE("%s: failed to allocate storage for report data", __FUNCTION__); |
| return; |
| } |
| sCallbackEnv->SetByteArrayRegion(data.get(), 0, len, (jbyte*)p_data); |
| |
| sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSetReport, (jbyte)type, |
| (jbyte)id, data.get()); |
| } |
| |
| static void set_protocol_callback(uint8_t protocol) { |
| CallbackEnv sCallbackEnv(__func__); |
| |
| sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSetProtocol, protocol); |
| } |
| |
| static void intr_data_callback(uint8_t report_id, uint16_t len, |
| uint8_t* p_data) { |
| CallbackEnv sCallbackEnv(__func__); |
| |
| ScopedLocalRef<jbyteArray> data(sCallbackEnv.get(), |
| sCallbackEnv->NewByteArray(len)); |
| if (!data.get()) { |
| ALOGE("%s: failed to allocate storage for report data", __FUNCTION__); |
| return; |
| } |
| sCallbackEnv->SetByteArrayRegion(data.get(), 0, len, (jbyte*)p_data); |
| |
| sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onInterruptData, |
| (jbyte)report_id, data.get()); |
| } |
| |
| static void vc_unplug_callback(void) { |
| CallbackEnv sCallbackEnv(__func__); |
| sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVirtualCableUnplug); |
| } |
| |
| 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_onInterruptData = env->GetMethodID(clazz, "onInterruptData", "(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__); |
| |
| if (!sHiddIf) { |
| ALOGE("%s: Failed to get the Bluetooth HIDD Interface", __func__); |
| return JNI_FALSE; |
| } |
| |
| 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; |
| |
| 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__); |
| |
| jboolean result = JNI_FALSE; |
| |
| if (!sHiddIf) { |
| ALOGE("%s: Failed to get the Bluetooth HIDD Interface", __func__); |
| return 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) { |
| jboolean result = JNI_FALSE; |
| |
| if (!sHiddIf) { |
| ALOGE("%s: Failed to get the Bluetooth HIDD Interface", __func__); |
| return 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); |
| |
| if (ret == BT_STATUS_SUCCESS) { |
| result = JNI_TRUE; |
| } |
| |
| free(buf); |
| } |
| |
| return result; |
| } |
| |
| static jboolean replyReportNative(JNIEnv* env, jobject thiz, jbyte type, |
| jbyte id, jbyteArray data) { |
| ALOGV("%s enter", __FUNCTION__); |
| |
| if (!sHiddIf) { |
| ALOGE("%s: Failed to get the Bluetooth HIDD Interface", __func__); |
| 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) { |
| ALOGE("%s: Failed to get the Bluetooth HIDD Interface", __func__); |
| 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) { |
| ALOGE("%s: Failed to get the Bluetooth HIDD Interface", __func__); |
| 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, jbyteArray address) { |
| ALOGV("%s enter", __FUNCTION__); |
| |
| if (!sHiddIf) { |
| ALOGE("%s: Failed to get the Bluetooth HIDD Interface", __func__); |
| return JNI_FALSE; |
| } |
| |
| jboolean result = JNI_FALSE; |
| |
| jbyte* addr = env->GetByteArrayElements(address, NULL); |
| if (!addr) { |
| ALOGE("Bluetooth device address null"); |
| return JNI_FALSE; |
| } |
| |
| bt_status_t ret = sHiddIf->connect((RawAddress*)addr); |
| |
| 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) { |
| ALOGE("%s: Failed to get the Bluetooth HIDD Interface", __func__); |
| 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", "([B)Z", (void*)connectNative}, |
| {"disconnectNative", "()Z", (void*)disconnectNative}, |
| }; |
| |
| int register_com_android_bluetooth_hid_device(JNIEnv* env) { |
| return jniRegisterNativeMethods( |
| env, "com/android/bluetooth/hid/HidDeviceNativeInterface", sMethods, |
| NELEM(sMethods)); |
| } |
| } // namespace android |