Loading android/app/jni/com_android_bluetooth_le_audio.cpp +266 −2 Original line number Diff line number Diff line Loading @@ -27,11 +27,16 @@ #include "com_android_bluetooth.h" #include "hardware/bt_le_audio.h" using bluetooth::le_audio::BroadcastAudioProfile; 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; Loading Loading @@ -299,7 +304,6 @@ static jboolean groupAddNodeNative(JNIEnv* env, jobject object, jint group_id, static jboolean groupRemoveNodeNative(JNIEnv* env, jobject object, jint group_id, jbyteArray address) { if (!sLeAudioClientInterface) { LOG(ERROR) << __func__ << ": Failed to get the Bluetooth LeAudio Interface"; return JNI_FALSE; Loading Loading @@ -340,9 +344,269 @@ static JNINativeMethod sMethods[] = { {"groupSetActiveNative", "(I)V", (void*)groupSetActiveNative}, }; /* Le Audio Broadcaster */ static jmethodID method_onBroadcastCreated; static jmethodID method_onBroadcastDestroyed; static jmethodID method_onBroadcastStateChanged; static jmethodID method_onBroadcastId; static LeAudioBroadcasterInterface* sLeAudioBroadcasterInterface = nullptr; static std::shared_timed_mutex sBroadcasterInterfaceMutex; static jobject sBroadcasterCallbacksObj = nullptr; static std::shared_timed_mutex sBroadcasterCallbacksMutex; class LeAudioBroadcasterCallbacksImpl : public LeAudioBroadcasterCallbacks { public: ~LeAudioBroadcasterCallbacksImpl() = default; void OnBroadcastCreated(uint8_t instance_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)instance_id, success ? JNI_TRUE : JNI_FALSE); } void OnBroadcastDestroyed(uint8_t instance_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)instance_id); } void OnBroadcastStateChanged(uint8_t instance_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)instance_id, (jint) static_cast<std::underlying_type<BroadcastState>::type>(state)); } void OnBroadcastId(uint8_t instance_id, const BroadcastId& broadcast_id) override { LOG(INFO) << __func__; std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterCallbacksMutex); CallbackEnv sCallbackEnv(__func__); // broadcast_id int field_size = broadcast_id.size(); ScopedLocalRef<jbyteArray> serialized_broadcast_id( sCallbackEnv.get(), sCallbackEnv->NewByteArray(field_size)); if (!serialized_broadcast_id.get()) { LOG(ERROR) << "Failed to allocate new jbyteArray broadcast_id for the " "announcement"; return; } sCallbackEnv->SetByteArrayRegion(serialized_broadcast_id.get(), 0, field_size, (jbyte*)broadcast_id.data()); if (!sCallbackEnv.valid() || sBroadcasterCallbacksObj == nullptr) return; sCallbackEnv->CallVoidMethod(sBroadcasterCallbacksObj, method_onBroadcastId, (jint)instance_id, serialized_broadcast_id.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_onBroadcastId = env->GetMethodID(clazz, "onBroadcastId", "(I[B)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; } 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; } if (sLeAudioBroadcasterInterface != nullptr) { sLeAudioBroadcasterInterface->Cleanup(); sLeAudioBroadcasterInterface = nullptr; } if (sBroadcasterCallbacksObj != nullptr) { env->DeleteGlobalRef(sBroadcasterCallbacksObj); sBroadcasterCallbacksObj = nullptr; } } static void CreateBroadcastNative(JNIEnv* env, jobject object, jbyteArray metadata, jint audio_profile, jbyteArray broadcast_code) { LOG(INFO) << __func__; std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex); if (!sLeAudioBroadcasterInterface) return; std::array<uint8_t, 16> code_array; if (broadcast_code) env->GetByteArrayRegion(broadcast_code, 0, 16, (jbyte*)code_array.data()); jbyte* meta = env->GetByteArrayElements(metadata, nullptr); sLeAudioBroadcasterInterface->CreateBroadcast( std::vector<uint8_t>(meta, meta + env->GetArrayLength(metadata)), static_cast<BroadcastAudioProfile>(audio_profile), broadcast_code ? std::optional<std::array<uint8_t, 16>>(code_array) : std::nullopt); env->ReleaseByteArrayElements(metadata, meta, 0); } static void UpdateMetadataNative(JNIEnv* env, jobject object, jint instance_id, jbyteArray metadata) { jbyte* meta = env->GetByteArrayElements(metadata, nullptr); sLeAudioBroadcasterInterface->UpdateMetadata( instance_id, std::vector<uint8_t>(meta, meta + env->GetArrayLength(metadata))); env->ReleaseByteArrayElements(metadata, meta, 0); } static void StartBroadcastNative(JNIEnv* env, jobject object, jint instance_id) { LOG(INFO) << __func__; std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex); if (!sLeAudioBroadcasterInterface) return; sLeAudioBroadcasterInterface->StartBroadcast(instance_id); } static void StopBroadcastNative(JNIEnv* env, jobject object, jint instance_id) { LOG(INFO) << __func__; std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex); if (!sLeAudioBroadcasterInterface) return; sLeAudioBroadcasterInterface->StopBroadcast(instance_id); } static void PauseBroadcastNative(JNIEnv* env, jobject object, jint instance_id) { LOG(INFO) << __func__; std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex); if (!sLeAudioBroadcasterInterface) return; sLeAudioBroadcasterInterface->PauseBroadcast(instance_id); } static void DestroyBroadcastNative(JNIEnv* env, jobject object, jint instance_id) { LOG(INFO) << __func__; std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex); if (!sLeAudioBroadcasterInterface) return; sLeAudioBroadcasterInterface->DestroyBroadcast(instance_id); } static void GetBroadcastIdNative(JNIEnv* env, jobject object, jint instance_id) { LOG(INFO) << __func__; std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex); if (!sLeAudioBroadcasterInterface) return; sLeAudioBroadcasterInterface->GetBroadcastId(instance_id); } static void GetAllBroadcastStatesNative(JNIEnv* env, jobject object) { LOG(INFO) << __func__; std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex); if (!sLeAudioBroadcasterInterface) return; sLeAudioBroadcasterInterface->GetAllBroadcastStates(); } static JNINativeMethod sBroadcasterMethods[] = { {"classInitNative", "()V", (void*)BroadcasterClassInitNative}, {"initNative", "()V", (void*)BroadcasterInitNative}, {"stopNative", "()V", (void*)BroadcasterStopNative}, {"cleanupNative", "()V", (void*)BroadcasterCleanupNative}, {"createBroadcastNative", "([BI[B)V", (void*)CreateBroadcastNative}, {"updateMetadataNative", "(I[B)V", (void*)UpdateMetadataNative}, {"startBroadcastNative", "(I)V", (void*)StartBroadcastNative}, {"stopBroadcastNative", "(I)V", (void*)StopBroadcastNative}, {"pauseBroadcastNative", "(I)V", (void*)PauseBroadcastNative}, {"destroyBroadcastNative", "(I)V", (void*)DestroyBroadcastNative}, {"getBroadcastIdNative", "(I)V", (void*)GetBroadcastIdNative}, {"getAllBroadcastStatesNative", "()V", (void*)GetAllBroadcastStatesNative}, }; int register_com_android_bluetooth_le_audio(JNIEnv* env) { return jniRegisterNativeMethods( 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 android/app/src/com/android/bluetooth/le_audio/LeAudioBroadcasterNativeInterface.java 0 → 100644 +260 −0 Original line number Diff line number Diff line /* * Copyright 2021 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. */ /* * Defines the native interface that is used by state machine/service to * send or receive messages from the native stack. This file is registered * for the native methods in the corresponding JNI C++ file. */ package com.android.bluetooth.le_audio; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.util.Log; import com.android.bluetooth.Utils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; /** * LeAudio Native Interface to/from JNI. */ public class LeAudioBroadcasterNativeInterface { private static final String TAG = "LeAudioBroadcasterNativeInterface"; private static final boolean DBG = true; private BluetoothAdapter mAdapter; @GuardedBy("INSTANCE_LOCK") private static LeAudioBroadcasterNativeInterface sInstance; private static final Object INSTANCE_LOCK = new Object(); static { classInitNative(); } private LeAudioBroadcasterNativeInterface() { mAdapter = BluetoothAdapter.getDefaultAdapter(); if (mAdapter == null) { Log.wtfStack(TAG, "No Bluetooth Adapter Available"); } } /** * Get singleton instance. */ public static LeAudioBroadcasterNativeInterface getInstance() { synchronized (INSTANCE_LOCK) { if (sInstance == null) { sInstance = new LeAudioBroadcasterNativeInterface(); } return sInstance; } } private void sendMessageToService(LeAudioStackEvent event) { LeAudioService service = LeAudioService.getLeAudioService(); if (service != null) { service.messageFromNative(event); } else { Log.e(TAG, "Event ignored, service not available: " + event); } } @VisibleForTesting public BluetoothDevice getDevice(byte[] address) { return mAdapter.getRemoteDevice(address); } // Callbacks from the native stack back into the Java framework. @VisibleForTesting public void onBroadcastCreated(int instanceId, boolean success) { if (DBG) { Log.d(TAG, "onBroadcastCreated: instanceId=" + instanceId); } LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_CREATED); event.valueInt1 = instanceId; event.valueBool1 = success; sendMessageToService(event); } @VisibleForTesting public void onBroadcastDestroyed(int instanceId) { if (DBG) { Log.d(TAG, "onBroadcastDestroyed: instanceId=" + instanceId); } LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_DESTROYED); event.valueInt1 = instanceId; sendMessageToService(event); } @VisibleForTesting public void onBroadcastStateChanged(int instanceId, int state) { if (DBG) { Log.d(TAG, "onBroadcastStateChanged: instanceId=" + instanceId + " state=" + state); } LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE); /* NOTICE: This is a fake device to satisfy Audio Manager in the upper * layers which needs a device instance to route audio streams to the * proper module (here it's Bluetooth). Broadcast has no concept of a * destination or peer device therefore this fake device was created. * For now it's only important that this device is a Bluetooth device. */ event.device = getDevice(Utils.getBytesFromAddress("FF:FF:FF:FF:FF:FF")); event.valueInt1 = instanceId; event.valueInt2 = state; sendMessageToService(event); } @VisibleForTesting public void onBroadcastId(int instanceId, byte[] broadcastId) { LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_ID); event.valueInt1 = instanceId; event.valueByte1 = broadcastId; if (DBG) { Log.d(TAG, "onBroadcastId: " + event); } sendMessageToService(event); } /** * Initializes the native interface. * * priorities to configure. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void init() { initNative(); } /** * Stop the Broadcast Service. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void stop() { stopNative(); } /** * Cleanup the native interface. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void cleanup() { cleanupNative(); } /** * Creates LeAudio Broadcast instance. * * @param metadata metadata buffer with TLVs * @param audioProfile broadcast audio profile * @param broadcastCode optional code if broadcast should be encrypted */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void createBroadcast(byte[] metadata, int audioProfile, byte[] broadcastCode) { createBroadcastNative(metadata, audioProfile, broadcastCode); } /** * Update LeAudio Broadcast instance metadata. * * @param instanceId broadcast instance identifier * @param metadata metadata buffer with TLVs */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void updateMetadata(int instanceId, byte[] metadata) { updateMetadataNative(instanceId, metadata); } /** * Start LeAudio Broadcast instance. * * @param instanceId broadcast instance identifier */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void startBroadcast(int instanceId) { startBroadcastNative(instanceId); } /** * Stop LeAudio Broadcast instance. * * @param instanceId broadcast instance identifier */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void stopBroadcast(int instanceId) { stopBroadcastNative(instanceId); } /** * Pause LeAudio Broadcast instance. * * @param instanceId broadcast instance identifier */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void pauseBroadcast(int instanceId) { pauseBroadcastNative(instanceId); } /** * Destroy LeAudio Broadcast instance. * * @param instanceId broadcast instance identifier */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void destroyBroadcast(int instanceId) { destroyBroadcastNative(instanceId); } /** * Get LeAudio Broadcast instance advertising address. * * @param instanceId broadcast instance identifier */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void getBroadcastId(int instanceId) { getBroadcastIdNative(instanceId); } /** * Get all LeAudio Broadcast instance states. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void getAllBroadcastStates() { getAllBroadcastStatesNative(); } // Native methods that call into the JNI interface private static native void classInitNative(); private native void initNative(); private native void stopNative(); private native void cleanupNative(); private native void createBroadcastNative(byte[] metadata, int profile, byte[] broadcastCode); private native void updateMetadataNative(int instanceId, byte[] metadata); private native void startBroadcastNative(int instanceId); private native void stopBroadcastNative(int instanceId); private native void pauseBroadcastNative(int instanceId); private native void destroyBroadcastNative(int instanceId); private native void getBroadcastIdNative(int instanceId); private native void getAllBroadcastStatesNative(); } Loading
android/app/jni/com_android_bluetooth_le_audio.cpp +266 −2 Original line number Diff line number Diff line Loading @@ -27,11 +27,16 @@ #include "com_android_bluetooth.h" #include "hardware/bt_le_audio.h" using bluetooth::le_audio::BroadcastAudioProfile; 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; Loading Loading @@ -299,7 +304,6 @@ static jboolean groupAddNodeNative(JNIEnv* env, jobject object, jint group_id, static jboolean groupRemoveNodeNative(JNIEnv* env, jobject object, jint group_id, jbyteArray address) { if (!sLeAudioClientInterface) { LOG(ERROR) << __func__ << ": Failed to get the Bluetooth LeAudio Interface"; return JNI_FALSE; Loading Loading @@ -340,9 +344,269 @@ static JNINativeMethod sMethods[] = { {"groupSetActiveNative", "(I)V", (void*)groupSetActiveNative}, }; /* Le Audio Broadcaster */ static jmethodID method_onBroadcastCreated; static jmethodID method_onBroadcastDestroyed; static jmethodID method_onBroadcastStateChanged; static jmethodID method_onBroadcastId; static LeAudioBroadcasterInterface* sLeAudioBroadcasterInterface = nullptr; static std::shared_timed_mutex sBroadcasterInterfaceMutex; static jobject sBroadcasterCallbacksObj = nullptr; static std::shared_timed_mutex sBroadcasterCallbacksMutex; class LeAudioBroadcasterCallbacksImpl : public LeAudioBroadcasterCallbacks { public: ~LeAudioBroadcasterCallbacksImpl() = default; void OnBroadcastCreated(uint8_t instance_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)instance_id, success ? JNI_TRUE : JNI_FALSE); } void OnBroadcastDestroyed(uint8_t instance_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)instance_id); } void OnBroadcastStateChanged(uint8_t instance_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)instance_id, (jint) static_cast<std::underlying_type<BroadcastState>::type>(state)); } void OnBroadcastId(uint8_t instance_id, const BroadcastId& broadcast_id) override { LOG(INFO) << __func__; std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterCallbacksMutex); CallbackEnv sCallbackEnv(__func__); // broadcast_id int field_size = broadcast_id.size(); ScopedLocalRef<jbyteArray> serialized_broadcast_id( sCallbackEnv.get(), sCallbackEnv->NewByteArray(field_size)); if (!serialized_broadcast_id.get()) { LOG(ERROR) << "Failed to allocate new jbyteArray broadcast_id for the " "announcement"; return; } sCallbackEnv->SetByteArrayRegion(serialized_broadcast_id.get(), 0, field_size, (jbyte*)broadcast_id.data()); if (!sCallbackEnv.valid() || sBroadcasterCallbacksObj == nullptr) return; sCallbackEnv->CallVoidMethod(sBroadcasterCallbacksObj, method_onBroadcastId, (jint)instance_id, serialized_broadcast_id.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_onBroadcastId = env->GetMethodID(clazz, "onBroadcastId", "(I[B)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; } 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; } if (sLeAudioBroadcasterInterface != nullptr) { sLeAudioBroadcasterInterface->Cleanup(); sLeAudioBroadcasterInterface = nullptr; } if (sBroadcasterCallbacksObj != nullptr) { env->DeleteGlobalRef(sBroadcasterCallbacksObj); sBroadcasterCallbacksObj = nullptr; } } static void CreateBroadcastNative(JNIEnv* env, jobject object, jbyteArray metadata, jint audio_profile, jbyteArray broadcast_code) { LOG(INFO) << __func__; std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex); if (!sLeAudioBroadcasterInterface) return; std::array<uint8_t, 16> code_array; if (broadcast_code) env->GetByteArrayRegion(broadcast_code, 0, 16, (jbyte*)code_array.data()); jbyte* meta = env->GetByteArrayElements(metadata, nullptr); sLeAudioBroadcasterInterface->CreateBroadcast( std::vector<uint8_t>(meta, meta + env->GetArrayLength(metadata)), static_cast<BroadcastAudioProfile>(audio_profile), broadcast_code ? std::optional<std::array<uint8_t, 16>>(code_array) : std::nullopt); env->ReleaseByteArrayElements(metadata, meta, 0); } static void UpdateMetadataNative(JNIEnv* env, jobject object, jint instance_id, jbyteArray metadata) { jbyte* meta = env->GetByteArrayElements(metadata, nullptr); sLeAudioBroadcasterInterface->UpdateMetadata( instance_id, std::vector<uint8_t>(meta, meta + env->GetArrayLength(metadata))); env->ReleaseByteArrayElements(metadata, meta, 0); } static void StartBroadcastNative(JNIEnv* env, jobject object, jint instance_id) { LOG(INFO) << __func__; std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex); if (!sLeAudioBroadcasterInterface) return; sLeAudioBroadcasterInterface->StartBroadcast(instance_id); } static void StopBroadcastNative(JNIEnv* env, jobject object, jint instance_id) { LOG(INFO) << __func__; std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex); if (!sLeAudioBroadcasterInterface) return; sLeAudioBroadcasterInterface->StopBroadcast(instance_id); } static void PauseBroadcastNative(JNIEnv* env, jobject object, jint instance_id) { LOG(INFO) << __func__; std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex); if (!sLeAudioBroadcasterInterface) return; sLeAudioBroadcasterInterface->PauseBroadcast(instance_id); } static void DestroyBroadcastNative(JNIEnv* env, jobject object, jint instance_id) { LOG(INFO) << __func__; std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex); if (!sLeAudioBroadcasterInterface) return; sLeAudioBroadcasterInterface->DestroyBroadcast(instance_id); } static void GetBroadcastIdNative(JNIEnv* env, jobject object, jint instance_id) { LOG(INFO) << __func__; std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex); if (!sLeAudioBroadcasterInterface) return; sLeAudioBroadcasterInterface->GetBroadcastId(instance_id); } static void GetAllBroadcastStatesNative(JNIEnv* env, jobject object) { LOG(INFO) << __func__; std::shared_lock<std::shared_timed_mutex> lock(sBroadcasterInterfaceMutex); if (!sLeAudioBroadcasterInterface) return; sLeAudioBroadcasterInterface->GetAllBroadcastStates(); } static JNINativeMethod sBroadcasterMethods[] = { {"classInitNative", "()V", (void*)BroadcasterClassInitNative}, {"initNative", "()V", (void*)BroadcasterInitNative}, {"stopNative", "()V", (void*)BroadcasterStopNative}, {"cleanupNative", "()V", (void*)BroadcasterCleanupNative}, {"createBroadcastNative", "([BI[B)V", (void*)CreateBroadcastNative}, {"updateMetadataNative", "(I[B)V", (void*)UpdateMetadataNative}, {"startBroadcastNative", "(I)V", (void*)StartBroadcastNative}, {"stopBroadcastNative", "(I)V", (void*)StopBroadcastNative}, {"pauseBroadcastNative", "(I)V", (void*)PauseBroadcastNative}, {"destroyBroadcastNative", "(I)V", (void*)DestroyBroadcastNative}, {"getBroadcastIdNative", "(I)V", (void*)GetBroadcastIdNative}, {"getAllBroadcastStatesNative", "()V", (void*)GetAllBroadcastStatesNative}, }; int register_com_android_bluetooth_le_audio(JNIEnv* env) { return jniRegisterNativeMethods( 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
android/app/src/com/android/bluetooth/le_audio/LeAudioBroadcasterNativeInterface.java 0 → 100644 +260 −0 Original line number Diff line number Diff line /* * Copyright 2021 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. */ /* * Defines the native interface that is used by state machine/service to * send or receive messages from the native stack. This file is registered * for the native methods in the corresponding JNI C++ file. */ package com.android.bluetooth.le_audio; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.util.Log; import com.android.bluetooth.Utils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; /** * LeAudio Native Interface to/from JNI. */ public class LeAudioBroadcasterNativeInterface { private static final String TAG = "LeAudioBroadcasterNativeInterface"; private static final boolean DBG = true; private BluetoothAdapter mAdapter; @GuardedBy("INSTANCE_LOCK") private static LeAudioBroadcasterNativeInterface sInstance; private static final Object INSTANCE_LOCK = new Object(); static { classInitNative(); } private LeAudioBroadcasterNativeInterface() { mAdapter = BluetoothAdapter.getDefaultAdapter(); if (mAdapter == null) { Log.wtfStack(TAG, "No Bluetooth Adapter Available"); } } /** * Get singleton instance. */ public static LeAudioBroadcasterNativeInterface getInstance() { synchronized (INSTANCE_LOCK) { if (sInstance == null) { sInstance = new LeAudioBroadcasterNativeInterface(); } return sInstance; } } private void sendMessageToService(LeAudioStackEvent event) { LeAudioService service = LeAudioService.getLeAudioService(); if (service != null) { service.messageFromNative(event); } else { Log.e(TAG, "Event ignored, service not available: " + event); } } @VisibleForTesting public BluetoothDevice getDevice(byte[] address) { return mAdapter.getRemoteDevice(address); } // Callbacks from the native stack back into the Java framework. @VisibleForTesting public void onBroadcastCreated(int instanceId, boolean success) { if (DBG) { Log.d(TAG, "onBroadcastCreated: instanceId=" + instanceId); } LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_CREATED); event.valueInt1 = instanceId; event.valueBool1 = success; sendMessageToService(event); } @VisibleForTesting public void onBroadcastDestroyed(int instanceId) { if (DBG) { Log.d(TAG, "onBroadcastDestroyed: instanceId=" + instanceId); } LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_DESTROYED); event.valueInt1 = instanceId; sendMessageToService(event); } @VisibleForTesting public void onBroadcastStateChanged(int instanceId, int state) { if (DBG) { Log.d(TAG, "onBroadcastStateChanged: instanceId=" + instanceId + " state=" + state); } LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_STATE); /* NOTICE: This is a fake device to satisfy Audio Manager in the upper * layers which needs a device instance to route audio streams to the * proper module (here it's Bluetooth). Broadcast has no concept of a * destination or peer device therefore this fake device was created. * For now it's only important that this device is a Bluetooth device. */ event.device = getDevice(Utils.getBytesFromAddress("FF:FF:FF:FF:FF:FF")); event.valueInt1 = instanceId; event.valueInt2 = state; sendMessageToService(event); } @VisibleForTesting public void onBroadcastId(int instanceId, byte[] broadcastId) { LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_BROADCAST_ID); event.valueInt1 = instanceId; event.valueByte1 = broadcastId; if (DBG) { Log.d(TAG, "onBroadcastId: " + event); } sendMessageToService(event); } /** * Initializes the native interface. * * priorities to configure. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void init() { initNative(); } /** * Stop the Broadcast Service. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void stop() { stopNative(); } /** * Cleanup the native interface. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void cleanup() { cleanupNative(); } /** * Creates LeAudio Broadcast instance. * * @param metadata metadata buffer with TLVs * @param audioProfile broadcast audio profile * @param broadcastCode optional code if broadcast should be encrypted */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void createBroadcast(byte[] metadata, int audioProfile, byte[] broadcastCode) { createBroadcastNative(metadata, audioProfile, broadcastCode); } /** * Update LeAudio Broadcast instance metadata. * * @param instanceId broadcast instance identifier * @param metadata metadata buffer with TLVs */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void updateMetadata(int instanceId, byte[] metadata) { updateMetadataNative(instanceId, metadata); } /** * Start LeAudio Broadcast instance. * * @param instanceId broadcast instance identifier */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void startBroadcast(int instanceId) { startBroadcastNative(instanceId); } /** * Stop LeAudio Broadcast instance. * * @param instanceId broadcast instance identifier */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void stopBroadcast(int instanceId) { stopBroadcastNative(instanceId); } /** * Pause LeAudio Broadcast instance. * * @param instanceId broadcast instance identifier */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void pauseBroadcast(int instanceId) { pauseBroadcastNative(instanceId); } /** * Destroy LeAudio Broadcast instance. * * @param instanceId broadcast instance identifier */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void destroyBroadcast(int instanceId) { destroyBroadcastNative(instanceId); } /** * Get LeAudio Broadcast instance advertising address. * * @param instanceId broadcast instance identifier */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void getBroadcastId(int instanceId) { getBroadcastIdNative(instanceId); } /** * Get all LeAudio Broadcast instance states. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public void getAllBroadcastStates() { getAllBroadcastStatesNative(); } // Native methods that call into the JNI interface private static native void classInitNative(); private native void initNative(); private native void stopNative(); private native void cleanupNative(); private native void createBroadcastNative(byte[] metadata, int profile, byte[] broadcastCode); private native void updateMetadataNative(int instanceId, byte[] metadata); private native void startBroadcastNative(int instanceId); private native void stopBroadcastNative(int instanceId); private native void pauseBroadcastNative(int instanceId); private native void destroyBroadcastNative(int instanceId); private native void getBroadcastIdNative(int instanceId); private native void getAllBroadcastStatesNative(); }