Loading android/app/jni/com_android_bluetooth.h +2 −0 Original line number Original line Diff line number Diff line Loading @@ -157,6 +157,8 @@ int register_com_android_bluetooth_hearing_aid(JNIEnv* env); int register_com_android_bluetooth_btservice_BluetoothKeystore(JNIEnv* env); int register_com_android_bluetooth_btservice_BluetoothKeystore(JNIEnv* env); int register_com_android_bluetooth_btservice_activity_attribution(JNIEnv* env); int register_com_android_bluetooth_btservice_activity_attribution(JNIEnv* env); int register_com_android_bluetooth_le_audio(JNIEnv* env); } // namespace android } // namespace android #endif /* COM_ANDROID_BLUETOOTH_H */ #endif /* COM_ANDROID_BLUETOOTH_H */ android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp +6 −0 Original line number Original line Diff line number Diff line Loading @@ -1486,5 +1486,11 @@ jint JNI_OnLoad(JavaVM* jvm, void* reserved) { return JNI_ERR; return JNI_ERR; } } status = android::register_com_android_bluetooth_le_audio(e); if (status < 0) { ALOGE("jni le_audio registration failure: %d", status); return JNI_ERR; } return JNI_VERSION_1_6; return JNI_VERSION_1_6; } } android/app/jni/com_android_bluetooth_le_audio.cpp 0 → 100644 +280 −0 Original line number Original line Diff line number Diff line /* 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" #define LOG_NDEBUG 0 #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::ConnectionState; using bluetooth::le_audio::GroupStatus; using bluetooth::le_audio::LeAudioClientCallbacks; using bluetooth::le_audio::LeAudioClientInterface; namespace android { static jmethodID method_onConnectionStateChanged; static jmethodID method_onGroupStatus; static jmethodID method_onAudioConf; static jmethodID method_onSetMemberAvailable; static LeAudioClientInterface* sLeAudioClientInterface = nullptr; static std::shared_timed_mutex interface_mutex; static jobject mCallbacksObj = nullptr; static std::shared_timed_mutex callbacks_mutex; class LeAudioClientCallbacksImpl : public LeAudioClientCallbacks { public: ~LeAudioClientCallbacksImpl() = default; void OnConnectionState(ConnectionState state, const RawAddress& bd_addr) override { LOG(INFO) << __func__ << ", state:" << int(state); 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(uint8_t group_id, GroupStatus group_status, uint8_t group_flags) 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, (jint)group_flags); } void OnAudioConf(const RawAddress& bd_addr, uint8_t direction, uint8_t group_id, uint32_t sink_audio_location, uint32_t source_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_onAudioConf, (jint)direction, (jint)group_id, (jint)sink_audio_location, (jint)source_audio_location, addr.get()); } void OnSetMemberAvailable(const RawAddress& bd_addr, uint8_t group_id) override { LOG(INFO) << __func__ << ", group id:" << int(group_id); 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_onSetMemberAvailable, addr.get(), (jint)group_id); } }; static LeAudioClientCallbacksImpl sLeAudioClientCallbacks; static void classInitNative(JNIEnv* env, jclass clazz) { method_onGroupStatus = env->GetMethodID(clazz, "onGroupStatus", "(III)V"); method_onAudioConf = env->GetMethodID(clazz, "onAudioConf", "(IIII[B)V"); method_onConnectionStateChanged = env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V"); method_onSetMemberAvailable = env->GetMethodID(clazz, "onSetMemberAvailable", "([BI)V"); } static void initNative(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 (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; } sLeAudioClientInterface = (LeAudioClientInterface*)btInf->get_profile_interface( BT_PROFILE_LE_AUDIO_ID); if (sLeAudioClientInterface == nullptr) { LOG(ERROR) << "Failed to get Bluetooth LeAudio Interface"; return; } sLeAudioClientInterface->Initialize(&sLeAudioClientCallbacks); } 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; } 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 void groupStreamNative(JNIEnv* env, jobject object, jint group_id, jint content_type) { LOG(INFO) << __func__; if (!sLeAudioClientInterface) { LOG(ERROR) << __func__ << ": Failed to get the Bluetooth LeAudio Interface"; return; } sLeAudioClientInterface->GroupStream(group_id, content_type); } static void groupSuspendNative(JNIEnv* env, jobject object, jint group_id) { LOG(INFO) << __func__; if (!sLeAudioClientInterface) { LOG(ERROR) << __func__ << ": Failed to get the Bluetooth LeAudio Interface"; return; } sLeAudioClientInterface->GroupSuspend(group_id); } static void groupStopNative(JNIEnv* env, jobject object, jint group_id) { LOG(INFO) << __func__; if (!sLeAudioClientInterface) { LOG(ERROR) << __func__ << ": Failed to get the Bluetooth LeAudio Interface"; return; } sLeAudioClientInterface->GroupStop(group_id); } static JNINativeMethod sMethods[] = { {"classInitNative", "()V", (void*)classInitNative}, {"initNative", "()V", (void*)initNative}, {"cleanupNative", "()V", (void*)cleanupNative}, {"connectLeAudioNative", "([B)Z", (void*)connectLeAudioNative}, {"disconnectLeAudioNative", "([B)Z", (void*)disconnectLeAudioNative}, {"groupStreamNative", "(II)V", (void*)groupStreamNative}, {"groupSuspendNative", "(I)V", (void*)groupSuspendNative}, {"groupStopNative", "(I)V", (void*)groupStopNative}, }; int register_com_android_bluetooth_le_audio(JNIEnv* env) { return jniRegisterNativeMethods( env, "com/android/bluetooth/le_audio/LeAudioNativeInterface", sMethods, NELEM(sMethods)); } } // namespace android android/app/src/com/android/bluetooth/le_audio/LeAudioNativeInterface.java 0 → 100644 +217 −0 Original line number Original line Diff line number Diff line /* * Copyright 2020 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; /** * LeAudio Native Interface to/from JNI. */ public class LeAudioNativeInterface { private static final String TAG = "LeAudioNativeInterface"; private static final boolean DBG = true; private BluetoothAdapter mAdapter; @GuardedBy("INSTANCE_LOCK") private static LeAudioNativeInterface sInstance; private static final Object INSTANCE_LOCK = new Object(); static { classInitNative(); } private LeAudioNativeInterface() { mAdapter = BluetoothAdapter.getDefaultAdapter(); if (mAdapter == null) { Log.wtfStack(TAG, "No Bluetooth Adapter Available"); } } /** * Get singleton instance. */ public static LeAudioNativeInterface getInstance() { synchronized (INSTANCE_LOCK) { if (sInstance == null) { sInstance = new LeAudioNativeInterface(); } return sInstance; } } private byte[] getByteAddress(BluetoothDevice device) { if (device == null) { return Utils.getBytesFromAddress("00:00:00:00:00:00"); } return Utils.getBytesFromAddress(device.getAddress()); } 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); } } private BluetoothDevice getDevice(byte[] address) { return mAdapter.getRemoteDevice(address); } // Callbacks from the native stack back into the Java framework. // All callbacks are routed via the Service which will disambiguate which // state machine the message should be routed to. private void onConnectionStateChanged(int state, byte[] address) { LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); event.device = getDevice(address); event.valueInt1 = state; if (DBG) { Log.d(TAG, "onConnectionStateChanged: " + event); } sendMessageToService(event); } // Callbacks from the native stack back into the Java framework. // All callbacks are routed via the Service which will disambiguate which // state machine the message should be routed to. private void onSetMemberAvailable(byte[] address, int groupId) { LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_SET_MEMBER_AVAILABLE); event.device = getDevice(address); event.valueInt1 = groupId; if (DBG) { Log.d(TAG, "onSetMemberAvailable: " + event); } sendMessageToService(event); } private void onGroupStatus(int groupId, int groupStatus, int groupFlags) { LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED); event.valueInt1 = groupId; event.valueInt2 = groupStatus; event.valueInt3 = groupFlags; event.device = null; if (DBG) { Log.d(TAG, "onGroupStatus: " + event); } sendMessageToService(event); } private void onAudioConf(int direction, int groupId, int sinkAudioLocation, int sourceAudioLocation, byte[] address) { LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); event.valueInt1 = direction; event.valueInt2 = groupId; event.valueInt3 = sinkAudioLocation; event.valueInt4 = sourceAudioLocation; event.device = getDevice(address); if (DBG) { Log.d(TAG, "onAudioConf: " + event); } sendMessageToService(event); } /** * Initializes the native interface. * * priorities to configure. */ public void init() { initNative(); } /** * Cleanup the native interface. */ public void cleanup() { cleanupNative(); } /** * Initiates LeAudio connection to a remote device. * * @param device the remote device * @return true on success, otherwise false. */ public boolean connectLeAudio(BluetoothDevice device) { return connectLeAudioNative(getByteAddress(device)); } /** * Disconnects LeAudio from a remote device. * * @param device the remote device * @return true on success, otherwise false. */ public boolean disconnectLeAudio(BluetoothDevice device) { return disconnectLeAudioNative(getByteAddress(device)); } /** * Enable content streaming. * @param groupId group identifier * @param contentType type of content to stream */ public void groupStream(int groupId, int contentType) { groupStreamNative(groupId, contentType); } /** * Suspend content streaming. * @param groupId group identifier */ public void groupSuspend(int groupId) { groupSuspendNative(groupId); } /** * Stop all content streaming. * @param groupId group identifier * TODO: Maybe we should use also pass the content type argument */ public void groupStop(int groupId) { groupStopNative(groupId); } // Native methods that call into the JNI interface private static native void classInitNative(); private native void initNative(); private native void cleanupNative(); private native boolean connectLeAudioNative(byte[] address); private native boolean disconnectLeAudioNative(byte[] address); private native void groupStreamNative(int groupId, int contentType); private native void groupSuspendNative(int groupId); private native void groupStopNative(int groupId); } android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +442 −14 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
android/app/jni/com_android_bluetooth.h +2 −0 Original line number Original line Diff line number Diff line Loading @@ -157,6 +157,8 @@ int register_com_android_bluetooth_hearing_aid(JNIEnv* env); int register_com_android_bluetooth_btservice_BluetoothKeystore(JNIEnv* env); int register_com_android_bluetooth_btservice_BluetoothKeystore(JNIEnv* env); int register_com_android_bluetooth_btservice_activity_attribution(JNIEnv* env); int register_com_android_bluetooth_btservice_activity_attribution(JNIEnv* env); int register_com_android_bluetooth_le_audio(JNIEnv* env); } // namespace android } // namespace android #endif /* COM_ANDROID_BLUETOOTH_H */ #endif /* COM_ANDROID_BLUETOOTH_H */
android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp +6 −0 Original line number Original line Diff line number Diff line Loading @@ -1486,5 +1486,11 @@ jint JNI_OnLoad(JavaVM* jvm, void* reserved) { return JNI_ERR; return JNI_ERR; } } status = android::register_com_android_bluetooth_le_audio(e); if (status < 0) { ALOGE("jni le_audio registration failure: %d", status); return JNI_ERR; } return JNI_VERSION_1_6; return JNI_VERSION_1_6; } }
android/app/jni/com_android_bluetooth_le_audio.cpp 0 → 100644 +280 −0 Original line number Original line Diff line number Diff line /* 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" #define LOG_NDEBUG 0 #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::ConnectionState; using bluetooth::le_audio::GroupStatus; using bluetooth::le_audio::LeAudioClientCallbacks; using bluetooth::le_audio::LeAudioClientInterface; namespace android { static jmethodID method_onConnectionStateChanged; static jmethodID method_onGroupStatus; static jmethodID method_onAudioConf; static jmethodID method_onSetMemberAvailable; static LeAudioClientInterface* sLeAudioClientInterface = nullptr; static std::shared_timed_mutex interface_mutex; static jobject mCallbacksObj = nullptr; static std::shared_timed_mutex callbacks_mutex; class LeAudioClientCallbacksImpl : public LeAudioClientCallbacks { public: ~LeAudioClientCallbacksImpl() = default; void OnConnectionState(ConnectionState state, const RawAddress& bd_addr) override { LOG(INFO) << __func__ << ", state:" << int(state); 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(uint8_t group_id, GroupStatus group_status, uint8_t group_flags) 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, (jint)group_flags); } void OnAudioConf(const RawAddress& bd_addr, uint8_t direction, uint8_t group_id, uint32_t sink_audio_location, uint32_t source_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_onAudioConf, (jint)direction, (jint)group_id, (jint)sink_audio_location, (jint)source_audio_location, addr.get()); } void OnSetMemberAvailable(const RawAddress& bd_addr, uint8_t group_id) override { LOG(INFO) << __func__ << ", group id:" << int(group_id); 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_onSetMemberAvailable, addr.get(), (jint)group_id); } }; static LeAudioClientCallbacksImpl sLeAudioClientCallbacks; static void classInitNative(JNIEnv* env, jclass clazz) { method_onGroupStatus = env->GetMethodID(clazz, "onGroupStatus", "(III)V"); method_onAudioConf = env->GetMethodID(clazz, "onAudioConf", "(IIII[B)V"); method_onConnectionStateChanged = env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V"); method_onSetMemberAvailable = env->GetMethodID(clazz, "onSetMemberAvailable", "([BI)V"); } static void initNative(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 (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; } sLeAudioClientInterface = (LeAudioClientInterface*)btInf->get_profile_interface( BT_PROFILE_LE_AUDIO_ID); if (sLeAudioClientInterface == nullptr) { LOG(ERROR) << "Failed to get Bluetooth LeAudio Interface"; return; } sLeAudioClientInterface->Initialize(&sLeAudioClientCallbacks); } 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; } 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 void groupStreamNative(JNIEnv* env, jobject object, jint group_id, jint content_type) { LOG(INFO) << __func__; if (!sLeAudioClientInterface) { LOG(ERROR) << __func__ << ": Failed to get the Bluetooth LeAudio Interface"; return; } sLeAudioClientInterface->GroupStream(group_id, content_type); } static void groupSuspendNative(JNIEnv* env, jobject object, jint group_id) { LOG(INFO) << __func__; if (!sLeAudioClientInterface) { LOG(ERROR) << __func__ << ": Failed to get the Bluetooth LeAudio Interface"; return; } sLeAudioClientInterface->GroupSuspend(group_id); } static void groupStopNative(JNIEnv* env, jobject object, jint group_id) { LOG(INFO) << __func__; if (!sLeAudioClientInterface) { LOG(ERROR) << __func__ << ": Failed to get the Bluetooth LeAudio Interface"; return; } sLeAudioClientInterface->GroupStop(group_id); } static JNINativeMethod sMethods[] = { {"classInitNative", "()V", (void*)classInitNative}, {"initNative", "()V", (void*)initNative}, {"cleanupNative", "()V", (void*)cleanupNative}, {"connectLeAudioNative", "([B)Z", (void*)connectLeAudioNative}, {"disconnectLeAudioNative", "([B)Z", (void*)disconnectLeAudioNative}, {"groupStreamNative", "(II)V", (void*)groupStreamNative}, {"groupSuspendNative", "(I)V", (void*)groupSuspendNative}, {"groupStopNative", "(I)V", (void*)groupStopNative}, }; int register_com_android_bluetooth_le_audio(JNIEnv* env) { return jniRegisterNativeMethods( env, "com/android/bluetooth/le_audio/LeAudioNativeInterface", sMethods, NELEM(sMethods)); } } // namespace android
android/app/src/com/android/bluetooth/le_audio/LeAudioNativeInterface.java 0 → 100644 +217 −0 Original line number Original line Diff line number Diff line /* * Copyright 2020 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; /** * LeAudio Native Interface to/from JNI. */ public class LeAudioNativeInterface { private static final String TAG = "LeAudioNativeInterface"; private static final boolean DBG = true; private BluetoothAdapter mAdapter; @GuardedBy("INSTANCE_LOCK") private static LeAudioNativeInterface sInstance; private static final Object INSTANCE_LOCK = new Object(); static { classInitNative(); } private LeAudioNativeInterface() { mAdapter = BluetoothAdapter.getDefaultAdapter(); if (mAdapter == null) { Log.wtfStack(TAG, "No Bluetooth Adapter Available"); } } /** * Get singleton instance. */ public static LeAudioNativeInterface getInstance() { synchronized (INSTANCE_LOCK) { if (sInstance == null) { sInstance = new LeAudioNativeInterface(); } return sInstance; } } private byte[] getByteAddress(BluetoothDevice device) { if (device == null) { return Utils.getBytesFromAddress("00:00:00:00:00:00"); } return Utils.getBytesFromAddress(device.getAddress()); } 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); } } private BluetoothDevice getDevice(byte[] address) { return mAdapter.getRemoteDevice(address); } // Callbacks from the native stack back into the Java framework. // All callbacks are routed via the Service which will disambiguate which // state machine the message should be routed to. private void onConnectionStateChanged(int state, byte[] address) { LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); event.device = getDevice(address); event.valueInt1 = state; if (DBG) { Log.d(TAG, "onConnectionStateChanged: " + event); } sendMessageToService(event); } // Callbacks from the native stack back into the Java framework. // All callbacks are routed via the Service which will disambiguate which // state machine the message should be routed to. private void onSetMemberAvailable(byte[] address, int groupId) { LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_SET_MEMBER_AVAILABLE); event.device = getDevice(address); event.valueInt1 = groupId; if (DBG) { Log.d(TAG, "onSetMemberAvailable: " + event); } sendMessageToService(event); } private void onGroupStatus(int groupId, int groupStatus, int groupFlags) { LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_GROUP_STATUS_CHANGED); event.valueInt1 = groupId; event.valueInt2 = groupStatus; event.valueInt3 = groupFlags; event.device = null; if (DBG) { Log.d(TAG, "onGroupStatus: " + event); } sendMessageToService(event); } private void onAudioConf(int direction, int groupId, int sinkAudioLocation, int sourceAudioLocation, byte[] address) { LeAudioStackEvent event = new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_AUDIO_CONF_CHANGED); event.valueInt1 = direction; event.valueInt2 = groupId; event.valueInt3 = sinkAudioLocation; event.valueInt4 = sourceAudioLocation; event.device = getDevice(address); if (DBG) { Log.d(TAG, "onAudioConf: " + event); } sendMessageToService(event); } /** * Initializes the native interface. * * priorities to configure. */ public void init() { initNative(); } /** * Cleanup the native interface. */ public void cleanup() { cleanupNative(); } /** * Initiates LeAudio connection to a remote device. * * @param device the remote device * @return true on success, otherwise false. */ public boolean connectLeAudio(BluetoothDevice device) { return connectLeAudioNative(getByteAddress(device)); } /** * Disconnects LeAudio from a remote device. * * @param device the remote device * @return true on success, otherwise false. */ public boolean disconnectLeAudio(BluetoothDevice device) { return disconnectLeAudioNative(getByteAddress(device)); } /** * Enable content streaming. * @param groupId group identifier * @param contentType type of content to stream */ public void groupStream(int groupId, int contentType) { groupStreamNative(groupId, contentType); } /** * Suspend content streaming. * @param groupId group identifier */ public void groupSuspend(int groupId) { groupSuspendNative(groupId); } /** * Stop all content streaming. * @param groupId group identifier * TODO: Maybe we should use also pass the content type argument */ public void groupStop(int groupId) { groupStopNative(groupId); } // Native methods that call into the JNI interface private static native void classInitNative(); private native void initNative(); private native void cleanupNative(); private native boolean connectLeAudioNative(byte[] address); private native boolean disconnectLeAudioNative(byte[] address); private native void groupStreamNative(int groupId, int contentType); private native void groupSuspendNative(int groupId); private native void groupStopNative(int groupId); }
android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +442 −14 File changed.Preview size limit exceeded, changes collapsed. Show changes