Loading android/app/AndroidManifest.xml +9 −0 Original line number Diff line number Diff line Loading @@ -434,6 +434,15 @@ <action android:name="android.bluetooth.IBluetoothLeAudio" /> </intent-filter> </service> <service android:process="@string/process" android:name = ".csip.CsipSetCoordinatorService" android:enabled="@bool/profile_supported_csip_set_coordinator" android:exported = "true"> <intent-filter> <action android:name="android.bluetooth.IBluetoothCsipSetCoordinator" /> </intent-filter> </service> <!-- Authenticator for PBAP account. --> <service android:process="@string/process" android:name=".pbapclient.AuthenticationService" Loading android/app/jni/com_android_bluetooth.h +2 −0 Original line number Diff line number Diff line Loading @@ -161,6 +161,8 @@ int register_com_android_bluetooth_btservice_activity_attribution(JNIEnv* env); int register_com_android_bluetooth_le_audio(JNIEnv* env); int register_com_android_bluetooth_vc(JNIEnv* env); int register_com_android_bluetooth_csip_set_coordinator(JNIEnv* env); } // namespace android #endif /* COM_ANDROID_BLUETOOTH_H */ android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp +6 −0 Original line number Diff line number Diff line Loading @@ -1841,5 +1841,11 @@ jint JNI_OnLoad(JavaVM* jvm, void* reserved) { return JNI_ERR; } status = android::register_com_android_bluetooth_csip_set_coordinator(e); if (status < 0) { ALOGE("jni csis client registration failure: %d", status); return JNI_ERR; } return JNI_VERSION_1_6; } android/app/jni/com_android_bluetooth_csip_set_coordinator.cpp 0 → 100644 +296 −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. */ #define LOG_TAG "BluetoothCsipSetCoordinatorJni" #define LOG_NDEBUG 0 #include <string.h> #include <shared_mutex> #include "base/logging.h" #include "com_android_bluetooth.h" #include "hardware/bt_csis.h" using bluetooth::csis::ConnectionState; using bluetooth::csis::CsisClientCallbacks; using bluetooth::csis::CsisClientInterface; using bluetooth::csis::CsisGroupLockStatus; namespace android { static jmethodID method_onConnectionStateChanged; static jmethodID method_onDeviceAvailable; static jmethodID method_onSetMemberAvailable; static jmethodID method_onGroupLockChanged; static CsisClientInterface* sCsisClientInterface = nullptr; static std::shared_timed_mutex interface_mutex; static jobject mCallbacksObj = nullptr; static std::shared_timed_mutex callbacks_mutex; using bluetooth::Uuid; #define UUID_PARAMS(uuid) uuid_lsb(uuid), uuid_msb(uuid) static uint64_t uuid_lsb(const Uuid& uuid) { uint64_t lsb = 0; auto uu = uuid.To128BitBE(); for (int i = 8; i <= 15; i++) { lsb <<= 8; lsb |= uu[i]; } return lsb; } static uint64_t uuid_msb(const Uuid& uuid) { uint64_t msb = 0; auto uu = uuid.To128BitBE(); for (int i = 0; i <= 7; i++) { msb <<= 8; msb |= uu[i]; } return msb; } class CsisClientCallbacksImpl : public CsisClientCallbacks { public: ~CsisClientCallbacksImpl() = default; void OnConnectionState(const RawAddress& bd_addr, ConnectionState state) 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 bd addr jbyteArray for connection state"; return; } sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), (jbyte*)&bd_addr); sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged, addr.get(), (jint)state); } void OnDeviceAvailable(const RawAddress& bd_addr, int group_id, int group_size, const bluetooth::Uuid& uuid) override { 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 bd addr jbyteArray for device available"; return; } sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), (jbyte*)&bd_addr); sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onDeviceAvailable, addr.get(), (jint)group_id, (jint)group_size, UUID_PARAMS(uuid)); } void OnSetMemberAvailable(const RawAddress& bd_addr, int group_id) override { LOG(INFO) << __func__ << ", group id:" << 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); } void OnGroupLockChanged(int group_id, bool locked, CsisGroupLockStatus status) override { LOG(INFO) << __func__ << ", group_id: " << int(group_id) << ", locked: " << locked << ", status: " << (int)status; std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGroupLockChanged, (jint)group_id, (jboolean)locked, (jint)status); } }; static CsisClientCallbacksImpl sCsisClientCallbacks; static void classInitNative(JNIEnv* env, jclass clazz) { method_onConnectionStateChanged = env->GetMethodID(clazz, "onConnectionStateChanged", "([BI)V"); method_onDeviceAvailable = env->GetMethodID(clazz, "onDeviceAvailable", "([BIIJJ)V"); method_onSetMemberAvailable = env->GetMethodID(clazz, "onSetMemberAvailable", "([BI)V"); method_onGroupLockChanged = env->GetMethodID(clazz, "onGroupLockChanged", "(IZI)V"); LOG(INFO) << __func__ << ": succeeds"; } 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 (sCsisClientInterface != nullptr) { LOG(INFO) << "Cleaning up Csis Interface before initializing..."; sCsisClientInterface->Cleanup(); sCsisClientInterface = nullptr; } if (mCallbacksObj != nullptr) { LOG(INFO) << "Cleaning up Csis callback object"; env->DeleteGlobalRef(mCallbacksObj); mCallbacksObj = nullptr; } if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) { LOG(ERROR) << "Failed to allocate Global Ref for Csis Client Callbacks"; return; } sCsisClientInterface = (CsisClientInterface*)btInf->get_profile_interface( BT_PROFILE_CSIS_CLIENT_ID); if (sCsisClientInterface == nullptr) { LOG(ERROR) << "Failed to get Csis Client Interface"; return; } sCsisClientInterface->Init(&sCsisClientCallbacks); } 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 (sCsisClientInterface != nullptr) { sCsisClientInterface->Cleanup(); sCsisClientInterface = nullptr; } if (mCallbacksObj != nullptr) { env->DeleteGlobalRef(mCallbacksObj); mCallbacksObj = nullptr; } } static jboolean connectNative(JNIEnv* env, jobject object, jbyteArray address) { std::shared_lock<std::shared_timed_mutex> lock(interface_mutex); if (!sCsisClientInterface) { LOG(ERROR) << __func__ << ": Failed to get the Csis Client Interface Interface"; return JNI_FALSE; } jbyte* addr = env->GetByteArrayElements(address, nullptr); if (!addr) { jniThrowIOException(env, EINVAL); return JNI_FALSE; } RawAddress* tmpraw = (RawAddress*)addr; sCsisClientInterface->Connect(*tmpraw); env->ReleaseByteArrayElements(address, addr, 0); return JNI_TRUE; } static jboolean disconnectNative(JNIEnv* env, jobject object, jbyteArray address) { std::shared_lock<std::shared_timed_mutex> lock(interface_mutex); if (!sCsisClientInterface) { LOG(ERROR) << __func__ << ": Failed to get the Csis Client Interface"; return JNI_FALSE; } jbyte* addr = env->GetByteArrayElements(address, nullptr); if (!addr) { jniThrowIOException(env, EINVAL); return JNI_FALSE; } RawAddress* tmpraw = (RawAddress*)addr; sCsisClientInterface->Disconnect(*tmpraw); env->ReleaseByteArrayElements(address, addr, 0); return JNI_TRUE; } static void groupLockSetNative(JNIEnv* env, jobject object, jint group_id, jboolean lock) { LOG(INFO) << __func__; if (!sCsisClientInterface) { LOG(ERROR) << __func__ << ": Failed to get the Bluetooth Csis Client Interface"; return; } sCsisClientInterface->LockGroup(group_id, lock); } static JNINativeMethod sMethods[] = { {"classInitNative", "()V", (void*)classInitNative}, {"initNative", "()V", (void*)initNative}, {"cleanupNative", "()V", (void*)cleanupNative}, {"connectNative", "([B)Z", (void*)connectNative}, {"disconnectNative", "([B)Z", (void*)disconnectNative}, {"groupLockSetNative", "(IZ)V", (void*)groupLockSetNative}, }; int register_com_android_bluetooth_csip_set_coordinator(JNIEnv* env) { return jniRegisterNativeMethods( env, "com/android/bluetooth/csip/CsipSetCoordinatorNativeInterface", sMethods, NELEM(sMethods)); } } // namespace android android/app/res/values/config.xml +1 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ <bool name="profile_supported_le_audio">true</bool> <bool name="profile_supported_vc">true</bool> <bool name="profile_supported_mcp_server">true</bool> <bool name="profile_supported_csip_set_coordinator">true</bool> <!-- If true, we will require location to be enabled on the device to fire Bluetooth LE scan result callbacks in addition to having one Loading Loading
android/app/AndroidManifest.xml +9 −0 Original line number Diff line number Diff line Loading @@ -434,6 +434,15 @@ <action android:name="android.bluetooth.IBluetoothLeAudio" /> </intent-filter> </service> <service android:process="@string/process" android:name = ".csip.CsipSetCoordinatorService" android:enabled="@bool/profile_supported_csip_set_coordinator" android:exported = "true"> <intent-filter> <action android:name="android.bluetooth.IBluetoothCsipSetCoordinator" /> </intent-filter> </service> <!-- Authenticator for PBAP account. --> <service android:process="@string/process" android:name=".pbapclient.AuthenticationService" Loading
android/app/jni/com_android_bluetooth.h +2 −0 Original line number Diff line number Diff line Loading @@ -161,6 +161,8 @@ int register_com_android_bluetooth_btservice_activity_attribution(JNIEnv* env); int register_com_android_bluetooth_le_audio(JNIEnv* env); int register_com_android_bluetooth_vc(JNIEnv* env); int register_com_android_bluetooth_csip_set_coordinator(JNIEnv* env); } // namespace android #endif /* COM_ANDROID_BLUETOOTH_H */
android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp +6 −0 Original line number Diff line number Diff line Loading @@ -1841,5 +1841,11 @@ jint JNI_OnLoad(JavaVM* jvm, void* reserved) { return JNI_ERR; } status = android::register_com_android_bluetooth_csip_set_coordinator(e); if (status < 0) { ALOGE("jni csis client registration failure: %d", status); return JNI_ERR; } return JNI_VERSION_1_6; }
android/app/jni/com_android_bluetooth_csip_set_coordinator.cpp 0 → 100644 +296 −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. */ #define LOG_TAG "BluetoothCsipSetCoordinatorJni" #define LOG_NDEBUG 0 #include <string.h> #include <shared_mutex> #include "base/logging.h" #include "com_android_bluetooth.h" #include "hardware/bt_csis.h" using bluetooth::csis::ConnectionState; using bluetooth::csis::CsisClientCallbacks; using bluetooth::csis::CsisClientInterface; using bluetooth::csis::CsisGroupLockStatus; namespace android { static jmethodID method_onConnectionStateChanged; static jmethodID method_onDeviceAvailable; static jmethodID method_onSetMemberAvailable; static jmethodID method_onGroupLockChanged; static CsisClientInterface* sCsisClientInterface = nullptr; static std::shared_timed_mutex interface_mutex; static jobject mCallbacksObj = nullptr; static std::shared_timed_mutex callbacks_mutex; using bluetooth::Uuid; #define UUID_PARAMS(uuid) uuid_lsb(uuid), uuid_msb(uuid) static uint64_t uuid_lsb(const Uuid& uuid) { uint64_t lsb = 0; auto uu = uuid.To128BitBE(); for (int i = 8; i <= 15; i++) { lsb <<= 8; lsb |= uu[i]; } return lsb; } static uint64_t uuid_msb(const Uuid& uuid) { uint64_t msb = 0; auto uu = uuid.To128BitBE(); for (int i = 0; i <= 7; i++) { msb <<= 8; msb |= uu[i]; } return msb; } class CsisClientCallbacksImpl : public CsisClientCallbacks { public: ~CsisClientCallbacksImpl() = default; void OnConnectionState(const RawAddress& bd_addr, ConnectionState state) 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 bd addr jbyteArray for connection state"; return; } sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), (jbyte*)&bd_addr); sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged, addr.get(), (jint)state); } void OnDeviceAvailable(const RawAddress& bd_addr, int group_id, int group_size, const bluetooth::Uuid& uuid) override { 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 bd addr jbyteArray for device available"; return; } sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), (jbyte*)&bd_addr); sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onDeviceAvailable, addr.get(), (jint)group_id, (jint)group_size, UUID_PARAMS(uuid)); } void OnSetMemberAvailable(const RawAddress& bd_addr, int group_id) override { LOG(INFO) << __func__ << ", group id:" << 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); } void OnGroupLockChanged(int group_id, bool locked, CsisGroupLockStatus status) override { LOG(INFO) << __func__ << ", group_id: " << int(group_id) << ", locked: " << locked << ", status: " << (int)status; std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onGroupLockChanged, (jint)group_id, (jboolean)locked, (jint)status); } }; static CsisClientCallbacksImpl sCsisClientCallbacks; static void classInitNative(JNIEnv* env, jclass clazz) { method_onConnectionStateChanged = env->GetMethodID(clazz, "onConnectionStateChanged", "([BI)V"); method_onDeviceAvailable = env->GetMethodID(clazz, "onDeviceAvailable", "([BIIJJ)V"); method_onSetMemberAvailable = env->GetMethodID(clazz, "onSetMemberAvailable", "([BI)V"); method_onGroupLockChanged = env->GetMethodID(clazz, "onGroupLockChanged", "(IZI)V"); LOG(INFO) << __func__ << ": succeeds"; } 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 (sCsisClientInterface != nullptr) { LOG(INFO) << "Cleaning up Csis Interface before initializing..."; sCsisClientInterface->Cleanup(); sCsisClientInterface = nullptr; } if (mCallbacksObj != nullptr) { LOG(INFO) << "Cleaning up Csis callback object"; env->DeleteGlobalRef(mCallbacksObj); mCallbacksObj = nullptr; } if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) { LOG(ERROR) << "Failed to allocate Global Ref for Csis Client Callbacks"; return; } sCsisClientInterface = (CsisClientInterface*)btInf->get_profile_interface( BT_PROFILE_CSIS_CLIENT_ID); if (sCsisClientInterface == nullptr) { LOG(ERROR) << "Failed to get Csis Client Interface"; return; } sCsisClientInterface->Init(&sCsisClientCallbacks); } 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 (sCsisClientInterface != nullptr) { sCsisClientInterface->Cleanup(); sCsisClientInterface = nullptr; } if (mCallbacksObj != nullptr) { env->DeleteGlobalRef(mCallbacksObj); mCallbacksObj = nullptr; } } static jboolean connectNative(JNIEnv* env, jobject object, jbyteArray address) { std::shared_lock<std::shared_timed_mutex> lock(interface_mutex); if (!sCsisClientInterface) { LOG(ERROR) << __func__ << ": Failed to get the Csis Client Interface Interface"; return JNI_FALSE; } jbyte* addr = env->GetByteArrayElements(address, nullptr); if (!addr) { jniThrowIOException(env, EINVAL); return JNI_FALSE; } RawAddress* tmpraw = (RawAddress*)addr; sCsisClientInterface->Connect(*tmpraw); env->ReleaseByteArrayElements(address, addr, 0); return JNI_TRUE; } static jboolean disconnectNative(JNIEnv* env, jobject object, jbyteArray address) { std::shared_lock<std::shared_timed_mutex> lock(interface_mutex); if (!sCsisClientInterface) { LOG(ERROR) << __func__ << ": Failed to get the Csis Client Interface"; return JNI_FALSE; } jbyte* addr = env->GetByteArrayElements(address, nullptr); if (!addr) { jniThrowIOException(env, EINVAL); return JNI_FALSE; } RawAddress* tmpraw = (RawAddress*)addr; sCsisClientInterface->Disconnect(*tmpraw); env->ReleaseByteArrayElements(address, addr, 0); return JNI_TRUE; } static void groupLockSetNative(JNIEnv* env, jobject object, jint group_id, jboolean lock) { LOG(INFO) << __func__; if (!sCsisClientInterface) { LOG(ERROR) << __func__ << ": Failed to get the Bluetooth Csis Client Interface"; return; } sCsisClientInterface->LockGroup(group_id, lock); } static JNINativeMethod sMethods[] = { {"classInitNative", "()V", (void*)classInitNative}, {"initNative", "()V", (void*)initNative}, {"cleanupNative", "()V", (void*)cleanupNative}, {"connectNative", "([B)Z", (void*)connectNative}, {"disconnectNative", "([B)Z", (void*)disconnectNative}, {"groupLockSetNative", "(IZ)V", (void*)groupLockSetNative}, }; int register_com_android_bluetooth_csip_set_coordinator(JNIEnv* env) { return jniRegisterNativeMethods( env, "com/android/bluetooth/csip/CsipSetCoordinatorNativeInterface", sMethods, NELEM(sMethods)); } } // namespace android
android/app/res/values/config.xml +1 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ <bool name="profile_supported_le_audio">true</bool> <bool name="profile_supported_vc">true</bool> <bool name="profile_supported_mcp_server">true</bool> <bool name="profile_supported_csip_set_coordinator">true</bool> <!-- If true, we will require location to be enabled on the device to fire Bluetooth LE scan result callbacks in addition to having one Loading