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