/*
* 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 "BluetoothA2dpServiceJni"
#define LOG_NDEBUG 0
#include "android_runtime/AndroidRuntime.h"
#include "com_android_bluetooth.h"
#include "hardware/bt_av.h"
#include "utils/Log.h"
#include <string.h>
#include <shared_mutex>
namespace android {
static jmethodID method_onConnectionStateChanged;
static jmethodID method_onAudioStateChanged;
static jmethodID method_onCodecConfigChanged;
static struct {
jclass clazz;
jmethodID constructor;
jmethodID getCodecType;
jmethodID getCodecPriority;
jmethodID getSampleRate;
jmethodID getBitsPerSample;
jmethodID getChannelMode;
jmethodID getCodecSpecific1;
jmethodID getCodecSpecific2;
jmethodID getCodecSpecific3;
jmethodID getCodecSpecific4;
} android_bluetooth_BluetoothCodecConfig;
static const btav_source_interface_t* sBluetoothA2dpInterface = nullptr;
static std::shared_timed_mutex interface_mutex;
static jobject mCallbacksObj = nullptr;
static std::shared_timed_mutex callbacks_mutex;
static void bta2dp_connection_state_callback(const RawAddress& bd_addr,
btav_connection_state_t state) {
ALOGI("%s", __func__);
std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
ALOGE("%s: Fail to new jbyteArray bd addr", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(
addr.get(), 0, sizeof(RawAddress),
reinterpret_cast<const jbyte*>(bd_addr.address));
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged,
addr.get(), (jint)state);
}
static void bta2dp_audio_state_callback(const RawAddress& bd_addr,
btav_audio_state_t state) {
ALOGI("%s", __func__);
std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
if (!addr.get()) {
ALOGE("%s: Fail to new jbyteArray bd addr", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(
addr.get(), 0, sizeof(RawAddress),
reinterpret_cast<const jbyte*>(bd_addr.address));
sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioStateChanged,
addr.get(), (jint)state);
}
static void bta2dp_audio_config_callback(
const RawAddress& bd_addr, btav_a2dp_codec_config_t codec_config,
std::vector<btav_a2dp_codec_config_t> codecs_local_capabilities,
std::vector<btav_a2dp_codec_config_t> codecs_selectable_capabilities) {
ALOGI("%s", __func__);
std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
CallbackEnv sCallbackEnv(__func__);
if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
jobject codecConfigObj = sCallbackEnv->NewObject(
android_bluetooth_BluetoothCodecConfig.clazz,
android_bluetooth_BluetoothCodecConfig.constructor,
(jint)codec_config.codec_type, (jint)codec_config.codec_priority,
(jint)codec_config.sample_rate, (jint)codec_config.bits_per_sample,
(jint)codec_config.channel_mode, (jlong)codec_config.codec_specific_1,
(jlong)codec_config.codec_specific_2,
(jlong)codec_config.codec_specific_3,
(jlong)codec_config.codec_specific_4);
jsize i = 0;
jobjectArray local_capabilities_array = sCallbackEnv->NewObjectArray(
(jsize)codecs_local_capabilities.size(),
android_bluetooth_BluetoothCodecConfig.clazz, nullptr);
for (auto const& cap : codecs_local_capabilities) {
jobject capObj = sCallbackEnv->NewObject(
android_bluetooth_BluetoothCodecConfig.clazz,
android_bluetooth_BluetoothCodecConfig.constructor,
(jint)cap.codec_type, (jint)cap.codec_priority, (jint)cap.sample_rate,
(jint)cap.bits_per_sample, (jint)cap.channel_mode,
(jlong)cap.codec_specific_1, (jlong)cap.codec_specific_2,
(jlong)cap.codec_specific_3, (jlong)cap.codec_specific_4);
sCallbackEnv->SetObjectArrayElement(local_capabilities_array, i++, capObj);
sCallbackEnv->DeleteLocalRef(capObj);
}
i = 0;
jobjectArray selectable_capabilities_array = sCallbackEnv->NewObjectArray(
(jsize)codecs_selectable_capabilities.size(),
android_bluetooth_BluetoothCodecConfig.clazz, nullptr);
for (auto const& cap : codecs_selectable_capabilities) {
jobject capObj = sCallbackEnv->NewObject(
android_bluetooth_BluetoothCodecConfig.clazz,
android_bluetooth_BluetoothCodecConfig.constructor,
(jint)cap.codec_type, (jint)cap.codec_priority, (jint)cap.sample_rate,
(jint)cap.bits_per_sample, (jint)cap.channel_mode,
(jlong)cap.codec_specific_1, (jlong)cap.codec_specific_2,
(jlong)cap.codec_specific_3, (jlong)cap.codec_specific_4);
sCallbackEnv->SetObjectArrayElement(selectable_capabilities_array, i++,
capObj);
sCallbackEnv->DeleteLocalRef(capObj);
}
ScopedLocalRef<jbyteArray> addr(
sCallbackEnv.get(), sCallbackEnv->NewByteArray(RawAddress::kLength));
if (!addr.get()) {
ALOGE("%s: Fail to new jbyteArray bd addr", __func__);
return;
}
sCallbackEnv->SetByteArrayRegion(
addr.get(), 0, RawAddress::kLength,
reinterpret_cast<const jbyte*>(bd_addr.address));
sCallbackEnv->CallVoidMethod(
mCallbacksObj, method_onCodecConfigChanged, addr.get(), codecConfigObj,
local_capabilities_array, selectable_capabilities_array);
}
static btav_source_callbacks_t sBluetoothA2dpCallbacks = {
sizeof(sBluetoothA2dpCallbacks), bta2dp_connection_state_callback,
bta2dp_audio_state_callback, bta2dp_audio_config_callback,
};
static void classInitNative(JNIEnv* env, jclass clazz) {
jclass jniBluetoothCodecConfigClass =
env->FindClass("android/bluetooth/BluetoothCodecConfig");
android_bluetooth_BluetoothCodecConfig.constructor =
env->GetMethodID(jniBluetoothCodecConfigClass, "<init>", "(IIIIIJJJJ)V");
android_bluetooth_BluetoothCodecConfig.getCodecType =
env->GetMethodID(jniBluetoothCodecConfigClass, "getCodecType", "()I");
android_bluetooth_BluetoothCodecConfig.getCodecPriority =
env->GetMethodID(jniBluetoothCodecConfigClass, "getCodecPriority", "()I");
android_bluetooth_BluetoothCodecConfig.getSampleRate =
env->GetMethodID(jniBluetoothCodecConfigClass, "getSampleRate", "()I");
android_bluetooth_BluetoothCodecConfig.getBitsPerSample =
env->GetMethodID(jniBluetoothCodecConfigClass, "getBitsPerSample", "()I");
android_bluetooth_BluetoothCodecConfig.getChannelMode =
env->GetMethodID(jniBluetoothCodecConfigClass, "getChannelMode", "()I");
android_bluetooth_BluetoothCodecConfig.getCodecSpecific1 = env->GetMethodID(
jniBluetoothCodecConfigClass, "getCodecSpecific1", "()J");
android_bluetooth_BluetoothCodecConfig.getCodecSpecific2 = env->GetMethodID(
jniBluetoothCodecConfigClass, "getCodecSpecific2", "()J");
android_bluetooth_BluetoothCodecConfig.getCodecSpecific3 = env->GetMethodID(
jniBluetoothCodecConfigClass, "getCodecSpecific3", "()J");
android_bluetooth_BluetoothCodecConfig.getCodecSpecific4 = env->GetMethodID(
jniBluetoothCodecConfigClass, "getCodecSpecific4", "()J");
method_onConnectionStateChanged =
env->GetMethodID(clazz, "onConnectionStateChanged", "([BI)V");
method_onAudioStateChanged =
env->GetMethodID(clazz, "onAudioStateChanged", "([BI)V");
method_onCodecConfigChanged =
env->GetMethodID(clazz, "onCodecConfigChanged",
"([BLandroid/bluetooth/BluetoothCodecConfig;"
"[Landroid/bluetooth/BluetoothCodecConfig;"
"[Landroid/bluetooth/BluetoothCodecConfig;)V");
ALOGI("%s: succeeds", __func__);
}
static std::vector<btav_a2dp_codec_config_t> prepareCodecPreferences(
JNIEnv* env, jobject object, jobjectArray codecConfigArray) {
std::vector<btav_a2dp_codec_config_t> codec_preferences;
int numConfigs = env->GetArrayLength(codecConfigArray);
for (int i = 0; i < numConfigs; i++) {
jobject jcodecConfig = env->GetObjectArrayElement(codecConfigArray, i);
if (jcodecConfig == nullptr) continue;
if (!env->IsInstanceOf(jcodecConfig,
android_bluetooth_BluetoothCodecConfig.clazz)) {
ALOGE("%s: Invalid BluetoothCodecConfig instance", __func__);
continue;
}
jint codecType = env->CallIntMethod(
jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecType);
jint codecPriority = env->CallIntMethod(
jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecPriority);
jint sampleRate = env->CallIntMethod(
jcodecConfig, android_bluetooth_BluetoothCodecConfig.getSampleRate);
jint bitsPerSample = env->CallIntMethod(
jcodecConfig, android_bluetooth_BluetoothCodecConfig.getBitsPerSample);
jint channelMode = env->CallIntMethod(
jcodecConfig, android_bluetooth_BluetoothCodecConfig.getChannelMode);
jlong codecSpecific1 = env->CallLongMethod(
jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecSpecific1);
jlong codecSpecific2 = env->CallLongMethod(
jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecSpecific2);
jlong codecSpecific3 = env->CallLongMethod(
jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecSpecific3);
jlong codecSpecific4 = env->CallLongMethod(
jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecSpecific4);
btav_a2dp_codec_config_t codec_config = {
.codec_type = static_cast<btav_a2dp_codec_index_t>(codecType),
.codec_priority =
static_cast<btav_a2dp_codec_priority_t>(codecPriority),
.sample_rate = static_cast<btav_a2dp_codec_sample_rate_t>(sampleRate),
.bits_per_sample =
static_cast<btav_a2dp_codec_bits_per_sample_t>(bitsPerSample),
.channel_mode =
static_cast<btav_a2dp_codec_channel_mode_t>(channelMode),
.codec_specific_1 = codecSpecific1,
.codec_specific_2 = codecSpecific2,
.codec_specific_3 = codecSpecific3,
.codec_specific_4 = codecSpecific4};
codec_preferences.push_back(codec_config);
}
return codec_preferences;
}
static void initNative(JNIEnv* env, jobject object,
jint maxConnectedAudioDevices,
jobjectArray codecConfigArray) {
std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
const bt_interface_t* btInf = getBluetoothInterface();
if (btInf == nullptr) {
ALOGE("%s: Bluetooth module is not loaded", __func__);
return;
}
if (sBluetoothA2dpInterface != nullptr) {
ALOGW("%s: Cleaning up A2DP Interface before initializing...", __func__);
sBluetoothA2dpInterface->cleanup();
sBluetoothA2dpInterface = nullptr;
}
if (mCallbacksObj != nullptr) {
ALOGW("%s: Cleaning up A2DP callback object", __func__);
env->DeleteGlobalRef(mCallbacksObj);
mCallbacksObj = nullptr;
}
if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) {
ALOGE("%s: Failed to allocate Global Ref for A2DP Callbacks", __func__);
return;
}
android_bluetooth_BluetoothCodecConfig.clazz = (jclass)env->NewGlobalRef(
env->FindClass("android/bluetooth/BluetoothCodecConfig"));
if (android_bluetooth_BluetoothCodecConfig.clazz == nullptr) {
ALOGE("%s: Failed to allocate Global Ref for BluetoothCodecConfig class",
__func__);
return;
}
sBluetoothA2dpInterface =
(btav_source_interface_t*)btInf->get_profile_interface(
BT_PROFILE_ADVANCED_AUDIO_ID);
if (sBluetoothA2dpInterface == nullptr) {
ALOGE("%s: Failed to get Bluetooth A2DP Interface", __func__);
return;
}
std::vector<btav_a2dp_codec_config_t> codec_priorities =
prepareCodecPreferences(env, object, codecConfigArray);
bt_status_t status = sBluetoothA2dpInterface->init(
&sBluetoothA2dpCallbacks, maxConnectedAudioDevices, codec_priorities);
if (status != BT_STATUS_SUCCESS) {
ALOGE("%s: Failed to initialize Bluetooth A2DP, status: %d", __func__,
status);
sBluetoothA2dpInterface = nullptr;
return;
}
}
static void cleanupNative(JNIEnv* env, jobject object) {
std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
const bt_interface_t* btInf = getBluetoothInterface();
if (btInf == nullptr) {
ALOGE("%s: Bluetooth module is not loaded", __func__);
return;
}
if (sBluetoothA2dpInterface != nullptr) {
sBluetoothA2dpInterface->cleanup();
sBluetoothA2dpInterface = nullptr;
}
env->DeleteGlobalRef(android_bluetooth_BluetoothCodecConfig.clazz);
android_bluetooth_BluetoothCodecConfig.clazz = nullptr;
if (mCallbacksObj != nullptr) {
env->DeleteGlobalRef(mCallbacksObj);
mCallbacksObj = nullptr;
}
}
static jboolean connectA2dpNative(JNIEnv* env, jobject object,
jbyteArray address) {
ALOGI("%s: sBluetoothA2dpInterface: %p", __func__, sBluetoothA2dpInterface);
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sBluetoothA2dpInterface) {
ALOGE("%s: Failed to get the Bluetooth A2DP Interface", __func__);
return JNI_FALSE;
}
jbyte* addr = env->GetByteArrayElements(address, nullptr);
if (!addr) {
jniThrowIOException(env, EINVAL);
return JNI_FALSE;
}
RawAddress bd_addr;
bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr));
bt_status_t status = sBluetoothA2dpInterface->connect(bd_addr);
if (status != BT_STATUS_SUCCESS) {
ALOGE("%s: Failed A2DP connection, status: %d", __func__, status);
}
env->ReleaseByteArrayElements(address, addr, 0);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
static jboolean disconnectA2dpNative(JNIEnv* env, jobject object,
jbyteArray address) {
ALOGI("%s: sBluetoothA2dpInterface: %p", __func__, sBluetoothA2dpInterface);
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sBluetoothA2dpInterface) {
ALOGE("%s: Failed to get the Bluetooth A2DP Interface", __func__);
return JNI_FALSE;
}
jbyte* addr = env->GetByteArrayElements(address, nullptr);
if (!addr) {
jniThrowIOException(env, EINVAL);
return JNI_FALSE;
}
RawAddress bd_addr;
bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr));
bt_status_t status = sBluetoothA2dpInterface->disconnect(bd_addr);
if (status != BT_STATUS_SUCCESS) {
ALOGE("%s: Failed A2DP disconnection, status: %d", __func__, status);
}
env->ReleaseByteArrayElements(address, addr, 0);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
static jboolean setActiveDeviceNative(JNIEnv* env, jobject object,
jbyteArray address) {
ALOGI("%s: sBluetoothA2dpInterface: %p", __func__, sBluetoothA2dpInterface);
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sBluetoothA2dpInterface) {
ALOGE("%s: Failed to get the Bluetooth A2DP Interface", __func__);
return JNI_FALSE;
}
jbyte* addr = env->GetByteArrayElements(address, nullptr);
RawAddress bd_addr = RawAddress::kEmpty;
if (addr) {
bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr));
}
bt_status_t status = sBluetoothA2dpInterface->set_active_device(bd_addr);
if (status != BT_STATUS_SUCCESS) {
ALOGE("%s: Failed A2DP set_active_device, status: %d", __func__, status);
}
env->ReleaseByteArrayElements(address, addr, 0);
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
static jboolean setCodecConfigPreferenceNative(JNIEnv* env, jobject object,
jbyteArray address,
jobjectArray codecConfigArray) {
ALOGI("%s: sBluetoothA2dpInterface: %p", __func__, sBluetoothA2dpInterface);
std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
if (!sBluetoothA2dpInterface) {
ALOGE("%s: Failed to get the Bluetooth A2DP Interface", __func__);
return JNI_FALSE;
}
jbyte* addr = env->GetByteArrayElements(address, nullptr);
if (!addr) {
jniThrowIOException(env, EINVAL);
return JNI_FALSE;
}
RawAddress bd_addr;
bd_addr.FromOctets(reinterpret_cast<const uint8_t*>(addr));
std::vector<btav_a2dp_codec_config_t> codec_preferences =
prepareCodecPreferences(env, object, codecConfigArray);
bt_status_t status =
sBluetoothA2dpInterface->config_codec(bd_addr, codec_preferences);
if (status != BT_STATUS_SUCCESS) {
ALOGE("%s: Failed codec configuration, 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},
{"initNative", "(I[Landroid/bluetooth/BluetoothCodecConfig;)V",
(void*)initNative},
{"cleanupNative", "()V", (void*)cleanupNative},
{"connectA2dpNative", "([B)Z", (void*)connectA2dpNative},
{"disconnectA2dpNative", "([B)Z", (void*)disconnectA2dpNative},
{"setActiveDeviceNative", "([B)Z", (void*)setActiveDeviceNative},
{"setCodecConfigPreferenceNative",
"([B[Landroid/bluetooth/BluetoothCodecConfig;)Z",
(void*)setCodecConfigPreferenceNative},
};
int register_com_android_bluetooth_a2dp(JNIEnv* env) {
return jniRegisterNativeMethods(
env, "com/android/bluetooth/a2dp/A2dpNativeInterface", sMethods,
NELEM(sMethods));
}
}