| /* Copyright 2019 HIMSA II K/S - www.himsa.com |
| * Represented by EHIMA - www.ehima.com |
| * |
| * 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 "BluetoothLeAudioServiceJni" |
| |
| #include <hardware/bluetooth.h> |
| |
| #include <array> |
| #include <optional> |
| #include <shared_mutex> |
| |
| #include "com_android_bluetooth.h" |
| #include "hardware/bt_le_audio.h" |
| |
| using bluetooth::le_audio::BroadcastId; |
| using bluetooth::le_audio::BroadcastState; |
| using bluetooth::le_audio::btle_audio_codec_config_t; |
| using bluetooth::le_audio::btle_audio_codec_index_t; |
| using bluetooth::le_audio::ConnectionState; |
| using bluetooth::le_audio::GroupNodeStatus; |
| using bluetooth::le_audio::GroupStatus; |
| using bluetooth::le_audio::LeAudioBroadcasterCallbacks; |
| using bluetooth::le_audio::LeAudioBroadcasterInterface; |
| using bluetooth::le_audio::LeAudioClientCallbacks; |
| using bluetooth::le_audio::LeAudioClientInterface; |
| |
| namespace android { |
| static jmethodID method_onInitialized; |
| static jmethodID method_onConnectionStateChanged; |
| static jmethodID method_onGroupStatus; |
| static jmethodID method_onGroupNodeStatus; |
| static jmethodID method_onAudioConf; |
| static jmethodID method_onSinkAudioLocationAvailable; |
| static jmethodID method_onAudioLocalCodecCapabilities; |
| static jmethodID method_onAudioGroupCodecConf; |
| static jmethodID method_onHealthBasedRecommendationAction; |
| static jmethodID method_onHealthBasedGroupRecommendationAction; |
| |
| static struct { |
| jclass clazz; |
| jmethodID constructor; |
| jmethodID getCodecType; |
| } android_bluetooth_BluetoothLeAudioCodecConfig; |
| |
| static struct { |
| jclass clazz; |
| jmethodID constructor; |
| } android_bluetooth_BluetoothLeAudioCodecConfigMetadata; |
| |
| static struct { |
| jclass clazz; |
| jmethodID constructor; |
| jmethodID add; |
| } java_util_ArrayList; |
| |
| static struct { |
| jclass clazz; |
| jmethodID constructor; |
| } android_bluetooth_BluetoothLeBroadcastChannel; |
| |
| static struct { |
| jclass clazz; |
| jmethodID constructor; |
| } android_bluetooth_BluetoothLeBroadcastSubgroup; |
| |
| static struct { |
| jclass clazz; |
| jmethodID constructor; |
| } android_bluetooth_BluetoothLeAudioContentMetadata; |
| |
| static struct { |
| jclass clazz; |
| jmethodID constructor; |
| } android_bluetooth_BluetoothLeBroadcastMetadata; |
| |
| static struct { |
| jclass clazz; |
| jmethodID constructor; |
| } android_bluetooth_BluetoothDevice; |
| |
| static LeAudioClientInterface* sLeAudioClientInterface = nullptr; |
| static std::shared_timed_mutex interface_mutex; |
| |
| static jobject mCallbacksObj = nullptr; |
| static std::shared_timed_mutex callbacks_mutex; |
| |
| jobject prepareCodecConfigObj(JNIEnv* env, |
| btle_audio_codec_config_t codecConfig) { |
| jobject codecConfigObj = |
| env->NewObject(android_bluetooth_BluetoothLeAudioCodecConfig.clazz, |
| android_bluetooth_BluetoothLeAudioCodecConfig.constructor, |
| (jint)codecConfig.codec_type, 0, 0, 0, 0, 0, 0, 0, 0); |
| return codecConfigObj; |
| } |
| |
| jobjectArray prepareArrayOfCodecConfigs( |
| JNIEnv* env, std::vector<btle_audio_codec_config_t> codecConfigs) { |
| jsize i = 0; |
| jobjectArray CodecConfigArray = env->NewObjectArray( |
| (jsize)codecConfigs.size(), |
| android_bluetooth_BluetoothLeAudioCodecConfig.clazz, nullptr); |
| |
| for (auto const& cap : codecConfigs) { |
| jobject Obj = prepareCodecConfigObj(env, cap); |
| |
| env->SetObjectArrayElement(CodecConfigArray, i++, Obj); |
| env->DeleteLocalRef(Obj); |
| } |
| |
| return CodecConfigArray; |
| } |
| |
| class LeAudioClientCallbacksImpl : public LeAudioClientCallbacks { |
| public: |
| ~LeAudioClientCallbacksImpl() = default; |
| |
| void OnInitialized(void) override { |
| LOG(INFO) << __func__; |
| std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); |
| CallbackEnv sCallbackEnv(__func__); |
| if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; |
| sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onInitialized); |
| } |
| |
| void OnConnectionState(ConnectionState state, |
| const RawAddress& bd_addr) override { |
| LOG(INFO) << __func__ << ", state:" << int(state) |
| << ", addr: " << bd_addr.ToRedactedStringForLogging(); |
| |
| 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()) { |
| LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state"; |
| return; |
| } |
| |
| sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), |
| (jbyte*)&bd_addr); |
| sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged, |
| (jint)state, addr.get()); |
| } |
| |
| void OnGroupStatus(int group_id, GroupStatus group_status) override { |
| LOG(INFO) << __func__; |
| |
| std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); |
| CallbackEnv sCallbackEnv(__func__); |
| if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; |
| |
| sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGroupStatus, |
| (jint)group_id, (jint)group_status); |
| } |
| |
| void OnGroupNodeStatus(const RawAddress& bd_addr, int group_id, |
| GroupNodeStatus node_status) override { |
| LOG(INFO) << __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()) { |
| LOG(ERROR) << "Failed to new jbyteArray bd addr for group status"; |
| return; |
| } |
| |
| sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), |
| (jbyte*)&bd_addr); |
| sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGroupNodeStatus, |
| addr.get(), (jint)group_id, (jint)node_status); |
| } |
| |
| void OnAudioConf(uint8_t direction, int group_id, |
| uint32_t sink_audio_location, uint32_t source_audio_location, |
| uint16_t avail_cont) override { |
| LOG(INFO) << __func__; |
| |
| std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); |
| CallbackEnv sCallbackEnv(__func__); |
| if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; |
| |
| sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioConf, |
| (jint)direction, (jint)group_id, |
| (jint)sink_audio_location, |
| (jint)source_audio_location, (jint)avail_cont); |
| } |
| |
| void OnSinkAudioLocationAvailable(const RawAddress& bd_addr, |
| uint32_t sink_audio_location) override { |
| LOG(INFO) << __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()) { |
| LOG(ERROR) << "Failed to new jbyteArray bd addr for group status"; |
| return; |
| } |
| |
| sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), |
| (jbyte*)&bd_addr); |
| sCallbackEnv->CallVoidMethod(mCallbacksObj, |
| method_onSinkAudioLocationAvailable, |
| addr.get(), (jint)sink_audio_location); |
| } |
| |
| void OnAudioLocalCodecCapabilities( |
| std::vector<btle_audio_codec_config_t> local_input_capa_codec_conf, |
| std::vector<btle_audio_codec_config_t> local_output_capa_codec_conf) |
| override { |
| LOG(INFO) << __func__; |
| |
| std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); |
| CallbackEnv sCallbackEnv(__func__); |
| if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; |
| |
| jobject localInputCapCodecConfigArray = prepareArrayOfCodecConfigs( |
| sCallbackEnv.get(), local_input_capa_codec_conf); |
| |
| jobject localOutputCapCodecConfigArray = prepareArrayOfCodecConfigs( |
| sCallbackEnv.get(), local_output_capa_codec_conf); |
| |
| sCallbackEnv->CallVoidMethod( |
| mCallbacksObj, method_onAudioLocalCodecCapabilities, |
| localInputCapCodecConfigArray, localOutputCapCodecConfigArray); |
| } |
| |
| void OnAudioGroupCodecConf( |
| int group_id, btle_audio_codec_config_t input_codec_conf, |
| btle_audio_codec_config_t output_codec_conf, |
| std::vector<btle_audio_codec_config_t> input_selectable_codec_conf, |
| std::vector<btle_audio_codec_config_t> output_selectable_codec_conf) |
| override { |
| LOG(INFO) << __func__; |
| |
| std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); |
| CallbackEnv sCallbackEnv(__func__); |
| if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; |
| |
| jobject inputCodecConfigObj = |
| prepareCodecConfigObj(sCallbackEnv.get(), input_codec_conf); |
| jobject outputCodecConfigObj = |
| prepareCodecConfigObj(sCallbackEnv.get(), input_codec_conf); |
| jobject inputSelectableCodecConfigArray = prepareArrayOfCodecConfigs( |
| sCallbackEnv.get(), input_selectable_codec_conf); |
| jobject outputSelectableCodecConfigArray = prepareArrayOfCodecConfigs( |
| sCallbackEnv.get(), output_selectable_codec_conf); |
| |
| sCallbackEnv->CallVoidMethod( |
| mCallbacksObj, method_onAudioGroupCodecConf, (jint)group_id, |
| inputCodecConfigObj, outputCodecConfigObj, |
| inputSelectableCodecConfigArray, outputSelectableCodecConfigArray); |
| } |
| |
| void OnHealthBasedRecommendationAction( |
| const RawAddress& bd_addr, |
| bluetooth::le_audio::LeAudioHealthBasedAction action) override { |
| LOG(INFO) << __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()) { |
| LOG(ERROR) << "Failed to new jbyteArray bd addr for group status"; |
| return; |
| } |
| |
| sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), |
| (jbyte*)&bd_addr); |
| sCallbackEnv->CallVoidMethod(mCallbacksObj, |
| method_onHealthBasedRecommendationAction, |
| addr.get(), (jint)action); |
| } |
| |
| void OnHealthBasedGroupRecommendationAction( |
| int group_id, |
| bluetooth::le_audio::LeAudioHealthBasedAction action) override { |
| LOG(INFO) << __func__; |
| |
| std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); |
| CallbackEnv sCallbackEnv(__func__); |
| if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; |
| |
| sCallbackEnv->CallVoidMethod(mCallbacksObj, |
| method_onHealthBasedGroupRecommendationAction, |
| (jint)group_id, (jint)action); |
| } |
| }; |
| |
| static LeAudioClientCallbacksImpl sLeAudioClientCallbacks; |
| |
| static void classInitNative(JNIEnv* env, jclass clazz) { |
| jclass jniBluetoothLeAudioCodecConfigClass = |
| env->FindClass("android/bluetooth/BluetoothLeAudioCodecConfig"); |
| android_bluetooth_BluetoothLeAudioCodecConfig.constructor = env->GetMethodID( |
| jniBluetoothLeAudioCodecConfigClass, "<init>", "(IIIIIIIII)V"); |
| android_bluetooth_BluetoothLeAudioCodecConfig.getCodecType = env->GetMethodID( |
| jniBluetoothLeAudioCodecConfigClass, "getCodecType", "()I"); |
| |
| method_onGroupStatus = env->GetMethodID(clazz, "onGroupStatus", "(II)V"); |
| method_onGroupNodeStatus = |
| env->GetMethodID(clazz, "onGroupNodeStatus", "([BII)V"); |
| method_onAudioConf = env->GetMethodID(clazz, "onAudioConf", "(IIIII)V"); |
| method_onSinkAudioLocationAvailable = |
| env->GetMethodID(clazz, "onSinkAudioLocationAvailable", "([BI)V"); |
| method_onInitialized = env->GetMethodID(clazz, "onInitialized", "()V"); |
| method_onConnectionStateChanged = |
| env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V"); |
| method_onAudioLocalCodecCapabilities = |
| env->GetMethodID(clazz, "onAudioLocalCodecCapabilities", |
| "([Landroid/bluetooth/BluetoothLeAudioCodecConfig;" |
| "[Landroid/bluetooth/BluetoothLeAudioCodecConfig;)V"); |
| method_onAudioGroupCodecConf = |
| env->GetMethodID(clazz, "onAudioGroupCodecConf", |
| "(ILandroid/bluetooth/BluetoothLeAudioCodecConfig;" |
| "Landroid/bluetooth/BluetoothLeAudioCodecConfig;" |
| "[Landroid/bluetooth/BluetoothLeAudioCodecConfig;" |
| "[Landroid/bluetooth/BluetoothLeAudioCodecConfig;)V"); |
| method_onHealthBasedRecommendationAction = |
| env->GetMethodID(clazz, "onHealthBasedRecommendationAction", "([BI)V"); |
| method_onHealthBasedGroupRecommendationAction = env->GetMethodID( |
| clazz, "onHealthBasedGroupRecommendationAction", "(II)V"); |
| } |
| |
| std::vector<btle_audio_codec_config_t> prepareCodecPreferences( |
| JNIEnv* env, jobject object, jobjectArray codecConfigArray) { |
| std::vector<btle_audio_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_BluetoothLeAudioCodecConfig.clazz)) { |
| ALOGE("%s: Invalid BluetoothLeAudioCodecConfig instance", __func__); |
| continue; |
| } |
| jint codecType = env->CallIntMethod( |
| jcodecConfig, |
| android_bluetooth_BluetoothLeAudioCodecConfig.getCodecType); |
| |
| btle_audio_codec_config_t codec_config = { |
| .codec_type = static_cast<btle_audio_codec_index_t>(codecType)}; |
| |
| codec_preferences.push_back(codec_config); |
| } |
| return codec_preferences; |
| } |
| |
| static void initNative(JNIEnv* env, jobject object, |
| jobjectArray codecOffloadingArray) { |
| 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) { |
| LOG(ERROR) << "Bluetooth module is not loaded"; |
| return; |
| } |
| |
| if (mCallbacksObj != nullptr) { |
| LOG(INFO) << "Cleaning up LeAudio callback object"; |
| env->DeleteGlobalRef(mCallbacksObj); |
| mCallbacksObj = nullptr; |
| } |
| |
| if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) { |
| LOG(ERROR) << "Failed to allocate Global Ref for LeAudio Callbacks"; |
| return; |
| } |
| |
| android_bluetooth_BluetoothLeAudioCodecConfig.clazz = |
| (jclass)env->NewGlobalRef( |
| env->FindClass("android/bluetooth/BluetoothLeAudioCodecConfig")); |
| if (android_bluetooth_BluetoothLeAudioCodecConfig.clazz == nullptr) { |
| LOG(ERROR) << "Failed to allocate Global Ref for " |
| "BluetoothLeAudioCodecConfig class"; |
| return; |
| } |
| |
| sLeAudioClientInterface = |
| (LeAudioClientInterface*)btInf->get_profile_interface( |
| BT_PROFILE_LE_AUDIO_ID); |
| if (sLeAudioClientInterface == nullptr) { |
| LOG(ERROR) << "Failed to get Bluetooth LeAudio Interface"; |
| return; |
| } |
| |
| std::vector<btle_audio_codec_config_t> codec_offloading = |
| prepareCodecPreferences(env, object, codecOffloadingArray); |
| |
| sLeAudioClientInterface->Initialize(&sLeAudioClientCallbacks, |
| codec_offloading); |
| } |
| |
| 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) { |
| LOG(ERROR) << "Bluetooth module is not loaded"; |
| return; |
| } |
| |
| if (sLeAudioClientInterface != nullptr) { |
| sLeAudioClientInterface->Cleanup(); |
| sLeAudioClientInterface = nullptr; |
| } |
| |
| env->DeleteGlobalRef(android_bluetooth_BluetoothLeAudioCodecConfig.clazz); |
| android_bluetooth_BluetoothLeAudioCodecConfig.clazz = nullptr; |
| |
| if (mCallbacksObj != nullptr) { |
| env->DeleteGlobalRef(mCallbacksObj); |
| mCallbacksObj = nullptr; |
| } |
| } |
| |
| static jboolean connectLeAudioNative(JNIEnv* env, jobject object, |
| jbyteArray address) { |
| LOG(INFO) << __func__; |
| std::shared_lock<std::shared_timed_mutex> lock(interface_mutex); |
| if (!sLeAudioClientInterface) return JNI_FALSE; |
| |
| jbyte* addr = env->GetByteArrayElements(address, nullptr); |
| if (!addr) { |
| jniThrowIOException(env, EINVAL); |
| return JNI_FALSE; |
| } |
| |
| RawAddress* tmpraw = (RawAddress*)addr; |
| sLeAudioClientInterface->Connect(*tmpraw); |
| env->ReleaseByteArrayElements(address, addr, 0); |
| return JNI_TRUE; |
| } |
| |
| static jboolean disconnectLeAudioNative(JNIEnv* env, jobject object, |
| jbyteArray address) { |
| LOG(INFO) << __func__; |
| std::shared_lock<std::shared_timed_mutex> lock(interface_mutex); |
| if (!sLeAudioClientInterface) return JNI_FALSE; |
| |
| jbyte* addr = env->GetByteArrayElements(address, nullptr); |
| if (!addr) { |
| jniThrowIOException(env, EINVAL); |
| return JNI_FALSE; |
| } |
| |
| RawAddress* tmpraw = (RawAddress*)addr; |
| sLeAudioClientInterface->Disconnect(*tmpraw); |
| env->ReleaseByteArrayElements(address, addr, 0); |
| return JNI_TRUE; |
| } |
| |
| static jboolean setEnableStateNative(JNIEnv* env, jobject object, |
| jbyteArray address, jboolean enabled) { |
| std::shared_lock<std::shared_timed_mutex> lock(interface_mutex); |
| jbyte* addr = env->GetByteArrayElements(address, nullptr); |
| |
| if (!sLeAudioClientInterface) { |
| LOG(ERROR) << __func__ << ": Failed to get the Bluetooth LeAudio Interface"; |
| return JNI_FALSE; |
| } |
| |
| if (!addr) { |
| jniThrowIOException(env, EINVAL); |
| return JNI_FALSE; |
| } |
| |
| RawAddress* tmpraw = (RawAddress*)addr; |
| sLeAudioClientInterface->SetEnableState(*tmpraw, enabled); |
| env->ReleaseByteArrayElements(address, addr, 0); |
| return JNI_TRUE; |
| } |
| |
| static jboolean groupAddNodeNative(JNIEnv* env, jobject object, jint group_id, |
| jbyteArray address) { |
| std::shared_lock<std::shared_timed_mutex> lock(interface_mutex); |
| jbyte* addr = env->GetByteArrayElements(address, nullptr); |
| |
| if (!sLeAudioClientInterface) { |
| LOG(ERROR) << __func__ << ": Failed to get the Bluetooth LeAudio Interface"; |
| return JNI_FALSE; |
| } |
| |
| if (!addr) { |
| jniThrowIOException(env, EINVAL); |
| return JNI_FALSE; |
| } |
| |
| RawAddress* tmpraw = (RawAddress*)addr; |
| sLeAudioClientInterface->GroupAddNode(group_id, *tmpraw); |
| env->ReleaseByteArrayElements(address, addr, 0); |
| |
| return JNI_TRUE; |
| } |
| |
| static jboolean groupRemoveNodeNative(JNIEnv* env, jobject object, |
| jint group_id, jbyteArray address) { |
| std::shared_lock<std::shared_timed_mutex> lock(interface_mutex); |
| if (!sLeAudioClientInterface) { |
| LOG(ERROR) << __func__ << ": Failed to get the Bluetooth LeAudio Interface"; |
| return JNI_FALSE; |
| } |
| |
| jbyte* addr = env->GetByteArrayElements(address, nullptr); |
| if (!addr) { |
| jniThrowIOException(env, EINVAL); |
| return JNI_FALSE; |
| } |
| |
| RawAddress* tmpraw = (RawAddress*)addr; |
| sLeAudioClientInterface->GroupRemoveNode(group_id, *tmpraw); |
| env->ReleaseByteArrayElements(address, addr, 0); |
| return JNI_TRUE; |
| } |
| |
| static void groupSetActiveNative(JNIEnv* env, jobject object, jint group_id) { |
| LOG(INFO) << __func__; |
| std::shared_lock<std::shared_timed_mutex> lock(interface_mutex); |
| |
| if (!sLeAudioClientInterface) { |
| LOG(ERROR) << __func__ << ": Failed to get the Bluetooth LeAudio Interface"; |
| return; |
| } |
| |
| sLeAudioClientInterface->GroupSetActive(group_id); |
| } |
| |
| static void setCodecConfigPreferenceNative(JNIEnv* env, jobject object, |
| jint group_id, |
| jobject inputCodecConfig, |
| jobject outputCodecConfig) { |
| std::shared_lock<std::shared_timed_mutex> lock(interface_mutex); |
| |
| if (!env->IsInstanceOf(inputCodecConfig, |
| android_bluetooth_BluetoothLeAudioCodecConfig.clazz) || |
| !env->IsInstanceOf(outputCodecConfig, |
| android_bluetooth_BluetoothLeAudioCodecConfig.clazz)) { |
| ALOGE("%s: Invalid BluetoothLeAudioCodecConfig instance", __func__); |
| return; |
| } |
| |
| jint inputCodecType = env->CallIntMethod( |
| inputCodecConfig, |
| android_bluetooth_BluetoothLeAudioCodecConfig.getCodecType); |
| |
| btle_audio_codec_config_t input_codec_config = { |
| .codec_type = static_cast<btle_audio_codec_index_t>(inputCodecType)}; |
| |
| jint outputCodecType = env->CallIntMethod( |
| outputCodecConfig, |
| android_bluetooth_BluetoothLeAudioCodecConfig.getCodecType); |
| |
| btle_audio_codec_config_t output_codec_config = { |
| .codec_type = static_cast<btle_audio_codec_index_t>(outputCodecType)}; |
| |
| sLeAudioClientInterface->SetCodecConfigPreference( |
| group_id, input_codec_config, output_codec_config); |
| } |
| |
| static void setCcidInformationNative(JNIEnv* env, jobject object, jint ccid, |
| jint contextType) { |
| std::shared_lock<std::shared_timed_mutex> lock(interface_mutex); |
| if (!sLeAudioClientInterface) { |
| LOG(ERROR) << __func__ << ": Failed to get the Bluetooth LeAudio Interface"; |
| return; |
| } |
| |
| sLeAudioClientInterface->SetCcidInformation(ccid, contextType); |
| } |
| |
| static void setInCallNative(JNIEnv* env, jobject object, jboolean inCall) { |
| std::shared_lock<std::shared_timed_mutex> lock(interface_mutex); |
| if (!sLeAudioClientInterface) { |
| LOG(ERROR) << __func__ << ": Failed to get the Bluetooth LeAudio Interface"; |
| return; |
| } |
| |
| sLeAudioClientInterface->SetInCall(inCall); |
| } |
| |
| static void sendAudioProfilePreferencesNative( |
| JNIEnv* env, jint groupId, jboolean isOutputPreferenceLeAudio, |
| jboolean isDuplexPreferenceLeAudio) { |
| std::shared_lock<std::shared_timed_mutex> lock(interface_mutex); |
| if (!sLeAudioClientInterface) { |
| LOG(ERROR) << __func__ << ": Failed to get the Bluetooth LeAudio Interface"; |
| return; |
| } |
| |
| sLeAudioClientInterface->SendAudioProfilePreferences( |
| groupId, isOutputPreferenceLeAudio, isDuplexPreferenceLeAudio); |
| } |
| |
| static JNINativeMethod sMethods[] = { |
| {"classInitNative", "()V", (void*)classInitNative}, |
| {"initNative", "([Landroid/bluetooth/BluetoothLeAudioCodecConfig;)V", |
| (void*)initNative}, |
| {"cleanupNative", "()V", (void*)cleanupNative}, |
| {"connectLeAudioNative", "([B)Z", (void*)connectLeAudioNative}, |
| {"disconnectLeAudioNative", "([B)Z", (void*)disconnectLeAudioNative}, |
| {"setEnableStateNative", "([BZ)Z", (void*)setEnableStateNative}, |
| {"groupAddNodeNative", "(I[B)Z", (void*)groupAddNodeNative}, |
| {"groupRemoveNodeNative", "(I[B)Z", (void*)groupRemoveNodeNative}, |
| {"groupSetActiveNative", "(I)V", (void*)groupSetActiveNative}, |
| {"setCodecConfigPreferenceNative", |
| "(ILandroid/bluetooth/BluetoothLeAudioCodecConfig;Landroid/bluetooth/" |
| "BluetoothLeAudioCodecConfig;)V", |
| (void*)setCodecConfigPreferenceNative}, |
| {"setCcidInformationNative", "(II)V", (void*)setCcidInformationNative}, |
| {"setInCallNative", "(Z)V", (void*)setInCallNative}, |
| {"sendAudioProfilePreferencesNative", "(IZZ)V", |
| (void*)sendAudioProfilePreferencesNative}, |
| }; |
| |
| /* Le Audio Broadcaster */ |
| static jmethodID method_onBroadcastCreated; |
| static jmethodID method_onBroadcastDestroyed; |
| static jmethodID method_onBroadcastStateChanged; |
| static jmethodID method_onBroadcastMetadataChanged; |
| |
| static LeAudioBroadcasterInterface* sLeAudioBroadcasterInterface = nullptr; |
| static std::shared_timed_mutex sBroadcasterInterfaceMutex; |
| |
| static jobject sBroadcasterCallbacksObj = nullptr; |
| static std::shared_timed_mutex sBroadcasterCallbacksMutex; |
| |
| #define VEC_UINT8_TO_UINT32(vec) \ |
| ((vec.data()[3] << 24) + (vec.data()[2] << 16) + (vec.data()[1] << 8) + \ |
| vec.data()[0]) |
| |
| #define VEC_UINT8_TO_UINT16(vec) (((vec).data()[1] << 8) + ((vec).data()[0])) |
| |
| size_t RawPacketSize(const std::map<uint8_t, std::vector<uint8_t>>& values) { |
| size_t bytes = 0; |
| for (auto const& value : values) { |
| bytes += (/* ltv_len + ltv_type */ 2 + value.second.size()); |
| } |
| return bytes; |
| } |
| |
| jbyteArray prepareRawLtvArray( |
| JNIEnv* env, const std::map<uint8_t, std::vector<uint8_t>>& metadata) { |
| auto raw_meta_size = RawPacketSize(metadata); |
| |
| jbyteArray raw_metadata = env->NewByteArray(raw_meta_size); |
| if (!raw_metadata) { |
| LOG(ERROR) << "Failed to create new jbyteArray for raw LTV"; |
| return nullptr; |
| } |
| |
| jsize offset = 0; |
| for (auto const& kv_pair : metadata) { |
| // Length |
| const jbyte ltv_sz = kv_pair.second.size() + 1; |
| env->SetByteArrayRegion(raw_metadata, offset, 1, <v_sz); |
| offset += 1; |
| // Type |
| env->SetByteArrayRegion(raw_metadata, offset, 1, |
| (const jbyte*)&kv_pair.first); |
| offset += 1; |
| // Value |
| env->SetByteArrayRegion(raw_metadata, offset, kv_pair.second.size(), |
| (const jbyte*)kv_pair.second.data()); |
| offset += kv_pair.second.size(); |
| } |
| |
| return raw_metadata; |
| } |
| |
| static jlong getAudioLocationOrDefault( |
| const std::map<uint8_t, std::vector<uint8_t>>& metadata, |
| jlong default_location) { |
| if (metadata.count( |
| bluetooth::le_audio::kLeAudioCodecLC3TypeAudioChannelAllocation) == 0) |
| return default_location; |
| |
| auto& vec = metadata.at( |
| bluetooth::le_audio::kLeAudioCodecLC3TypeAudioChannelAllocation); |
| return VEC_UINT8_TO_UINT32(vec); |
| } |
| |
| static jint getSamplingFrequencyOrDefault( |
| const std::map<uint8_t, std::vector<uint8_t>>& metadata, |
| jint default_sampling_frequency) { |
| if (metadata.count(bluetooth::le_audio::kLeAudioCodecLC3TypeSamplingFreq) == |
| 0) |
| return default_sampling_frequency; |
| |
| auto& vec = |
| metadata.at(bluetooth::le_audio::kLeAudioCodecLC3TypeSamplingFreq); |
| return (jint)(vec.data()[0]); |
| } |
| |
| static jint getFrameDurationOrDefault( |
| const std::map<uint8_t, std::vector<uint8_t>>& metadata, |
| jint default_frame_duration) { |
| if (metadata.count(bluetooth::le_audio::kLeAudioCodecLC3TypeFrameDuration) == |
| 0) |
| return default_frame_duration; |
| |
| auto& vec = |
| metadata.at(bluetooth::le_audio::kLeAudioCodecLC3TypeFrameDuration); |
| return (jint)(vec.data()[0]); |
| } |
| |
| static jint getOctetsPerFrameOrDefault( |
| const std::map<uint8_t, std::vector<uint8_t>>& metadata, |
| jint default_octets_per_frame) { |
| if (metadata.count(bluetooth::le_audio::kLeAudioCodecLC3TypeOctetPerFrame) == |
| 0) |
| return default_octets_per_frame; |
| |
| auto& vec = |
| metadata.at(bluetooth::le_audio::kLeAudioCodecLC3TypeOctetPerFrame); |
| return VEC_UINT8_TO_UINT16(vec); |
| } |
| |
| jobject prepareLeAudioCodecConfigMetadataObject( |
| JNIEnv* env, const std::map<uint8_t, std::vector<uint8_t>>& metadata) { |
| jlong audio_location = getAudioLocationOrDefault(metadata, -1); |
| jint sampling_frequency = getSamplingFrequencyOrDefault(metadata, 0); |
| jint frame_duration = getFrameDurationOrDefault(metadata, -1); |
| jint octets_per_frame = getOctetsPerFrameOrDefault(metadata, 0); |
| ScopedLocalRef<jbyteArray> raw_metadata(env, |
| prepareRawLtvArray(env, metadata)); |
| if (!raw_metadata.get()) { |
| LOG(ERROR) << "Failed to create raw metadata jbyteArray"; |
| return nullptr; |
| } |
| |
| jobject obj = env->NewObject( |
| android_bluetooth_BluetoothLeAudioCodecConfigMetadata.clazz, |
| android_bluetooth_BluetoothLeAudioCodecConfigMetadata.constructor, |
| audio_location, sampling_frequency, frame_duration, octets_per_frame, |
| raw_metadata.get()); |
| |
| return obj; |
| } |
| |
| jobject prepareLeBroadcastChannelObject( |
| JNIEnv* env, |
| const bluetooth::le_audio::BasicAudioAnnouncementBisConfig& bis_config) { |
| ScopedLocalRef<jobject> meta_object( |
| env, prepareLeAudioCodecConfigMetadataObject( |
| env, bis_config.codec_specific_params)); |
| if (!meta_object.get()) { |
| LOG(ERROR) << "Failed to create new metadata object for bis config"; |
| return nullptr; |
| } |
| |
| jobject obj = |
| env->NewObject(android_bluetooth_BluetoothLeBroadcastChannel.clazz, |
| android_bluetooth_BluetoothLeBroadcastChannel.constructor, |
| false, bis_config.bis_index, meta_object.get()); |
| |
| return obj; |
| } |
| |
| jobject prepareLeAudioContentMetadataObject( |
| JNIEnv* env, const std::map<uint8_t, std::vector<uint8_t>>& metadata) { |
| jstring program_info_str = nullptr; |
| if (metadata.count(bluetooth::le_audio::kLeAudioMetadataTypeProgramInfo)) { |
| // Convert the metadata vector to string with null terminator |
| std::string p_str( |
| (const char*)metadata |
| .at(bluetooth::le_audio::kLeAudioMetadataTypeProgramInfo) |
| .data(), |
| metadata.at(bluetooth::le_audio::kLeAudioMetadataTypeProgramInfo) |
| .size()); |
| |
| program_info_str = env->NewStringUTF(p_str.c_str()); |
| if (!program_info_str) { |
| LOG(ERROR) << "Failed to create new preset name String for preset name"; |
| return nullptr; |
| } |
| } |
| |
| jstring language_str = nullptr; |
| if (metadata.count(bluetooth::le_audio::kLeAudioMetadataTypeLanguage)) { |
| // Convert the metadata vector to string with null terminator |
| std::string l_str( |
| (const char*)metadata |
| .at(bluetooth::le_audio::kLeAudioMetadataTypeLanguage) |
| .data(), |
| metadata.at(bluetooth::le_audio::kLeAudioMetadataTypeLanguage).size()); |
| |
| language_str = env->NewStringUTF(l_str.c_str()); |
| if (!language_str) { |
| LOG(ERROR) << "Failed to create new preset name String for language"; |
| return nullptr; |
| } |
| } |
| |
| // This can be nullptr |
| ScopedLocalRef<jbyteArray> raw_metadata(env, |
| prepareRawLtvArray(env, metadata)); |
| if (!raw_metadata.get()) { |
| LOG(ERROR) << "Failed to create raw_metadata jbyteArray"; |
| return nullptr; |
| } |
| |
| jobject obj = env->NewObject( |
| android_bluetooth_BluetoothLeAudioContentMetadata.clazz, |
| android_bluetooth_BluetoothLeAudioContentMetadata.constructor, |
| program_info_str, language_str, raw_metadata.get()); |
| |
| if (program_info_str) { |
| env->DeleteLocalRef(program_info_str); |
| } |
| |
| if (language_str) { |
| env->DeleteLocalRef(language_str); |
| } |
| |
| return obj; |
| } |
| |
| jobject prepareLeBroadcastChannelListObject( |
| JNIEnv* env, |
| const std::vector<bluetooth::le_audio::BasicAudioAnnouncementBisConfig>& |
| bis_configs) { |
| jobject array = env->NewObject(java_util_ArrayList.clazz, |
| java_util_ArrayList.constructor); |
| if (!array) { |
| LOG(ERROR) << "Failed to create array for subgroups"; |
| return nullptr; |
| } |
| |
| for (const auto& el : bis_configs) { |
| ScopedLocalRef<jobject> channel_obj( |
| env, prepareLeBroadcastChannelObject(env, el)); |
| if (!channel_obj.get()) { |
| LOG(ERROR) << "Failed to create new channel object"; |
| return nullptr; |
| } |
| |
| env->CallBooleanMethod(array, java_util_ArrayList.add, channel_obj.get()); |
| } |
| return array; |
| } |
| |
| jobject prepareLeBroadcastSubgroupObject( |
| JNIEnv* env, |
| const bluetooth::le_audio::BasicAudioAnnouncementSubgroup& subgroup) { |
| // Serialize codec ID |
| jlong jlong_codec_id = |
| subgroup.codec_config.codec_id | |
| ((jlong)subgroup.codec_config.vendor_company_id << 16) | |
| ((jlong)subgroup.codec_config.vendor_codec_id << 32); |
| |
| ScopedLocalRef<jobject> codec_config_meta_obj( |
| env, prepareLeAudioCodecConfigMetadataObject( |
| env, subgroup.codec_config.codec_specific_params)); |
| if (!codec_config_meta_obj.get()) { |
| LOG(ERROR) << "Failed to create new codec config metadata"; |
| return nullptr; |
| } |
| |
| ScopedLocalRef<jobject> content_meta_obj( |
| env, prepareLeAudioContentMetadataObject(env, subgroup.metadata)); |
| if (!content_meta_obj.get()) { |
| LOG(ERROR) << "Failed to create new codec config metadata"; |
| return nullptr; |
| } |
| |
| ScopedLocalRef<jobject> channel_list_obj( |
| env, prepareLeBroadcastChannelListObject(env, subgroup.bis_configs)); |
| if (!channel_list_obj.get()) { |
| LOG(ERROR) << "Failed to create new codec config metadata"; |
| return nullptr; |
| } |
| |
| // Create the subgroup |
| return env->NewObject( |
| android_bluetooth_BluetoothLeBroadcastSubgroup.clazz, |
| android_bluetooth_BluetoothLeBroadcastSubgroup.constructor, |
| jlong_codec_id, codec_config_meta_obj.get(), content_meta_obj.get(), |
| channel_list_obj.get()); |
| } |
| |
| jobject prepareLeBroadcastSubgroupListObject( |
| JNIEnv* env, |
| const std::vector<bluetooth::le_audio::BasicAudioAnnouncementSubgroup>& |
| subgroup_configs) { |
| jobject array = env->NewObject(java_util_ArrayList.clazz, |
| java_util_ArrayList.constructor); |
| if (!array) { |
| LOG(ERROR) << "Failed to create array for subgroups"; |
| return nullptr; |
| } |
| |
| for (const auto& el : subgroup_configs) { |
| ScopedLocalRef<jobject> subgroup_obj( |
| env, prepareLeBroadcastSubgroupObject(env, el)); |
| if (!subgroup_obj.get()) { |
| LOG(ERROR) << "Failed to create new subgroup object"; |
| return nullptr; |
| } |
| |
| env->CallBooleanMethod(array, java_util_ArrayList.add, subgroup_obj.get()); |
| } |
| return array; |
| } |
| |
| jobject prepareBluetoothDeviceObject(JNIEnv* env, const RawAddress& addr, |
| int addr_type) { |
| // The address string has to be uppercase or the BluetoothDevice constructor |
| // will treat it as invalid. |
| auto addr_str = addr.ToString(); |
| std::transform(addr_str.begin(), addr_str.end(), addr_str.begin(), |
| [](unsigned char c) { return std::toupper(c); }); |
| |
| ScopedLocalRef<jstring> addr_jstr(env, env->NewStringUTF(addr_str.c_str())); |
| if (!addr_jstr.get()) { |
| LOG(ERROR) << "Failed to create new preset name String for preset name"; |
| return nullptr; |
| } |
| |
| return env->NewObject(android_bluetooth_BluetoothDevice.clazz, |
| android_bluetooth_BluetoothDevice.constructor, |
| addr_jstr.get(), (jint)addr_type); |
| } |
| |
| jobject prepareBluetoothLeBroadcastMetadataObject( |
| JNIEnv* env, |
| const bluetooth::le_audio::BroadcastMetadata& broadcast_metadata) { |
| ScopedLocalRef<jobject> device_obj( |
| env, prepareBluetoothDeviceObject(env, broadcast_metadata.addr, |
| broadcast_metadata.addr_type)); |
| if (!device_obj.get()) { |
| LOG(ERROR) << "Failed to create new BluetoothDevice"; |
| return nullptr; |
| } |
| |
| ScopedLocalRef<jobject> subgroup_list_obj( |
| env, |
| prepareLeBroadcastSubgroupListObject( |
| env, broadcast_metadata.basic_audio_announcement.subgroup_configs)); |
| if (!subgroup_list_obj.get()) { |
| LOG(ERROR) << "Failed to create new Subgroup array"; |
| return nullptr; |
| } |
| |
| // Skip the leading null char bytes |
| int nativeCodeSize = 16; |
| int nativeCodeLeadingZeros = 0; |
| if (broadcast_metadata.broadcast_code) { |
| auto& nativeCode = broadcast_metadata.broadcast_code.value(); |
| nativeCodeLeadingZeros = |
| std::find_if(nativeCode.cbegin(), nativeCode.cend(), |
| [](int x) { return x != 0x00; }) - |
| nativeCode.cbegin(); |
| nativeCodeSize = nativeCode.size() - nativeCodeLeadingZeros; |
| } |
| |
| ScopedLocalRef<jbyteArray> code(env, env->NewByteArray(nativeCodeSize)); |
| if (!code.get()) { |
| LOG(ERROR) << "Failed to create new jbyteArray for the broadcast code"; |
| return nullptr; |
| } |
| |
| if (broadcast_metadata.broadcast_code) { |
| env->SetByteArrayRegion( |
| code.get(), 0, nativeCodeSize, |
| (const jbyte*)broadcast_metadata.broadcast_code->data() + |
| nativeCodeLeadingZeros); |
| CHECK(!env->ExceptionCheck()); |
| } |
| |
| ScopedLocalRef<jstring> broadcast_name( |
| env, env->NewStringUTF(broadcast_metadata.broadcast_name.c_str())); |
| if (!broadcast_name.get()) { |
| LOG(ERROR) << "Failed to create new broadcast name String"; |
| return nullptr; |
| } |
| |
| jint audio_cfg_quality = 0; |
| if (broadcast_metadata.public_announcement.features & |
| bluetooth::le_audio::kLeAudioQualityStandard) { |
| // Set bit 0 for AUDIO_CONFIG_QUALITY_STANDARD |
| audio_cfg_quality |= 0x1 << bluetooth::le_audio::QUALITY_STANDARD; |
| } |
| if (broadcast_metadata.public_announcement.features & |
| bluetooth::le_audio::kLeAudioQualityHigh) { |
| // Set bit 1 for AUDIO_CONFIG_QUALITY_HIGH |
| audio_cfg_quality |= 0x1 << bluetooth::le_audio::QUALITY_HIGH; |
| } |
| |
| ScopedLocalRef<jobject> public_meta_obj( |
| env, prepareLeAudioContentMetadataObject( |
| env, broadcast_metadata.public_announcement.metadata)); |
| if (!public_meta_obj.get()) { |
| LOG(ERROR) << "Failed to create new public metadata obj"; |
| return nullptr; |
| } |
| |
| return env->NewObject( |
| android_bluetooth_BluetoothLeBroadcastMetadata.clazz, |
| android_bluetooth_BluetoothLeBroadcastMetadata.constructor, |
| (jint)broadcast_metadata.addr_type, device_obj.get(), |
| (jint)broadcast_metadata.adv_sid, (jint)broadcast_metadata.broadcast_id, |
| (jint)broadcast_metadata.pa_interval, |
| broadcast_metadata.broadcast_code ? true : false, |
| broadcast_metadata.is_public, broadcast_name.get(), |
| broadcast_metadata.broadcast_code ? code.get() : nullptr, |
| (jint)broadcast_metadata.basic_audio_announcement.presentation_delay, |
| audio_cfg_quality, public_meta_obj.get(), subgroup_list_obj.get()); |
| } |
| |
| class LeAudioBroadcasterCallbacksImpl : public LeAudioBroadcasterCallbacks { |
| public: |
| ~LeAudioBroadcasterCallbacksImpl() = default; |
| |
| void OnBroadcastCreated(uint32_t broadcast_id, bool success) override { |
| LOG(INFO) << __func__; |
| |
| std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterCallbacksMutex); |
| CallbackEnv sCallbackEnv(__func__); |
| |
| if (!sCallbackEnv.valid() || sBroadcasterCallbacksObj == nullptr) return; |
| sCallbackEnv->CallVoidMethod(sBroadcasterCallbacksObj, |
| method_onBroadcastCreated, (jint)broadcast_id, |
| success ? JNI_TRUE : JNI_FALSE); |
| } |
| |
| void OnBroadcastDestroyed(uint32_t broadcast_id) override { |
| LOG(INFO) << __func__; |
| |
| std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterCallbacksMutex); |
| CallbackEnv sCallbackEnv(__func__); |
| |
| if (!sCallbackEnv.valid() || sBroadcasterCallbacksObj == nullptr) return; |
| sCallbackEnv->CallVoidMethod(sBroadcasterCallbacksObj, |
| method_onBroadcastDestroyed, |
| (jint)broadcast_id); |
| } |
| |
| void OnBroadcastStateChanged(uint32_t broadcast_id, |
| BroadcastState state) override { |
| LOG(INFO) << __func__; |
| |
| std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterCallbacksMutex); |
| CallbackEnv sCallbackEnv(__func__); |
| |
| if (!sCallbackEnv.valid() || sBroadcasterCallbacksObj == nullptr) return; |
| sCallbackEnv->CallVoidMethod( |
| sBroadcasterCallbacksObj, method_onBroadcastStateChanged, |
| (jint)broadcast_id, |
| (jint) static_cast<std::underlying_type<BroadcastState>::type>(state)); |
| } |
| |
| void OnBroadcastMetadataChanged(uint32_t broadcast_id, |
| const bluetooth::le_audio::BroadcastMetadata& |
| broadcast_metadata) override { |
| LOG(INFO) << __func__; |
| |
| std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterCallbacksMutex); |
| CallbackEnv sCallbackEnv(__func__); |
| |
| ScopedLocalRef<jobject> metadata_obj( |
| sCallbackEnv.get(), prepareBluetoothLeBroadcastMetadataObject( |
| sCallbackEnv.get(), broadcast_metadata)); |
| |
| if (!sCallbackEnv.valid() || sBroadcasterCallbacksObj == nullptr) return; |
| sCallbackEnv->CallVoidMethod(sBroadcasterCallbacksObj, |
| method_onBroadcastMetadataChanged, |
| (jint)broadcast_id, metadata_obj.get()); |
| } |
| }; |
| |
| static LeAudioBroadcasterCallbacksImpl sLeAudioBroadcasterCallbacks; |
| |
| static void BroadcasterClassInitNative(JNIEnv* env, jclass clazz) { |
| method_onBroadcastCreated = |
| env->GetMethodID(clazz, "onBroadcastCreated", "(IZ)V"); |
| method_onBroadcastDestroyed = |
| env->GetMethodID(clazz, "onBroadcastDestroyed", "(I)V"); |
| method_onBroadcastStateChanged = |
| env->GetMethodID(clazz, "onBroadcastStateChanged", "(II)V"); |
| method_onBroadcastMetadataChanged = |
| env->GetMethodID(clazz, "onBroadcastMetadataChanged", |
| "(ILandroid/bluetooth/BluetoothLeBroadcastMetadata;)V"); |
| |
| jclass jniArrayListClass = env->FindClass("java/util/ArrayList"); |
| java_util_ArrayList.constructor = |
| env->GetMethodID(jniArrayListClass, "<init>", "()V"); |
| java_util_ArrayList.add = |
| env->GetMethodID(jniArrayListClass, "add", "(Ljava/lang/Object;)Z"); |
| |
| jclass jniBluetoothLeAudioCodecConfigMetadataClass = |
| env->FindClass("android/bluetooth/BluetoothLeAudioCodecConfigMetadata"); |
| android_bluetooth_BluetoothLeAudioCodecConfigMetadata.constructor = |
| env->GetMethodID(jniBluetoothLeAudioCodecConfigMetadataClass, "<init>", |
| "(JIII[B)V"); |
| |
| jclass jniBluetoothLeAudioContentMetadataClass = |
| env->FindClass("android/bluetooth/BluetoothLeAudioContentMetadata"); |
| android_bluetooth_BluetoothLeAudioContentMetadata.constructor = |
| env->GetMethodID(jniBluetoothLeAudioContentMetadataClass, "<init>", |
| "(Ljava/lang/String;Ljava/lang/String;[B)V"); |
| |
| jclass jniBluetoothLeBroadcastChannelClass = |
| env->FindClass("android/bluetooth/BluetoothLeBroadcastChannel"); |
| android_bluetooth_BluetoothLeBroadcastChannel.constructor = env->GetMethodID( |
| jniBluetoothLeBroadcastChannelClass, "<init>", |
| "(ZILandroid/bluetooth/BluetoothLeAudioCodecConfigMetadata;)V"); |
| |
| jclass jniBluetoothLeBroadcastSubgroupClass = |
| env->FindClass("android/bluetooth/BluetoothLeBroadcastSubgroup"); |
| android_bluetooth_BluetoothLeBroadcastSubgroup.constructor = env->GetMethodID( |
| jniBluetoothLeBroadcastSubgroupClass, "<init>", |
| "(JLandroid/bluetooth/BluetoothLeAudioCodecConfigMetadata;" |
| "Landroid/bluetooth/BluetoothLeAudioContentMetadata;" |
| "Ljava/util/List;)V"); |
| |
| jclass jniBluetoothDeviceClass = |
| env->FindClass("android/bluetooth/BluetoothDevice"); |
| android_bluetooth_BluetoothDevice.constructor = env->GetMethodID( |
| jniBluetoothDeviceClass, "<init>", "(Ljava/lang/String;I)V"); |
| |
| jclass jniBluetoothLeBroadcastMetadataClass = |
| env->FindClass("android/bluetooth/BluetoothLeBroadcastMetadata"); |
| android_bluetooth_BluetoothLeBroadcastMetadata.constructor = env->GetMethodID( |
| jniBluetoothLeBroadcastMetadataClass, "<init>", |
| "(ILandroid/bluetooth/BluetoothDevice;IIIZZLjava/lang/String;" |
| "[BIILandroid/bluetooth/BluetoothLeAudioContentMetadata;" |
| "Ljava/util/List;)V"); |
| } |
| |
| static void BroadcasterInitNative(JNIEnv* env, jobject object) { |
| std::unique_lock<std::shared_timed_mutex> interface_lock( |
| sBroadcasterInterfaceMutex); |
| std::unique_lock<std::shared_timed_mutex> callbacks_lock( |
| sBroadcasterCallbacksMutex); |
| |
| const bt_interface_t* btInf = getBluetoothInterface(); |
| if (btInf == nullptr) { |
| LOG(ERROR) << "Bluetooth module is not loaded"; |
| return; |
| } |
| |
| android_bluetooth_BluetoothDevice.clazz = (jclass)env->NewGlobalRef( |
| env->FindClass("android/bluetooth/BluetoothDevice")); |
| if (android_bluetooth_BluetoothDevice.clazz == nullptr) { |
| LOG(ERROR) << "Failed to allocate Global Ref for BluetoothDevice class"; |
| return; |
| } |
| |
| java_util_ArrayList.clazz = |
| (jclass)env->NewGlobalRef(env->FindClass("java/util/ArrayList")); |
| if (java_util_ArrayList.clazz == nullptr) { |
| LOG(ERROR) << "Failed to allocate Global Ref for ArrayList class"; |
| return; |
| } |
| |
| android_bluetooth_BluetoothLeAudioCodecConfigMetadata.clazz = |
| (jclass)env->NewGlobalRef(env->FindClass( |
| "android/bluetooth/BluetoothLeAudioCodecConfigMetadata")); |
| if (android_bluetooth_BluetoothLeAudioCodecConfigMetadata.clazz == nullptr) { |
| LOG(ERROR) << "Failed to allocate Global Ref for " |
| "BluetoothLeAudioCodecConfigMetadata class"; |
| return; |
| } |
| |
| android_bluetooth_BluetoothLeAudioContentMetadata.clazz = |
| (jclass)env->NewGlobalRef( |
| env->FindClass("android/bluetooth/BluetoothLeAudioContentMetadata")); |
| if (android_bluetooth_BluetoothLeAudioContentMetadata.clazz == nullptr) { |
| LOG(ERROR) << "Failed to allocate Global Ref for " |
| "BluetoothLeAudioContentMetadata class"; |
| return; |
| } |
| |
| android_bluetooth_BluetoothLeBroadcastSubgroup.clazz = |
| (jclass)env->NewGlobalRef( |
| env->FindClass("android/bluetooth/BluetoothLeBroadcastSubgroup")); |
| if (android_bluetooth_BluetoothLeBroadcastSubgroup.clazz == nullptr) { |
| LOG(ERROR) << "Failed to allocate Global Ref for " |
| "BluetoothLeBroadcastSubgroup class"; |
| return; |
| } |
| |
| android_bluetooth_BluetoothLeBroadcastChannel.clazz = |
| (jclass)env->NewGlobalRef( |
| env->FindClass("android/bluetooth/BluetoothLeBroadcastChannel")); |
| if (android_bluetooth_BluetoothLeBroadcastChannel.clazz == nullptr) { |
| LOG(ERROR) << "Failed to allocate Global Ref for " |
| "BluetoothLeBroadcastChannel class"; |
| return; |
| } |
| |
| android_bluetooth_BluetoothLeBroadcastMetadata.clazz = |
| (jclass)env->NewGlobalRef( |
| env->FindClass("android/bluetooth/BluetoothLeBroadcastMetadata")); |
| if (android_bluetooth_BluetoothLeBroadcastMetadata.clazz == nullptr) { |
| LOG(ERROR) << "Failed to allocate Global Ref for " |
| "BluetoothLeBroadcastMetadata class"; |
| return; |
| } |
| |
| if (sBroadcasterCallbacksObj != nullptr) { |
| LOG(INFO) << "Cleaning up LeAudio Broadcaster callback object"; |
| env->DeleteGlobalRef(sBroadcasterCallbacksObj); |
| sBroadcasterCallbacksObj = nullptr; |
| } |
| |
| if ((sBroadcasterCallbacksObj = env->NewGlobalRef(object)) == nullptr) { |
| LOG(ERROR) |
| << "Failed to allocate Global Ref for LeAudio Broadcaster Callbacks"; |
| return; |
| } |
| |
| sLeAudioBroadcasterInterface = |
| (LeAudioBroadcasterInterface*)btInf->get_profile_interface( |
| BT_PROFILE_LE_AUDIO_BROADCASTER_ID); |
| if (sLeAudioBroadcasterInterface == nullptr) { |
| LOG(ERROR) << "Failed to get Bluetooth LeAudio Broadcaster Interface"; |
| return; |
| } |
| |
| sLeAudioBroadcasterInterface->Initialize(&sLeAudioBroadcasterCallbacks); |
| } |
| |
| static void BroadcasterStopNative(JNIEnv* env, jobject object) { |
| std::unique_lock<std::shared_timed_mutex> interface_lock( |
| sBroadcasterInterfaceMutex); |
| |
| const bt_interface_t* btInf = getBluetoothInterface(); |
| if (btInf == nullptr) { |
| LOG(ERROR) << "Bluetooth module is not loaded"; |
| return; |
| } |
| |
| if (sLeAudioBroadcasterInterface != nullptr) |
| sLeAudioBroadcasterInterface->Stop(); |
| } |
| |
| static void BroadcasterCleanupNative(JNIEnv* env, jobject object) { |
| std::unique_lock<std::shared_timed_mutex> interface_lock( |
| sBroadcasterInterfaceMutex); |
| std::unique_lock<std::shared_timed_mutex> callbacks_lock( |
| sBroadcasterCallbacksMutex); |
| |
| const bt_interface_t* btInf = getBluetoothInterface(); |
| if (btInf == nullptr) { |
| LOG(ERROR) << "Bluetooth module is not loaded"; |
| return; |
| } |
| |
| env->DeleteGlobalRef(java_util_ArrayList.clazz); |
| java_util_ArrayList.clazz = nullptr; |
| |
| env->DeleteGlobalRef(android_bluetooth_BluetoothDevice.clazz); |
| android_bluetooth_BluetoothDevice.clazz = nullptr; |
| |
| env->DeleteGlobalRef( |
| android_bluetooth_BluetoothLeAudioCodecConfigMetadata.clazz); |
| android_bluetooth_BluetoothLeAudioCodecConfigMetadata.clazz = nullptr; |
| |
| env->DeleteGlobalRef(android_bluetooth_BluetoothLeAudioContentMetadata.clazz); |
| android_bluetooth_BluetoothLeAudioContentMetadata.clazz = nullptr; |
| |
| env->DeleteGlobalRef(android_bluetooth_BluetoothLeBroadcastSubgroup.clazz); |
| android_bluetooth_BluetoothLeBroadcastSubgroup.clazz = nullptr; |
| |
| env->DeleteGlobalRef(android_bluetooth_BluetoothLeBroadcastChannel.clazz); |
| android_bluetooth_BluetoothLeBroadcastChannel.clazz = nullptr; |
| |
| env->DeleteGlobalRef(android_bluetooth_BluetoothLeBroadcastMetadata.clazz); |
| android_bluetooth_BluetoothLeBroadcastMetadata.clazz = nullptr; |
| |
| if (sLeAudioBroadcasterInterface != nullptr) { |
| sLeAudioBroadcasterInterface->Cleanup(); |
| sLeAudioBroadcasterInterface = nullptr; |
| } |
| |
| if (sBroadcasterCallbacksObj != nullptr) { |
| env->DeleteGlobalRef(sBroadcasterCallbacksObj); |
| sBroadcasterCallbacksObj = nullptr; |
| } |
| } |
| |
| std::vector<std::vector<uint8_t>> convertToDataVectors(JNIEnv* env, |
| jobjectArray dataArray) { |
| jsize arraySize = env->GetArrayLength(dataArray); |
| std::vector<std::vector<uint8_t>> res(arraySize); |
| |
| for (int i = 0; i < arraySize; ++i) { |
| jbyteArray rowData = (jbyteArray)env->GetObjectArrayElement(dataArray, i); |
| jsize dataSize = env->GetArrayLength(rowData); |
| std::vector<uint8_t>& rowVector = res[i]; |
| rowVector.resize(dataSize); |
| env->GetByteArrayRegion(rowData, 0, dataSize, |
| reinterpret_cast<jbyte*>(rowVector.data())); |
| env->DeleteLocalRef(rowData); |
| } |
| return res; |
| } |
| |
| static void CreateBroadcastNative(JNIEnv* env, jobject object, |
| jboolean isPublic, jstring broadcastName, |
| jbyteArray broadcast_code, |
| jbyteArray publicMetadata, |
| jintArray qualityArray, |
| jobjectArray metadataArray) { |
| LOG(INFO) << __func__; |
| std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex); |
| if (!sLeAudioBroadcasterInterface) return; |
| |
| std::array<uint8_t, 16> code_array{0}; |
| if (broadcast_code) { |
| jsize size = env->GetArrayLength(broadcast_code); |
| if (size > 16) { |
| ALOGE("%s: broadcast code to long", __func__); |
| return; |
| } |
| |
| // Padding with zeros on MSB positions if code is shorter than 16 octets |
| env->GetByteArrayRegion(broadcast_code, 0, size, (jbyte*)code_array.data()); |
| } |
| |
| const char* broadcast_name = nullptr; |
| if (broadcastName) { |
| broadcast_name = env->GetStringUTFChars(broadcastName, nullptr); |
| } |
| |
| jbyte* public_meta = nullptr; |
| if (publicMetadata) { |
| public_meta = env->GetByteArrayElements(publicMetadata, nullptr); |
| } |
| |
| jint* quality_array = nullptr; |
| if (qualityArray) { |
| quality_array = env->GetIntArrayElements(qualityArray, nullptr); |
| } |
| |
| sLeAudioBroadcasterInterface->CreateBroadcast( |
| isPublic, broadcast_name ? broadcast_name : "", |
| broadcast_code ? std::optional<std::array<uint8_t, 16>>(code_array) |
| : std::nullopt, |
| public_meta ? std::vector<uint8_t>( |
| public_meta, public_meta + env->GetArrayLength(publicMetadata)) |
| : std::vector<uint8_t>(), |
| quality_array ? std::vector<uint8_t>( |
| quality_array, quality_array + env->GetArrayLength(qualityArray)) |
| : std::vector<uint8_t>(), |
| convertToDataVectors(env, metadataArray)); |
| |
| if (broadcast_name) env->ReleaseStringUTFChars(broadcastName, broadcast_name); |
| if (public_meta) env->ReleaseByteArrayElements(publicMetadata, public_meta, 0); |
| if (quality_array) env->ReleaseIntArrayElements(qualityArray, quality_array, 0); |
| } |
| |
| static void UpdateMetadataNative(JNIEnv* env, jobject object, jint broadcast_id, |
| jstring broadcastName, |
| jbyteArray publicMetadata, |
| jobjectArray metadataArray) { |
| const char* broadcast_name = nullptr; |
| if (broadcastName) { |
| broadcast_name = env->GetStringUTFChars(broadcastName, nullptr); |
| } |
| |
| jbyte* public_meta = nullptr; |
| if (publicMetadata) { |
| public_meta = env->GetByteArrayElements(publicMetadata, nullptr); |
| } |
| |
| sLeAudioBroadcasterInterface->UpdateMetadata( |
| broadcast_id, broadcast_name ? broadcast_name : "", |
| public_meta |
| ? std::vector<uint8_t>( |
| public_meta, public_meta + env->GetArrayLength(publicMetadata)) |
| : std::vector<uint8_t>(), |
| convertToDataVectors(env, metadataArray)); |
| |
| if (broadcast_name) env->ReleaseStringUTFChars(broadcastName, broadcast_name); |
| if (public_meta) env->ReleaseByteArrayElements(publicMetadata, public_meta, 0); |
| } |
| |
| static void StartBroadcastNative(JNIEnv* env, jobject object, |
| jint broadcast_id) { |
| LOG(INFO) << __func__; |
| std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex); |
| if (!sLeAudioBroadcasterInterface) return; |
| sLeAudioBroadcasterInterface->StartBroadcast(broadcast_id); |
| } |
| |
| static void StopBroadcastNative(JNIEnv* env, jobject object, |
| jint broadcast_id) { |
| LOG(INFO) << __func__; |
| std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex); |
| if (!sLeAudioBroadcasterInterface) return; |
| sLeAudioBroadcasterInterface->StopBroadcast(broadcast_id); |
| } |
| |
| static void PauseBroadcastNative(JNIEnv* env, jobject object, |
| jint broadcast_id) { |
| LOG(INFO) << __func__; |
| std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex); |
| if (!sLeAudioBroadcasterInterface) return; |
| sLeAudioBroadcasterInterface->PauseBroadcast(broadcast_id); |
| } |
| |
| static void DestroyBroadcastNative(JNIEnv* env, jobject object, |
| jint broadcast_id) { |
| LOG(INFO) << __func__; |
| std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex); |
| if (!sLeAudioBroadcasterInterface) return; |
| sLeAudioBroadcasterInterface->DestroyBroadcast(broadcast_id); |
| } |
| |
| static void getBroadcastMetadataNative(JNIEnv* env, jobject object, |
| jint broadcast_id) { |
| LOG(INFO) << __func__; |
| std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex); |
| if (!sLeAudioBroadcasterInterface) return; |
| sLeAudioBroadcasterInterface->GetBroadcastMetadata(broadcast_id); |
| } |
| |
| static JNINativeMethod sBroadcasterMethods[] = { |
| {"classInitNative", "()V", (void*)BroadcasterClassInitNative}, |
| {"initNative", "()V", (void*)BroadcasterInitNative}, |
| {"stopNative", "()V", (void*)BroadcasterStopNative}, |
| {"cleanupNative", "()V", (void*)BroadcasterCleanupNative}, |
| {"createBroadcastNative", "(ZLjava/lang/String;[B[B[I[[B)V", |
| (void*)CreateBroadcastNative}, |
| {"updateMetadataNative", "(ILjava/lang/String;[B[[B)V", |
| (void*)UpdateMetadataNative}, |
| {"startBroadcastNative", "(I)V", (void*)StartBroadcastNative}, |
| {"stopBroadcastNative", "(I)V", (void*)StopBroadcastNative}, |
| {"pauseBroadcastNative", "(I)V", (void*)PauseBroadcastNative}, |
| {"destroyBroadcastNative", "(I)V", (void*)DestroyBroadcastNative}, |
| {"getBroadcastMetadataNative", "(I)V", (void*)getBroadcastMetadataNative}, |
| }; |
| |
| int register_com_android_bluetooth_le_audio(JNIEnv* env) { |
| int register_success = jniRegisterNativeMethods( |
| env, "com/android/bluetooth/le_audio/LeAudioNativeInterface", sMethods, |
| NELEM(sMethods)); |
| return register_success & |
| jniRegisterNativeMethods( |
| env, |
| "com/android/bluetooth/le_audio/LeAudioBroadcasterNativeInterface", |
| sBroadcasterMethods, NELEM(sBroadcasterMethods)); |
| } |
| } // namespace android |