/*
* Copyright (C) 2015 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 "BluetoothSdpJni"
#define LOG_NDEBUG 0
#include "android_runtime/AndroidRuntime.h"
#include "com_android_bluetooth.h"
#include "hardware/bt_sdp.h"
#include "utils/Log.h"
#include <string.h>
using bluetooth::Uuid;
static const Uuid UUID_OBEX_OBJECT_PUSH = Uuid::From16Bit(0x1105);
static const Uuid UUID_PBAP_PSE = Uuid::From16Bit(0x112F);
static const Uuid UUID_MAP_MAS = Uuid::From16Bit(0x1132);
static const Uuid UUID_MAP_MNS = Uuid::From16Bit(0x1133);
static const Uuid UUID_SAP = Uuid::From16Bit(0x112D);
namespace android {
static jmethodID method_sdpRecordFoundCallback;
static jmethodID method_sdpMasRecordFoundCallback;
static jmethodID method_sdpMnsRecordFoundCallback;
static jmethodID method_sdpPseRecordFoundCallback;
static jmethodID method_sdpOppOpsRecordFoundCallback;
static jmethodID method_sdpSapsRecordFoundCallback;
static const btsdp_interface_t* sBluetoothSdpInterface = NULL;
static void sdp_search_callback(bt_status_t status, const RawAddress& bd_addr,
const Uuid& uuid_in, int record_size,
bluetooth_sdp_record* record);
btsdp_callbacks_t sBluetoothSdpCallbacks = {sizeof(sBluetoothSdpCallbacks),
sdp_search_callback};
static jobject sCallbacksObj = NULL;
static void initializeNative(JNIEnv* env, jobject object) {
const bt_interface_t* btInf = getBluetoothInterface();
if (btInf == NULL) {
ALOGE("Bluetooth module is not loaded");
return;
}
if (sBluetoothSdpInterface != NULL) {
ALOGW("Cleaning up Bluetooth SDP Interface before initializing...");
sBluetoothSdpInterface->deinit();
sBluetoothSdpInterface = NULL;
}
sBluetoothSdpInterface = (btsdp_interface_t*)btInf->get_profile_interface(
BT_PROFILE_SDP_CLIENT_ID);
if (sBluetoothSdpInterface == NULL) {
ALOGE("Error getting SDP client interface");
} else {
sBluetoothSdpInterface->init(&sBluetoothSdpCallbacks);
}
sCallbacksObj = env->NewGlobalRef(object);
}
static void classInitNative(JNIEnv* env, jclass clazz) {
/* generic SDP record (raw data)*/
method_sdpRecordFoundCallback =
env->GetMethodID(clazz, "sdpRecordFoundCallback", "(I[B[BI[B)V");
/* MAS SDP record*/
method_sdpMasRecordFoundCallback = env->GetMethodID(
clazz, "sdpMasRecordFoundCallback", "(I[B[BIIIIIILjava/lang/String;Z)V");
/* MNS SDP record*/
method_sdpMnsRecordFoundCallback = env->GetMethodID(
clazz, "sdpMnsRecordFoundCallback", "(I[B[BIIIILjava/lang/String;Z)V");
/* PBAP PSE record */
method_sdpPseRecordFoundCallback = env->GetMethodID(
clazz, "sdpPseRecordFoundCallback", "(I[B[BIIIIILjava/lang/String;Z)V");
/* OPP Server record */
method_sdpOppOpsRecordFoundCallback =
env->GetMethodID(clazz, "sdpOppOpsRecordFoundCallback",
"(I[B[BIIILjava/lang/String;[BZ)V");
/* SAP Server record */
method_sdpSapsRecordFoundCallback = env->GetMethodID(
clazz, "sdpSapsRecordFoundCallback", "(I[B[BIILjava/lang/String;Z)V");
}
static jboolean sdpSearchNative(JNIEnv* env, jobject obj, jbyteArray address,
jbyteArray uuidObj) {
ALOGD("%s", __func__);
if (!sBluetoothSdpInterface) return JNI_FALSE;
jbyte* addr = env->GetByteArrayElements(address, NULL);
if (addr == NULL) {
jniThrowIOException(env, EINVAL);
return JNI_FALSE;
}
jbyte* uuid = env->GetByteArrayElements(uuidObj, NULL);
if (!uuid) {
ALOGE("failed to get uuid");
env->ReleaseByteArrayElements(address, addr, 0);
return JNI_FALSE;
}
ALOGD("%s UUID %.*s", __func__, 16, (uint8_t*)uuid);
int ret = sBluetoothSdpInterface->sdp_search(
(RawAddress*)addr, Uuid::From128BitBE((uint8_t*)uuid));
if (ret != BT_STATUS_SUCCESS) {
ALOGE("SDP Search initialization failed: %d", ret);
}
if (addr) env->ReleaseByteArrayElements(address, addr, 0);
if (uuid) env->ReleaseByteArrayElements(uuidObj, uuid, 0);
return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
static void sdp_search_callback(bt_status_t status, const RawAddress& bd_addr,
const Uuid& uuid_in, int count,
bluetooth_sdp_record* records) {
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid()) return;
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) return;
ScopedLocalRef<jbyteArray> uuid(sCallbackEnv.get(),
sCallbackEnv->NewByteArray(sizeof(Uuid)));
if (!uuid.get()) return;
sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
(const jbyte*)&bd_addr);
sCallbackEnv->SetByteArrayRegion(uuid.get(), 0, sizeof(Uuid),
(const jbyte*)uuid_in.To128BitBE().data());
ALOGD("%s: Status is: %d, Record count: %d", __func__, status, count);
// Ensure we run the loop at least once, to also signal errors if they occur
for (int i = 0; i < count || i == 0; i++) {
bool more_results = (i < (count - 1)) ? true : false;
bluetooth_sdp_record* record = &records[i];
ScopedLocalRef<jstring> service_name(sCallbackEnv.get(), NULL);
if (record->hdr.service_name_length > 0) {
ALOGD("%s, ServiceName: %s", __func__, record->mas.hdr.service_name);
service_name.reset(
(jstring)sCallbackEnv->NewStringUTF(record->mas.hdr.service_name));
}
/* call the right callback according to the uuid*/
if (uuid_in == UUID_MAP_MAS) {
sCallbackEnv->CallVoidMethod(
sCallbacksObj, method_sdpMasRecordFoundCallback, (jint)status,
addr.get(), uuid.get(), (jint)record->mas.mas_instance_id,
(jint)record->mas.hdr.l2cap_psm,
(jint)record->mas.hdr.rfcomm_channel_number,
(jint)record->mas.hdr.profile_version,
(jint)record->mas.supported_features,
(jint)record->mas.supported_message_types, service_name.get(),
more_results);
} else if (uuid_in == UUID_MAP_MNS) {
sCallbackEnv->CallVoidMethod(
sCallbacksObj, method_sdpMnsRecordFoundCallback, (jint)status,
addr.get(), uuid.get(), (jint)record->mns.hdr.l2cap_psm,
(jint)record->mns.hdr.rfcomm_channel_number,
(jint)record->mns.hdr.profile_version,
(jint)record->mns.supported_features, service_name.get(),
more_results);
} else if (uuid_in == UUID_PBAP_PSE) {
sCallbackEnv->CallVoidMethod(
sCallbacksObj, method_sdpPseRecordFoundCallback, (jint)status,
addr.get(), uuid.get(), (jint)record->pse.hdr.l2cap_psm,
(jint)record->pse.hdr.rfcomm_channel_number,
(jint)record->pse.hdr.profile_version,
(jint)record->pse.supported_features,
(jint)record->pse.supported_repositories, service_name.get(),
more_results);
} else if (uuid_in == UUID_OBEX_OBJECT_PUSH) {
jint formats_list_size = record->ops.supported_formats_list_len;
ScopedLocalRef<jbyteArray> formats_list(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(formats_list_size));
if (!formats_list.get()) return;
sCallbackEnv->SetByteArrayRegion(
formats_list.get(), 0, formats_list_size,
(jbyte*)record->ops.supported_formats_list);
sCallbackEnv->CallVoidMethod(
sCallbacksObj, method_sdpOppOpsRecordFoundCallback, (jint)status,
addr.get(), uuid.get(), (jint)record->ops.hdr.l2cap_psm,
(jint)record->ops.hdr.rfcomm_channel_number,
(jint)record->ops.hdr.profile_version, service_name.get(),
formats_list.get(), more_results);
} else if (uuid_in == UUID_SAP) {
sCallbackEnv->CallVoidMethod(
sCallbacksObj, method_sdpSapsRecordFoundCallback, (jint)status,
addr.get(), uuid.get(), (jint)record->mas.hdr.rfcomm_channel_number,
(jint)record->mas.hdr.profile_version, service_name.get(),
more_results);
} else {
// we don't have a wrapper for this uuid, send as raw data
jint record_data_size = record->hdr.user1_ptr_len;
ScopedLocalRef<jbyteArray> record_data(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(record_data_size));
if (!record_data.get()) return;
sCallbackEnv->SetByteArrayRegion(record_data.get(), 0, record_data_size,
(jbyte*)record->hdr.user1_ptr);
sCallbackEnv->CallVoidMethod(sCallbacksObj, method_sdpRecordFoundCallback,
(jint)status, addr.get(), uuid.get(),
record_data_size, record_data.get());
}
} // End of for-loop
}
static jint sdpCreateMapMasRecordNative(JNIEnv* env, jobject obj,
jstring name_str, jint mas_id, jint scn,
jint l2cap_psm, jint version,
jint msg_types, jint features) {
ALOGD("%s", __func__);
if (!sBluetoothSdpInterface) return -1;
bluetooth_sdp_record record = {}; // Must be zero initialized
record.mas.hdr.type = SDP_TYPE_MAP_MAS;
const char* service_name = NULL;
if (name_str != NULL) {
service_name = env->GetStringUTFChars(name_str, NULL);
record.mas.hdr.service_name = (char*)service_name;
record.mas.hdr.service_name_length = strlen(service_name);
} else {
record.mas.hdr.service_name = NULL;
record.mas.hdr.service_name_length = 0;
}
record.mas.hdr.rfcomm_channel_number = scn;
record.mas.hdr.l2cap_psm = l2cap_psm;
record.mas.hdr.profile_version = version;
record.mas.mas_instance_id = mas_id;
record.mas.supported_features = features;
record.mas.supported_message_types = msg_types;
int handle = -1;
int ret = sBluetoothSdpInterface->create_sdp_record(&record, &handle);
if (ret != BT_STATUS_SUCCESS) {
ALOGE("SDP Create record failed: %d", ret);
} else {
ALOGD("SDP Create record success - handle: %d", handle);
}
if (service_name) env->ReleaseStringUTFChars(name_str, service_name);
return handle;
}
static jint sdpCreateMapMnsRecordNative(JNIEnv* env, jobject obj,
jstring name_str, jint scn,
jint l2cap_psm, jint version,
jint features) {
ALOGD("%s", __func__);
if (!sBluetoothSdpInterface) return -1;
bluetooth_sdp_record record = {}; // Must be zero initialized
record.mns.hdr.type = SDP_TYPE_MAP_MNS;
const char* service_name = NULL;
if (name_str != NULL) {
service_name = env->GetStringUTFChars(name_str, NULL);
record.mns.hdr.service_name = (char*)service_name;
record.mns.hdr.service_name_length = strlen(service_name);
} else {
record.mns.hdr.service_name = NULL;
record.mns.hdr.service_name_length = 0;
}
record.mns.hdr.rfcomm_channel_number = scn;
record.mns.hdr.l2cap_psm = l2cap_psm;
record.mns.hdr.profile_version = version;
record.mns.supported_features = features;
int handle = -1;
int ret = sBluetoothSdpInterface->create_sdp_record(&record, &handle);
if (ret != BT_STATUS_SUCCESS) {
ALOGE("SDP Create record failed: %d", ret);
} else {
ALOGD("SDP Create record success - handle: %d", handle);
}
if (service_name) env->ReleaseStringUTFChars(name_str, service_name);
return handle;
}
static jint sdpCreatePbapPseRecordNative(JNIEnv* env, jobject obj,
jstring name_str, jint scn,
jint l2cap_psm, jint version,
jint supported_repositories,
jint features) {
ALOGD("%s", __func__);
if (!sBluetoothSdpInterface) return -1;
bluetooth_sdp_record record = {}; // Must be zero initialized
record.pse.hdr.type = SDP_TYPE_PBAP_PSE;
const char* service_name = NULL;
if (name_str != NULL) {
service_name = env->GetStringUTFChars(name_str, NULL);
record.pse.hdr.service_name = (char*)service_name;
record.pse.hdr.service_name_length = strlen(service_name);
} else {
record.pse.hdr.service_name = NULL;
record.pse.hdr.service_name_length = 0;
}
record.pse.hdr.rfcomm_channel_number = scn;
record.pse.hdr.l2cap_psm = l2cap_psm;
record.pse.hdr.profile_version = version;
record.pse.supported_features = features;
record.pse.supported_repositories = supported_repositories;
int handle = -1;
int ret = sBluetoothSdpInterface->create_sdp_record(&record, &handle);
if (ret != BT_STATUS_SUCCESS) {
ALOGE("SDP Create record failed: %d", ret);
} else {
ALOGD("SDP Create record success - handle: %d", handle);
}
if (service_name) env->ReleaseStringUTFChars(name_str, service_name);
return handle;
}
static jint sdpCreateOppOpsRecordNative(JNIEnv* env, jobject obj,
jstring name_str, jint scn,
jint l2cap_psm, jint version,
jbyteArray supported_formats_list) {
ALOGD("%s", __func__);
if (!sBluetoothSdpInterface) return -1;
bluetooth_sdp_record record = {}; // Must be zero initialized
record.ops.hdr.type = SDP_TYPE_OPP_SERVER;
const char* service_name = NULL;
if (name_str != NULL) {
service_name = env->GetStringUTFChars(name_str, NULL);
record.ops.hdr.service_name = (char*)service_name;
record.ops.hdr.service_name_length = strlen(service_name);
} else {
record.ops.hdr.service_name = NULL;
record.ops.hdr.service_name_length = 0;
}
record.ops.hdr.rfcomm_channel_number = scn;
record.ops.hdr.l2cap_psm = l2cap_psm;
record.ops.hdr.profile_version = version;
int formats_list_len = 0;
jbyte* formats_list = env->GetByteArrayElements(supported_formats_list, NULL);
if (formats_list != NULL) {
formats_list_len = env->GetArrayLength(supported_formats_list);
if (formats_list_len > SDP_OPP_SUPPORTED_FORMATS_MAX_LENGTH) {
formats_list_len = SDP_OPP_SUPPORTED_FORMATS_MAX_LENGTH;
}
memcpy(record.ops.supported_formats_list, formats_list, formats_list_len);
}
record.ops.supported_formats_list_len = formats_list_len;
int handle = -1;
int ret = sBluetoothSdpInterface->create_sdp_record(&record, &handle);
if (ret != BT_STATUS_SUCCESS) {
ALOGE("SDP Create record failed: %d", ret);
} else {
ALOGD("SDP Create record success - handle: %d", handle);
}
if (service_name) env->ReleaseStringUTFChars(name_str, service_name);
if (formats_list)
env->ReleaseByteArrayElements(supported_formats_list, formats_list, 0);
return handle;
}
static jint sdpCreateSapsRecordNative(JNIEnv* env, jobject obj,
jstring name_str, jint scn,
jint version) {
ALOGD("%s", __func__);
if (!sBluetoothSdpInterface) return -1;
bluetooth_sdp_record record = {}; // Must be zero initialized
record.sap.hdr.type = SDP_TYPE_SAP_SERVER;
const char* service_name = NULL;
if (name_str != NULL) {
service_name = env->GetStringUTFChars(name_str, NULL);
record.mas.hdr.service_name = (char*)service_name;
record.mas.hdr.service_name_length = strlen(service_name);
} else {
record.mas.hdr.service_name = NULL;
record.mas.hdr.service_name_length = 0;
}
record.mas.hdr.rfcomm_channel_number = scn;
record.mas.hdr.profile_version = version;
int handle = -1;
int ret = sBluetoothSdpInterface->create_sdp_record(&record, &handle);
if (ret != BT_STATUS_SUCCESS) {
ALOGE("SDP Create record failed: %d", ret);
} else {
ALOGD("SDP Create record success - handle: %d", handle);
}
if (service_name) env->ReleaseStringUTFChars(name_str, service_name);
return handle;
}
static jboolean sdpRemoveSdpRecordNative(JNIEnv* env, jobject obj,
jint record_id) {
ALOGD("%s", __func__);
if (!sBluetoothSdpInterface) return false;
int ret = sBluetoothSdpInterface->remove_sdp_record(record_id);
if (ret != BT_STATUS_SUCCESS) {
ALOGE("SDP Remove record failed: %d", ret);
return false;
}
ALOGD("SDP Remove record success - handle: %d", record_id);
return true;
}
static void cleanupNative(JNIEnv* env, jobject object) {
const bt_interface_t* btInf = getBluetoothInterface();
if (btInf == NULL) {
ALOGE("Bluetooth module is not loaded");
return;
}
if (sBluetoothSdpInterface != NULL) {
ALOGW("Cleaning up Bluetooth SDP Interface...");
sBluetoothSdpInterface->deinit();
sBluetoothSdpInterface = NULL;
}
if (sCallbacksObj != NULL) {
ALOGW("Cleaning up Bluetooth SDP object");
env->DeleteGlobalRef(sCallbacksObj);
sCallbacksObj = NULL;
}
}
static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"classInitNative", "()V", (void*)classInitNative},
{"initializeNative", "()V", (void*)initializeNative},
{"cleanupNative", "()V", (void*)cleanupNative},
{"sdpSearchNative", "([B[B)Z", (void*)sdpSearchNative},
{"sdpCreateMapMasRecordNative", "(Ljava/lang/String;IIIIII)I",
(void*)sdpCreateMapMasRecordNative},
{"sdpCreateMapMnsRecordNative", "(Ljava/lang/String;IIII)I",
(void*)sdpCreateMapMnsRecordNative},
{"sdpCreatePbapPseRecordNative", "(Ljava/lang/String;IIIII)I",
(void*)sdpCreatePbapPseRecordNative},
{"sdpCreateOppOpsRecordNative", "(Ljava/lang/String;III[B)I",
(void*)sdpCreateOppOpsRecordNative},
{"sdpCreateSapsRecordNative", "(Ljava/lang/String;II)I",
(void*)sdpCreateSapsRecordNative},
{"sdpRemoveSdpRecordNative", "(I)Z", (void*)sdpRemoveSdpRecordNative}};
int register_com_android_bluetooth_sdp(JNIEnv* env) {
return jniRegisterNativeMethods(env, "com/android/bluetooth/sdp/SdpManager",
sMethods, NELEM(sMethods));
}
}