/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "com_android_bluetooth.h" #include "hardware/bluetooth.h" #include "hardware/bt_le_audio.h" #include "types/raw_address.h" using bluetooth::le_audio::BroadcastId; using bluetooth::le_audio::BroadcastState; using bluetooth::le_audio::btle_audio_bits_per_sample_index_t; using bluetooth::le_audio::btle_audio_channel_count_index_t; using bluetooth::le_audio::btle_audio_codec_config_t; using bluetooth::le_audio::btle_audio_codec_index_t; using bluetooth::le_audio::btle_audio_frame_duration_index_t; using bluetooth::le_audio::btle_audio_sample_rate_index_t; using bluetooth::le_audio::ConnectionState; using bluetooth::le_audio::GroupNodeStatus; using bluetooth::le_audio::GroupStatus; using bluetooth::le_audio::GroupStreamStatus; using bluetooth::le_audio::LeAudioBroadcasterCallbacks; using bluetooth::le_audio::LeAudioBroadcasterInterface; using bluetooth::le_audio::LeAudioClientCallbacks; using bluetooth::le_audio::LeAudioClientInterface; using bluetooth::le_audio::UnicastMonitorModeStatus; 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_onAudioGroupCurrentCodecConf; static jmethodID method_onAudioGroupSelectableCodecConf; static jmethodID method_onHealthBasedRecommendationAction; static jmethodID method_onHealthBasedGroupRecommendationAction; static jmethodID method_onUnicastMonitorModeStatus; static jmethodID method_onGroupStreamStatus; static struct { jclass clazz; jmethodID constructor; jmethodID getCodecType; jmethodID getSampleRate; jmethodID getBitsPerSample; jmethodID getChannelCount; jmethodID getFrameDuration; jmethodID getOctetsPerFrame; jmethodID getCodecPriority; } 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; static jclass class_LeAudioNativeInterface; static jobject prepareCodecConfigObj(JNIEnv* env, btle_audio_codec_config_t codecConfig) { log::info( "ct: {}, codec_priority: {}, sample_rate: {}, bits_per_sample: {}, " "channel_count: {}, frame_duration: {}, octets_per_frame: {}", codecConfig.codec_type, codecConfig.codec_priority, codecConfig.sample_rate, codecConfig.bits_per_sample, codecConfig.channel_count, codecConfig.frame_duration, codecConfig.octets_per_frame); jobject codecConfigObj = env->NewObject( android_bluetooth_BluetoothLeAudioCodecConfig.clazz, android_bluetooth_BluetoothLeAudioCodecConfig.constructor, (jint)codecConfig.codec_type, (jint)codecConfig.codec_priority, (jint)codecConfig.sample_rate, (jint)codecConfig.bits_per_sample, (jint)codecConfig.channel_count, (jint)codecConfig.frame_duration, (jint)codecConfig.octets_per_frame, 0, 0); return codecConfigObj; } static jobjectArray prepareArrayOfCodecConfigs( JNIEnv* env, std::vector 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(""); std::shared_lock lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) { return; } sCallbackEnv->CallStaticVoidMethod(class_LeAudioNativeInterface, method_onInitialized); } void OnConnectionState(ConnectionState state, const RawAddress& bd_addr) override { log::info("state:{}, addr: {}", int(state), bd_addr.ToRedactedStringForLogging()); std::shared_lock lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) { return; } ScopedLocalRef 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(""); std::shared_lock 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(""); std::shared_lock lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) { return; } ScopedLocalRef 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, std::optional> sink_audio_location, std::optional> source_audio_location, uint16_t avail_cont) override { log::info(""); std::shared_lock lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) { return; } jint jni_sink_audio_location = sink_audio_location ? sink_audio_location->to_ulong() : -1; jint jni_source_audio_location = source_audio_location ? source_audio_location->to_ulong() : -1; sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioConf, (jint)direction, (jint)group_id, jni_sink_audio_location, jni_source_audio_location, (jint)avail_cont); } void OnSinkAudioLocationAvailable(const RawAddress& bd_addr, std::optional> sink_audio_location) override { log::info(""); std::shared_lock lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) { return; } ScopedLocalRef 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); jint jni_sink_audio_location = sink_audio_location ? sink_audio_location->to_ulong() : -1; sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSinkAudioLocationAvailable, addr.get(), jni_sink_audio_location); } void OnAudioLocalCodecCapabilities( std::vector local_input_capa_codec_conf, std::vector local_output_capa_codec_conf) override { log::info(""); std::shared_lock 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 OnAudioGroupCurrentCodecConf(int group_id, btle_audio_codec_config_t input_codec_conf, btle_audio_codec_config_t output_codec_conf) override { log::info(""); std::shared_lock 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(), output_codec_conf); sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioGroupCurrentCodecConf, (jint)group_id, inputCodecConfigObj, outputCodecConfigObj); } void OnAudioGroupSelectableCodecConf( int group_id, std::vector input_selectable_codec_conf, std::vector output_selectable_codec_conf) override { log::info(""); std::shared_lock lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) { return; } jobject inputSelectableCodecConfigArray = prepareArrayOfCodecConfigs(sCallbackEnv.get(), input_selectable_codec_conf); jobject outputSelectableCodecConfigArray = prepareArrayOfCodecConfigs(sCallbackEnv.get(), output_selectable_codec_conf); sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioGroupSelectableCodecConf, (jint)group_id, inputSelectableCodecConfigArray, outputSelectableCodecConfigArray); } void OnHealthBasedRecommendationAction( const RawAddress& bd_addr, bluetooth::le_audio::LeAudioHealthBasedAction action) override { log::info(""); std::shared_lock lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) { return; } ScopedLocalRef 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(""); std::shared_lock lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) { return; } sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onHealthBasedGroupRecommendationAction, (jint)group_id, (jint)action); } void OnUnicastMonitorModeStatus(uint8_t direction, UnicastMonitorModeStatus status) override { log::info(""); std::shared_lock lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) { return; } sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onUnicastMonitorModeStatus, (jint)direction, (jint)status); } void OnGroupStreamStatus(int group_id, GroupStreamStatus group_stream_status) override { log::info(""); std::shared_lock lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) { return; } sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGroupStreamStatus, (jint)group_id, (jint)group_stream_status); } }; static LeAudioClientCallbacksImpl sLeAudioClientCallbacks; static std::vector prepareCodecPreferences( JNIEnv* env, jobject /* object */, jobjectArray codecConfigArray) { std::vector 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)) { log::error("Invalid BluetoothLeAudioCodecConfig instance"); continue; } jint codecType = env->CallIntMethod(jcodecConfig, android_bluetooth_BluetoothLeAudioCodecConfig.getCodecType); btle_audio_codec_config_t codec_config = { .codec_type = static_cast(codecType)}; codec_preferences.push_back(codec_config); } return codec_preferences; } static void initNative(JNIEnv* env, jobject object, jobjectArray codecOffloadingArray) { std::unique_lock interface_lock(interface_mutex); std::unique_lock callbacks_lock(callbacks_mutex); const bt_interface_t* btInf = getBluetoothInterface(); if (btInf == nullptr) { log::error("Bluetooth module is not loaded"); return; } jclass tmpControllerInterface = env->FindClass("com/android/bluetooth/le_audio/LeAudioNativeInterface"); class_LeAudioNativeInterface = (jclass)env->NewGlobalRef(tmpControllerInterface); 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 codec_offloading = prepareCodecPreferences(env, object, codecOffloadingArray); sLeAudioClientInterface->Initialize(&sLeAudioClientCallbacks, codec_offloading); } static void cleanupNative(JNIEnv* env, jobject /* object */) { std::unique_lock interface_lock(interface_mutex); std::unique_lock 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(""); std::shared_lock 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(""); std::shared_lock 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 lock(interface_mutex); jbyte* addr = env->GetByteArrayElements(address, nullptr); if (!sLeAudioClientInterface) { log::error("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 lock(interface_mutex); jbyte* addr = env->GetByteArrayElements(address, nullptr); if (!sLeAudioClientInterface) { log::error("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 lock(interface_mutex); if (!sLeAudioClientInterface) { log::error("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(""); std::shared_lock lock(interface_mutex); if (!sLeAudioClientInterface) { log::error("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 lock(interface_mutex); if (!env->IsInstanceOf(inputCodecConfig, android_bluetooth_BluetoothLeAudioCodecConfig.clazz) || !env->IsInstanceOf(outputCodecConfig, android_bluetooth_BluetoothLeAudioCodecConfig.clazz)) { log::error("Invalid BluetoothLeAudioCodecConfig instance"); return; } jint inputCodecType = env->CallIntMethod( inputCodecConfig, android_bluetooth_BluetoothLeAudioCodecConfig.getCodecType); jint inputSampleRate = env->CallIntMethod( inputCodecConfig, android_bluetooth_BluetoothLeAudioCodecConfig.getSampleRate); jint inputBitsPerSample = env->CallIntMethod( inputCodecConfig, android_bluetooth_BluetoothLeAudioCodecConfig.getBitsPerSample); jint inputChannelCount = env->CallIntMethod( inputCodecConfig, android_bluetooth_BluetoothLeAudioCodecConfig.getChannelCount); jint inputFrameDuration = env->CallIntMethod( inputCodecConfig, android_bluetooth_BluetoothLeAudioCodecConfig.getFrameDuration); jint inputOctetsPerFrame = env->CallIntMethod( inputCodecConfig, android_bluetooth_BluetoothLeAudioCodecConfig.getOctetsPerFrame); jint inputCodecPriority = env->CallIntMethod( inputCodecConfig, android_bluetooth_BluetoothLeAudioCodecConfig.getCodecPriority); btle_audio_codec_config_t input_codec_config = { .codec_type = static_cast(inputCodecType), .sample_rate = static_cast(inputSampleRate), .bits_per_sample = static_cast(inputBitsPerSample), .channel_count = static_cast(inputChannelCount), .frame_duration = static_cast(inputFrameDuration), .octets_per_frame = static_cast(inputOctetsPerFrame), .codec_priority = static_cast(inputCodecPriority), }; jint outputCodecType = env->CallIntMethod( outputCodecConfig, android_bluetooth_BluetoothLeAudioCodecConfig.getCodecType); jint outputSampleRate = env->CallIntMethod( outputCodecConfig, android_bluetooth_BluetoothLeAudioCodecConfig.getSampleRate); jint outputBitsPerSample = env->CallIntMethod( outputCodecConfig, android_bluetooth_BluetoothLeAudioCodecConfig.getBitsPerSample); jint outputChannelCount = env->CallIntMethod( outputCodecConfig, android_bluetooth_BluetoothLeAudioCodecConfig.getChannelCount); jint outputFrameDuration = env->CallIntMethod( outputCodecConfig, android_bluetooth_BluetoothLeAudioCodecConfig.getFrameDuration); jint outputOctetsPerFrame = env->CallIntMethod( outputCodecConfig, android_bluetooth_BluetoothLeAudioCodecConfig.getOctetsPerFrame); jint outputCodecPriority = env->CallIntMethod( outputCodecConfig, android_bluetooth_BluetoothLeAudioCodecConfig.getCodecPriority); btle_audio_codec_config_t output_codec_config = { .codec_type = static_cast(outputCodecType), .sample_rate = static_cast(outputSampleRate), .bits_per_sample = static_cast(outputBitsPerSample), .channel_count = static_cast(outputChannelCount), .frame_duration = static_cast(outputFrameDuration), .octets_per_frame = static_cast(outputOctetsPerFrame), .codec_priority = static_cast(outputCodecPriority), }; sLeAudioClientInterface->SetCodecConfigPreference(group_id, input_codec_config, output_codec_config); } static void setCcidInformationNative(JNIEnv* /* env */, jobject /* object */, jint ccid, jint contextType) { std::shared_lock lock(interface_mutex); if (!sLeAudioClientInterface) { log::error("Failed to get the Bluetooth LeAudio Interface"); return; } sLeAudioClientInterface->SetCcidInformation(ccid, contextType); } static void setInCallNative(JNIEnv* /* env */, jobject /* object */, jboolean inCall) { std::shared_lock lock(interface_mutex); if (!sLeAudioClientInterface) { log::error("Failed to get the Bluetooth LeAudio Interface"); return; } sLeAudioClientInterface->SetInCall(inCall); } static void setUnicastMonitorModeNative(JNIEnv* /* env */, jobject /* object */, jint direction, jboolean enable) { std::shared_lock lock(interface_mutex); if (!sLeAudioClientInterface) { log::error("Failed to get the Bluetooth LeAudio Interface"); return; } sLeAudioClientInterface->SetUnicastMonitorMode(direction, enable); } static void sendAudioProfilePreferencesNative(JNIEnv* /* env */, jobject /* object */, jint groupId, jboolean isOutputPreferenceLeAudio, jboolean isDuplexPreferenceLeAudio) { std::shared_lock lock(interface_mutex); if (!sLeAudioClientInterface) { log::error("Failed to get the Bluetooth LeAudio Interface"); return; } sLeAudioClientInterface->SendAudioProfilePreferences(groupId, isOutputPreferenceLeAudio, isDuplexPreferenceLeAudio); } static void setGroupAllowedContextMaskNative(JNIEnv* /* env */, jobject /* object */, jint groupId, jint sinkContextTypes, jint sourceContextTypes) { std::shared_lock lock(interface_mutex); if (!sLeAudioClientInterface) { log::error("Failed to get the Bluetooth LeAudio Interface"); return; } log::info("group_id: {}, sink context types: {}, source context types: {}", groupId, sinkContextTypes, sourceContextTypes); sLeAudioClientInterface->SetGroupAllowedContextMask(groupId, sinkContextTypes, sourceContextTypes); } /* Le Audio Broadcaster */ static jmethodID method_onBroadcastCreated; static jmethodID method_onBroadcastDestroyed; static jmethodID method_onBroadcastStateChanged; static jmethodID method_onBroadcastMetadataChanged; static jmethodID method_onBroadcastAudioSessionCreated; 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])) static size_t RawPacketSize(const std::map>& values) { size_t bytes = 0; for (auto const& value : values) { bytes += (/* ltv_len + ltv_type */ 2 + value.second.size()); } return bytes; } static jbyteArray prepareRawLtvArray(JNIEnv* env, const std::map>& 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>& metadata, jlong default_location) { if (metadata.count(bluetooth::le_audio::kLeAudioLtvTypeAudioChannelAllocation) == 0) { return default_location; } auto& vec = metadata.at(bluetooth::le_audio::kLeAudioLtvTypeAudioChannelAllocation); return VEC_UINT8_TO_UINT32(vec); } static jint getSamplingFrequencyOrDefault(const std::map>& metadata, jint default_sampling_frequency) { if (metadata.count(bluetooth::le_audio::kLeAudioLtvTypeSamplingFreq) == 0) { return default_sampling_frequency; } auto& vec = metadata.at(bluetooth::le_audio::kLeAudioLtvTypeSamplingFreq); return (jint)(vec.data()[0]); } static jint getFrameDurationOrDefault(const std::map>& metadata, jint default_frame_duration) { if (metadata.count(bluetooth::le_audio::kLeAudioLtvTypeFrameDuration) == 0) { return default_frame_duration; } auto& vec = metadata.at(bluetooth::le_audio::kLeAudioLtvTypeFrameDuration); return (jint)(vec.data()[0]); } static jint getOctetsPerFrameOrDefault(const std::map>& metadata, jint default_octets_per_frame) { if (metadata.count(bluetooth::le_audio::kLeAudioLtvTypeOctetsPerCodecFrame) == 0) { return default_octets_per_frame; } auto& vec = metadata.at(bluetooth::le_audio::kLeAudioLtvTypeOctetsPerCodecFrame); return VEC_UINT8_TO_UINT16(vec); } static jobject prepareLeAudioCodecConfigMetadataObject( JNIEnv* env, const std::map>& 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 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; } static jobject prepareLeBroadcastChannelObject( JNIEnv* env, const bluetooth::le_audio::BasicAudioAnnouncementBisConfig& bis_config) { ScopedLocalRef 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; } static jobject prepareLeAudioContentMetadataObject( JNIEnv* env, const std::map>& 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 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; } static jobject prepareLeBroadcastChannelListObject( JNIEnv* env, const std::vector& 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 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; } static 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 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 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 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()); } static jobject prepareLeBroadcastSubgroupListObject( JNIEnv* env, const std::vector& 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 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; } static 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 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); } static jobject prepareBluetoothLeBroadcastMetadataObject( JNIEnv* env, const bluetooth::le_audio::BroadcastMetadata& broadcast_metadata) { ScopedLocalRef 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 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; } // Remove the ending null char bytes int nativeCodeSize = 16; if (broadcast_metadata.broadcast_code) { auto& nativeCode = broadcast_metadata.broadcast_code.value(); nativeCodeSize = std::find_if(nativeCode.cbegin(), nativeCode.cend(), [](int x) { return x == 0x00; }) - nativeCode.cbegin(); } ScopedLocalRef 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()); log::assert_that(!env->ExceptionCheck(), "assert failed: !env->ExceptionCheck()"); } ScopedLocalRef 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 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_us, audio_cfg_quality, (jint)bluetooth::le_audio::kLeAudioSourceRssiUnknown, 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(""); std::shared_lock 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(""); std::shared_lock 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(""); std::shared_lock lock(sBroadcasterCallbacksMutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || sBroadcasterCallbacksObj == nullptr) { return; } sCallbackEnv->CallVoidMethod( sBroadcasterCallbacksObj, method_onBroadcastStateChanged, (jint)broadcast_id, (jint) static_cast::type>(state)); } void OnBroadcastMetadataChanged( uint32_t broadcast_id, const bluetooth::le_audio::BroadcastMetadata& broadcast_metadata) override { log::info(""); std::shared_lock lock(sBroadcasterCallbacksMutex); CallbackEnv sCallbackEnv(__func__); ScopedLocalRef 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()); } void OnBroadcastAudioSessionCreated(bool success) override { log::info(""); std::shared_lock lock(sBroadcasterCallbacksMutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || sBroadcasterCallbacksObj == nullptr) { return; } sCallbackEnv->CallVoidMethod(sBroadcasterCallbacksObj, method_onBroadcastAudioSessionCreated, success ? JNI_TRUE : JNI_FALSE); } }; static LeAudioBroadcasterCallbacksImpl sLeAudioBroadcasterCallbacks; static void BroadcasterInitNative(JNIEnv* env, jobject object) { std::unique_lock interface_lock(sBroadcasterInterfaceMutex); std::unique_lock 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 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 interface_lock(sBroadcasterInterfaceMutex); std::unique_lock 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; } } static std::vector> convertToDataVectors(JNIEnv* env, jobjectArray dataArray) { jsize arraySize = env->GetArrayLength(dataArray); std::vector> res(arraySize); for (int i = 0; i < arraySize; ++i) { jbyteArray rowData = (jbyteArray)env->GetObjectArrayElement(dataArray, i); jsize dataSize = env->GetArrayLength(rowData); std::vector& rowVector = res[i]; rowVector.resize(dataSize); env->GetByteArrayRegion(rowData, 0, dataSize, reinterpret_cast(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(""); std::shared_lock lock(sBroadcasterInterfaceMutex); if (!sLeAudioBroadcasterInterface) { return; } std::array code_array{0}; if (broadcast_code) { jsize size = env->GetArrayLength(broadcast_code); if (size > 16) { log::error("broadcast code to long"); 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>(code_array) : std::nullopt, public_meta ? std::vector(public_meta, public_meta + env->GetArrayLength(publicMetadata)) : std::vector(), quality_array ? std::vector(quality_array, quality_array + env->GetArrayLength(qualityArray)) : std::vector(), 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(public_meta, public_meta + env->GetArrayLength(publicMetadata)) : std::vector(), 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(""); std::shared_lock lock(sBroadcasterInterfaceMutex); if (!sLeAudioBroadcasterInterface) { return; } sLeAudioBroadcasterInterface->StartBroadcast(broadcast_id); } static void StopBroadcastNative(JNIEnv* /* env */, jobject /* object */, jint broadcast_id) { log::info(""); std::shared_lock lock(sBroadcasterInterfaceMutex); if (!sLeAudioBroadcasterInterface) { return; } sLeAudioBroadcasterInterface->StopBroadcast(broadcast_id); } static void PauseBroadcastNative(JNIEnv* /* env */, jobject /* object */, jint broadcast_id) { log::info(""); std::shared_lock lock(sBroadcasterInterfaceMutex); if (!sLeAudioBroadcasterInterface) { return; } sLeAudioBroadcasterInterface->PauseBroadcast(broadcast_id); } static void DestroyBroadcastNative(JNIEnv* /* env */, jobject /* object */, jint broadcast_id) { log::info(""); std::shared_lock lock(sBroadcasterInterfaceMutex); if (!sLeAudioBroadcasterInterface) { return; } sLeAudioBroadcasterInterface->DestroyBroadcast(broadcast_id); } static void getBroadcastMetadataNative(JNIEnv* /* env */, jobject /* object */, jint broadcast_id) { log::info(""); std::shared_lock lock(sBroadcasterInterfaceMutex); if (!sLeAudioBroadcasterInterface) { return; } sLeAudioBroadcasterInterface->GetBroadcastMetadata(broadcast_id); } static int register_com_android_bluetooth_le_audio_broadcaster(JNIEnv* env) { const JNINativeMethod methods[] = { {"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}, }; const int result = REGISTER_NATIVE_METHODS( env, "com/android/bluetooth/le_audio/LeAudioBroadcasterNativeInterface", methods); if (result != 0) { return result; } const JNIJavaMethod javaMethods[] = { {"onBroadcastCreated", "(IZ)V", &method_onBroadcastCreated}, {"onBroadcastDestroyed", "(I)V", &method_onBroadcastDestroyed}, {"onBroadcastStateChanged", "(II)V", &method_onBroadcastStateChanged}, {"onBroadcastMetadataChanged", "(ILandroid/bluetooth/BluetoothLeBroadcastMetadata;)V", &method_onBroadcastMetadataChanged}, {"onBroadcastAudioSessionCreated", "(Z)V", &method_onBroadcastAudioSessionCreated}, }; GET_JAVA_METHODS(env, "com/android/bluetooth/le_audio/LeAudioBroadcasterNativeInterface", javaMethods); const JNIJavaMethod javaArrayListMethods[] = { {"", "()V", &java_util_ArrayList.constructor}, {"add", "(Ljava/lang/Object;)Z", &java_util_ArrayList.add}, }; GET_JAVA_METHODS(env, "java/util/ArrayList", javaArrayListMethods); const JNIJavaMethod javaLeAudioCodecMethods[] = { {"", "(JIII[B)V", &android_bluetooth_BluetoothLeAudioCodecConfigMetadata.constructor}, }; GET_JAVA_METHODS(env, "android/bluetooth/BluetoothLeAudioCodecConfigMetadata", javaLeAudioCodecMethods); const JNIJavaMethod javaLeAudioContentMethods[] = { {"", "(Ljava/lang/String;Ljava/lang/String;[B)V", &android_bluetooth_BluetoothLeAudioContentMetadata.constructor}, }; GET_JAVA_METHODS(env, "android/bluetooth/BluetoothLeAudioContentMetadata", javaLeAudioContentMethods); const JNIJavaMethod javaLeBroadcastChannelMethods[] = { {"", "(ZILandroid/bluetooth/BluetoothLeAudioCodecConfigMetadata;)V", &android_bluetooth_BluetoothLeBroadcastChannel.constructor}, }; GET_JAVA_METHODS(env, "android/bluetooth/BluetoothLeBroadcastChannel", javaLeBroadcastChannelMethods); const JNIJavaMethod javaLeBroadcastSubgroupMethods[] = { {"", "(JLandroid/bluetooth/BluetoothLeAudioCodecConfigMetadata;" "Landroid/bluetooth/BluetoothLeAudioContentMetadata;" "Ljava/util/List;)V", &android_bluetooth_BluetoothLeBroadcastSubgroup.constructor}, }; GET_JAVA_METHODS(env, "android/bluetooth/BluetoothLeBroadcastSubgroup", javaLeBroadcastSubgroupMethods); const JNIJavaMethod javaBluetoothDevieceMethods[] = { {"", "(Ljava/lang/String;I)V", &android_bluetooth_BluetoothDevice.constructor}, }; GET_JAVA_METHODS(env, "android/bluetooth/BluetoothDevice", javaBluetoothDevieceMethods); const JNIJavaMethod javaLeBroadcastMetadataMethods[] = { {"", "(ILandroid/bluetooth/BluetoothDevice;IIIZZLjava/lang/String;" "[BIIILandroid/bluetooth/BluetoothLeAudioContentMetadata;" "Ljava/util/List;)V", &android_bluetooth_BluetoothLeBroadcastMetadata.constructor}, }; GET_JAVA_METHODS(env, "android/bluetooth/BluetoothLeBroadcastMetadata", javaLeBroadcastMetadataMethods); return 0; } int register_com_android_bluetooth_le_audio(JNIEnv* env) { const JNINativeMethod methods[] = { {"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}, {"setUnicastMonitorModeNative", "(IZ)V", (void*)setUnicastMonitorModeNative}, {"sendAudioProfilePreferencesNative", "(IZZ)V", (void*)sendAudioProfilePreferencesNative}, {"setGroupAllowedContextMaskNative", "(III)V", (void*)setGroupAllowedContextMaskNative}, }; const int result = REGISTER_NATIVE_METHODS( env, "com/android/bluetooth/le_audio/LeAudioNativeInterface", methods); if (result != 0) { return result; } const JNIJavaMethod javaMethods[] = { {"onGroupStatus", "(II)V", &method_onGroupStatus}, {"onGroupNodeStatus", "([BII)V", &method_onGroupNodeStatus}, {"onAudioConf", "(IIIII)V", &method_onAudioConf}, {"onSinkAudioLocationAvailable", "([BI)V", &method_onSinkAudioLocationAvailable}, {"onInitialized", "()V", &method_onInitialized, true}, {"onConnectionStateChanged", "(I[B)V", &method_onConnectionStateChanged}, {"onAudioLocalCodecCapabilities", "([Landroid/bluetooth/BluetoothLeAudioCodecConfig;" "[Landroid/bluetooth/BluetoothLeAudioCodecConfig;)V", &method_onAudioLocalCodecCapabilities}, {"onAudioGroupCurrentCodecConf", "(ILandroid/bluetooth/BluetoothLeAudioCodecConfig;" "Landroid/bluetooth/BluetoothLeAudioCodecConfig;)V", &method_onAudioGroupCurrentCodecConf}, {"onAudioGroupSelectableCodecConf", "(I[Landroid/bluetooth/BluetoothLeAudioCodecConfig;" "[Landroid/bluetooth/BluetoothLeAudioCodecConfig;)V", &method_onAudioGroupSelectableCodecConf}, {"onHealthBasedRecommendationAction", "([BI)V", &method_onHealthBasedRecommendationAction}, {"onHealthBasedGroupRecommendationAction", "(II)V", &method_onHealthBasedGroupRecommendationAction}, {"onUnicastMonitorModeStatus", "(II)V", &method_onUnicastMonitorModeStatus}, {"onGroupStreamStatus", "(II)V", &method_onGroupStreamStatus}, }; GET_JAVA_METHODS(env, "com/android/bluetooth/le_audio/LeAudioNativeInterface", javaMethods); const JNIJavaMethod javaLeAudioCodecMethods[] = { {"", "(IIIIIIIII)V", &android_bluetooth_BluetoothLeAudioCodecConfig.constructor}, {"getCodecType", "()I", &android_bluetooth_BluetoothLeAudioCodecConfig.getCodecType}, {"getSampleRate", "()I", &android_bluetooth_BluetoothLeAudioCodecConfig.getSampleRate}, {"getBitsPerSample", "()I", &android_bluetooth_BluetoothLeAudioCodecConfig.getBitsPerSample}, {"getChannelCount", "()I", &android_bluetooth_BluetoothLeAudioCodecConfig.getChannelCount}, {"getFrameDuration", "()I", &android_bluetooth_BluetoothLeAudioCodecConfig.getFrameDuration}, {"getOctetsPerFrame", "()I", &android_bluetooth_BluetoothLeAudioCodecConfig.getOctetsPerFrame}, {"getCodecPriority", "()I", &android_bluetooth_BluetoothLeAudioCodecConfig.getCodecPriority}, }; GET_JAVA_METHODS(env, "android/bluetooth/BluetoothLeAudioCodecConfig", javaLeAudioCodecMethods); return register_com_android_bluetooth_le_audio_broadcaster(env); } } // namespace android