From ea1561c768cd84755052b0f1f5727ea6c476d3f8 Mon Sep 17 00:00:00 2001 From: sravan voleti Date: Thu, 21 Oct 2021 17:05:40 +0530 Subject: [PATCH] Bluetooth advance audio code Bluetooth advance audio code Snapshot as of 326217e71ec6099715f76338356bda6e6e605132. Submitted on behalf of a third-party: The Linux Foundation, Broadcom Corporation, The Android Open Source Project, Google, Inc License rights, if any, to the submission are granted solely by the copyright owner of such submission under its applicable intellectual property. Copyright (c) 2014-2015, 2020-2021 The Linux Foundation. All rights reserved Copyright (C) 2003-2012 Broadcom Corporation Copyright (C) 2008, 2011-2012, 2016-2018 The Android Open Source Project Copyright (C) 2014 Google, Inc 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. Third Party code includes additions/modifications from Qualcomm Innovation Center, Inc. Change-Id: I4bbebd14323c4b4fa292ecb617c16918386d867c --- .../bap_uclient_test_tool/Android.bp | 28 + .../bap_uclient_test.cpp | 1904 +++++ le_audio/certification_tool/types/Android.bp | 17 + .../types/bluetooth/uuid.cc | 177 + .../certification_tool/types/bluetooth/uuid.h | 143 + .../types/class_of_device.cc | 78 + .../types/class_of_device.h | 63 + .../certification_tool/types/raw_address.cc | 73 + .../certification_tool/types/raw_address.h | 79 + le_audio/frameworks/base/Android.bp | 4 + .../BleBroadcastAudioScanAssistCallback.java | 164 + .../BleBroadcastAudioScanAssistManager.java | 621 ++ .../bluetooth/BleBroadcastSourceChannel.java | 217 + .../bluetooth/BleBroadcastSourceInfo.java | 1038 +++ .../android/bluetooth/BluetoothBroadcast.java | 266 + .../bluetooth/BluetoothSyncHelper.java | 736 ++ .../base/packages/SettingsLib/Android.bp | 4 + .../settingslib/bluetooth/BCProfile.java | 301 + .../bluetooth/BroadcastProfile.java | 166 + .../bluetooth/BroadcastSourceInfoHandler.java | 65 + .../VendorCachedBluetoothDevice.java | 273 + le_audio/packages/apps/Bluetooth/Android.bp | 12 + .../packages/apps/Bluetooth/jni/Android.bp | 34 + .../jni/com_android_bluetooth_acm.cpp | 543 ++ .../jni/com_android_bluetooth_apm.cpp | 180 + .../jni/com_android_bluetooth_broadcast.cpp | 456 ++ ..._bluetooth_btservice_AdapterServiceExt.cpp | 62 + .../jni/com_android_bluetooth_cc.cpp | 402 + .../jni/com_android_bluetooth_csip_client.cpp | 395 + .../Bluetooth/jni/com_android_bluetooth_ext.h | 29 + .../jni/com_android_bluetooth_mcp.cpp | 417 ++ .../jni/com_android_bluetooth_pacs_client.cpp | 378 + .../com_android_bluetooth_vcp_controller.cpp | 295 + .../android/bluetooth/acm/AcmCodecConfig.java | 127 + .../bluetooth/acm/AcmNativeInterface.java | 224 + .../com/android/bluetooth/acm/AcmService.java | 1852 +++++ .../android/bluetooth/acm/AcmStackEvent.java | 130 + .../bluetooth/acm/AcmStateMachine.java | 2340 ++++++ .../apm/ActiveDeviceManagerService.java | 1430 ++++ .../com/android/bluetooth/apm/ApmConst.java | 56 + .../bluetooth/apm/ApmNativeInterface.java | 117 + .../com/android/bluetooth/apm/CallAudio.java | 744 ++ .../android/bluetooth/apm/CallControl.java | 150 + .../bluetooth/apm/DeviceProfileMap.java | 800 ++ .../com/android/bluetooth/apm/MediaAudio.java | 1190 +++ .../bluetooth/apm/MediaControlManager.java | 197 + .../bluetooth/apm/StreamAudioService.java | 368 + .../android/bluetooth/apm/VolumeManager.java | 765 ++ .../bluetooth/bassclient/BCService.java | 1691 +++++ .../bluetooth/bassclient/BaseData.java | 847 +++ .../bassclient/BassClientStateMachine.java | 2388 ++++++ .../bluetooth/bassclient/BassCsetManager.java | 592 ++ .../bluetooth/bassclient/BassUtils.java | 492 ++ .../broadcast/BroadcastNativeInterface.java | 239 + .../bluetooth/broadcast/BroadcastService.java | 1946 +++++ .../broadcast/BroadcastStackEvent.java | 106 + .../android/bluetooth/cc/CCHalConstants.java | 100 + .../bluetooth/cc/CCNativeInterface.java | 255 + .../com/android/bluetooth/cc/CCService.java | 864 +++ .../bluetooth/cc/CallControlState.java | 84 + .../bluetooth/groupclient/GroupAppMap.java | 164 + .../GroupClientNativeInterface.java | 237 + .../bluetooth/groupclient/GroupScanner.java | 515 ++ .../bluetooth/groupclient/GroupService.java | 1093 +++ .../bluetooth/mcp/McpNativeInterface.java | 260 + .../com/android/bluetooth/mcp/McpService.java | 828 ++ .../bluetooth/pacsclient/PCService.java | 528 ++ .../pacsclient/PacsClientNativeInterface.java | 237 + .../pacsclient/PacsClientStackEvent.java | 95 + .../pacsclient/PacsClientStateMachine.java | 703 ++ .../android/bluetooth/vcp/VcpController.java | 733 ++ .../vcp/VcpControllerNativeInterface.java | 200 + .../vcp/VcpControllerStateMachine.java | 1046 +++ .../android/bluetooth/vcp/VcpStackEvent.java | 79 + le_audio/packages/apps/Settings/Android.bp | 4 + .../BADevicePreferenceController.java | 144 + ...eBroadcastSourceInfoDetailsController.java | 679 ++ ...BleBroadcastSourceInfoDetailsFragment.java | 129 + .../BleBroadcastSourceInfoPreference.java | 234 + ...BroadcastSourceInfoPreferenceCallback.java | 37 + .../BleBroadcastSourceInfoUpdater.java | 255 + .../BluetoothBroadcastEnableController.java | 233 + .../BluetoothBroadcastPinController.java | 223 + .../BluetoothBroadcastPinFragment.java | 242 + .../BluetoothBroadcastSourceInfoEntries.java | 52 + ...toothDetailsAddSourceButtonController.java | 118 + .../settings/bluetooth/BluetoothSADetail.java | 655 ++ .../BroadcastScanAssistanceUtils.java | 151 + le_audio/system/bt/binder/Android.bp | 10 + .../bluetooth/BleBroadcastSourceChannel.aidl | 9 + .../bluetooth/BleBroadcastSourceInfo.aidl | 8 + .../IBleBroadcastAudioScanAssistCallback.aidl | 34 + .../bluetooth/IBluetoothBroadcast.aidl | 20 + .../bluetooth/IBluetoothSyncHelper.aidl | 61 + le_audio/system/bt/bta/Android.bp | 70 + le_audio/system/bt/bta/bap/ascs_client.cc | 1731 +++++ le_audio/system/bt/bta/bap/connected_iso.cc | 1542 ++++ le_audio/system/bt/bta/bap/gattc_ops_queue.cc | 297 + le_audio/system/bt/bta/bap/gattc_ops_queue.h | 119 + le_audio/system/bt/bta/bap/gatts_ops_queue.cc | 139 + le_audio/system/bt/bta/bap/gatts_ops_queue.h | 62 + le_audio/system/bt/bta/bap/pacs_client.cc | 1862 +++++ le_audio/system/bt/bta/bap/ucast_client_int.h | 1262 ++++ le_audio/system/bt/bta/bap/uclient_alarm.cc | 85 + le_audio/system/bt/bta/bap/uclient_alarm.h | 49 + le_audio/system/bt/bta/bap/uclient_main.cc | 534 ++ .../system/bt/bta/bap/uclient_strm_mgr.cc | 999 +++ .../system/bt/bta/bap/uclient_strm_tracker.cc | 3987 ++++++++++ le_audio/system/bt/bta/cc/bta_cc_main.cc | 2334 ++++++ le_audio/system/bt/bta/csip/bta_csip_act.cc | 1686 +++++ le_audio/system/bt/bta/csip/bta_csip_api.cc | 273 + le_audio/system/bt/bta/csip/bta_csip_int.h | 307 + le_audio/system/bt/bta/csip/bta_csip_main.cc | 282 + le_audio/system/bt/bta/csip/bta_csip_utils.cc | 1304 ++++ le_audio/system/bt/bta/dm/bta_dm_adv_audio.cc | 1172 +++ .../bt/bta/include/bta_ascs_client_api.h | 77 + .../bt/bta/include/bta_bap_uclient_api.h | 53 + le_audio/system/bt/bta/include/bta_cc_api.h | 472 ++ le_audio/system/bt/bta/include/bta_csip_api.h | 382 + .../system/bt/bta/include/bta_dm_adv_audio.h | 137 + le_audio/system/bt/bta/include/bta_mcp_api.h | 475 ++ .../bt/bta/include/bta_pacs_client_api.h | 48 + .../bt/bta/include/bta_vcp_controller_api.h | 148 + .../system/bt/bta/include/connected_iso_api.h | 97 + le_audio/system/bt/bta/mcp/bta_mcp_main.cc | 3075 ++++++++ .../system/bt/bta/vcp/bta_vcp_controller.cc | 1108 +++ le_audio/system/bt/btif/Android.bp | 80 + .../bt/btif/include/bluetooth_adv_audio.h | 19 + le_audio/system/bt/btif/include/btif_acm.h | 236 + .../system/bt/btif/include/btif_acm_source.h | 21 + .../bt/btif/include/btif_bap_broadcast.h | 78 + .../bt/btif/include/btif_bap_codec_utils.h | 76 + .../system/bt/btif/include/btif_bap_config.h | 83 + .../bt/btif/include/btif_dm_adv_audio.h | 18 + le_audio/system/bt/btif/include/btif_vmcp.h | 133 + le_audio/system/bt/btif/leaudio_configs.xml | 713 ++ .../system/bt/btif/src/bluetooth_adv_audio.cc | 179 + le_audio/system/bt/btif/src/btif_acm.cc | 6658 +++++++++++++++++ .../system/bt/btif/src/btif_acm_source.cc | 228 + le_audio/system/bt/btif/src/btif_apm.cc | 175 + .../system/bt/btif/src/btif_ascs_client.cc | 209 + .../system/bt/btif/src/btif_bap_broadcast.cc | 1808 +++++ .../bt/btif/src/btif_bap_codec_utils.cc | 381 + .../system/bt/btif/src/btif_bap_config.cc | 860 +++ .../system/bt/btif/src/btif_bap_uclient.cc | 186 + .../bt/btif/src/btif_bap_uclient_test.cc | 2957 ++++++++ le_audio/system/bt/btif/src/btif_cc.cc | 222 + le_audio/system/bt/btif/src/btif_csip.cc | 238 + .../system/bt/btif/src/btif_dm_adv_audio.cc | 678 ++ le_audio/system/bt/btif/src/btif_mcp.cc | 171 + .../system/bt/btif/src/btif_pacs_client.cc | 147 + .../system/bt/btif/src/btif_vcp_controller.cc | 131 + le_audio/system/bt/btif/src/btif_vmcp.cc | 762 ++ le_audio/system/bt/common/state_machine.h | 214 + .../bluetooth_callcontrol_callbacks.h | 62 + .../bluetooth_callcontrol_interface.h | 152 + le_audio/vhal/include/hardware/bt_acm.h | 112 + le_audio/vhal/include/hardware/bt_apm.h | 61 + .../vhal/include/hardware/bt_ascs_client.h | 295 + le_audio/vhal/include/hardware/bt_bap_ba.h | 112 + .../vhal/include/hardware/bt_bap_uclient.h | 251 + le_audio/vhal/include/hardware/bt_csip.h | 105 + le_audio/vhal/include/hardware/bt_mcp.h | 52 + .../vhal/include/hardware/bt_pacs_client.h | 183 + .../vhal/include/hardware/bt_vcp_controller.h | 82 + 165 files changed, 85026 insertions(+) create mode 100644 le_audio/certification_tool/bap_uclient_test_tool/Android.bp create mode 100644 le_audio/certification_tool/bap_uclient_test_tool/bap_uclient_test.cpp create mode 100644 le_audio/certification_tool/types/Android.bp create mode 100644 le_audio/certification_tool/types/bluetooth/uuid.cc create mode 100644 le_audio/certification_tool/types/bluetooth/uuid.h create mode 100644 le_audio/certification_tool/types/class_of_device.cc create mode 100644 le_audio/certification_tool/types/class_of_device.h create mode 100644 le_audio/certification_tool/types/raw_address.cc create mode 100644 le_audio/certification_tool/types/raw_address.h create mode 100644 le_audio/frameworks/base/Android.bp create mode 100644 le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastAudioScanAssistCallback.java create mode 100644 le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastAudioScanAssistManager.java create mode 100644 le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastSourceChannel.java create mode 100644 le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastSourceInfo.java create mode 100644 le_audio/frameworks/base/core/java/android/bluetooth/BluetoothBroadcast.java create mode 100644 le_audio/frameworks/base/core/java/android/bluetooth/BluetoothSyncHelper.java create mode 100644 le_audio/frameworks/base/packages/SettingsLib/Android.bp create mode 100644 le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BCProfile.java create mode 100644 le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastProfile.java create mode 100644 le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastSourceInfoHandler.java create mode 100644 le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/VendorCachedBluetoothDevice.java create mode 100644 le_audio/packages/apps/Bluetooth/Android.bp create mode 100644 le_audio/packages/apps/Bluetooth/jni/Android.bp create mode 100644 le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_acm.cpp create mode 100644 le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_apm.cpp create mode 100644 le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_broadcast.cpp create mode 100644 le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterServiceExt.cpp create mode 100644 le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_cc.cpp create mode 100644 le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_csip_client.cpp create mode 100644 le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_ext.h create mode 100644 le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_mcp.cpp create mode 100644 le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_pacs_client.cpp create mode 100644 le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_vcp_controller.cpp create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmCodecConfig.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmNativeInterface.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmService.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmStackEvent.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmStateMachine.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ActiveDeviceManagerService.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ApmConst.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ApmNativeInterface.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/CallAudio.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/CallControl.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/DeviceProfileMap.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/MediaAudio.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/MediaControlManager.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/StreamAudioService.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/VolumeManager.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BCService.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BaseData.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassClientStateMachine.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassCsetManager.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassUtils.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/broadcast/BroadcastNativeInterface.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/broadcast/BroadcastService.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/broadcast/BroadcastStackEvent.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCHalConstants.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCNativeInterface.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCService.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CallControlState.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupAppMap.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupClientNativeInterface.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupScanner.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupService.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/mcp/McpNativeInterface.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/mcp/McpService.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PCService.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientNativeInterface.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientStackEvent.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientStateMachine.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpController.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpControllerNativeInterface.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpControllerStateMachine.java create mode 100644 le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpStackEvent.java create mode 100644 le_audio/packages/apps/Settings/Android.bp create mode 100644 le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BADevicePreferenceController.java create mode 100644 le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoDetailsController.java create mode 100644 le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoDetailsFragment.java create mode 100644 le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoPreference.java create mode 100644 le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoPreferenceCallback.java create mode 100644 le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoUpdater.java create mode 100644 le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastEnableController.java create mode 100644 le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastPinController.java create mode 100644 le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastPinFragment.java create mode 100644 le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastSourceInfoEntries.java create mode 100644 le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothDetailsAddSourceButtonController.java create mode 100644 le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothSADetail.java create mode 100644 le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BroadcastScanAssistanceUtils.java create mode 100644 le_audio/system/bt/binder/Android.bp create mode 100644 le_audio/system/bt/binder/android/bluetooth/BleBroadcastSourceChannel.aidl create mode 100644 le_audio/system/bt/binder/android/bluetooth/BleBroadcastSourceInfo.aidl create mode 100644 le_audio/system/bt/binder/android/bluetooth/IBleBroadcastAudioScanAssistCallback.aidl create mode 100644 le_audio/system/bt/binder/android/bluetooth/IBluetoothBroadcast.aidl create mode 100644 le_audio/system/bt/binder/android/bluetooth/IBluetoothSyncHelper.aidl create mode 100644 le_audio/system/bt/bta/Android.bp create mode 100644 le_audio/system/bt/bta/bap/ascs_client.cc create mode 100644 le_audio/system/bt/bta/bap/connected_iso.cc create mode 100644 le_audio/system/bt/bta/bap/gattc_ops_queue.cc create mode 100644 le_audio/system/bt/bta/bap/gattc_ops_queue.h create mode 100644 le_audio/system/bt/bta/bap/gatts_ops_queue.cc create mode 100644 le_audio/system/bt/bta/bap/gatts_ops_queue.h create mode 100644 le_audio/system/bt/bta/bap/pacs_client.cc create mode 100644 le_audio/system/bt/bta/bap/ucast_client_int.h create mode 100644 le_audio/system/bt/bta/bap/uclient_alarm.cc create mode 100644 le_audio/system/bt/bta/bap/uclient_alarm.h create mode 100644 le_audio/system/bt/bta/bap/uclient_main.cc create mode 100644 le_audio/system/bt/bta/bap/uclient_strm_mgr.cc create mode 100644 le_audio/system/bt/bta/bap/uclient_strm_tracker.cc create mode 100644 le_audio/system/bt/bta/cc/bta_cc_main.cc create mode 100644 le_audio/system/bt/bta/csip/bta_csip_act.cc create mode 100644 le_audio/system/bt/bta/csip/bta_csip_api.cc create mode 100644 le_audio/system/bt/bta/csip/bta_csip_int.h create mode 100644 le_audio/system/bt/bta/csip/bta_csip_main.cc create mode 100644 le_audio/system/bt/bta/csip/bta_csip_utils.cc create mode 100644 le_audio/system/bt/bta/dm/bta_dm_adv_audio.cc create mode 100644 le_audio/system/bt/bta/include/bta_ascs_client_api.h create mode 100644 le_audio/system/bt/bta/include/bta_bap_uclient_api.h create mode 100644 le_audio/system/bt/bta/include/bta_cc_api.h create mode 100644 le_audio/system/bt/bta/include/bta_csip_api.h create mode 100644 le_audio/system/bt/bta/include/bta_dm_adv_audio.h create mode 100644 le_audio/system/bt/bta/include/bta_mcp_api.h create mode 100644 le_audio/system/bt/bta/include/bta_pacs_client_api.h create mode 100644 le_audio/system/bt/bta/include/bta_vcp_controller_api.h create mode 100644 le_audio/system/bt/bta/include/connected_iso_api.h create mode 100644 le_audio/system/bt/bta/mcp/bta_mcp_main.cc create mode 100644 le_audio/system/bt/bta/vcp/bta_vcp_controller.cc create mode 100644 le_audio/system/bt/btif/Android.bp create mode 100644 le_audio/system/bt/btif/include/bluetooth_adv_audio.h create mode 100644 le_audio/system/bt/btif/include/btif_acm.h create mode 100644 le_audio/system/bt/btif/include/btif_acm_source.h create mode 100644 le_audio/system/bt/btif/include/btif_bap_broadcast.h create mode 100644 le_audio/system/bt/btif/include/btif_bap_codec_utils.h create mode 100644 le_audio/system/bt/btif/include/btif_bap_config.h create mode 100644 le_audio/system/bt/btif/include/btif_dm_adv_audio.h create mode 100644 le_audio/system/bt/btif/include/btif_vmcp.h create mode 100644 le_audio/system/bt/btif/leaudio_configs.xml create mode 100644 le_audio/system/bt/btif/src/bluetooth_adv_audio.cc create mode 100644 le_audio/system/bt/btif/src/btif_acm.cc create mode 100644 le_audio/system/bt/btif/src/btif_acm_source.cc create mode 100644 le_audio/system/bt/btif/src/btif_apm.cc create mode 100644 le_audio/system/bt/btif/src/btif_ascs_client.cc create mode 100644 le_audio/system/bt/btif/src/btif_bap_broadcast.cc create mode 100644 le_audio/system/bt/btif/src/btif_bap_codec_utils.cc create mode 100644 le_audio/system/bt/btif/src/btif_bap_config.cc create mode 100644 le_audio/system/bt/btif/src/btif_bap_uclient.cc create mode 100644 le_audio/system/bt/btif/src/btif_bap_uclient_test.cc create mode 100644 le_audio/system/bt/btif/src/btif_cc.cc create mode 100644 le_audio/system/bt/btif/src/btif_csip.cc create mode 100644 le_audio/system/bt/btif/src/btif_dm_adv_audio.cc create mode 100644 le_audio/system/bt/btif/src/btif_mcp.cc create mode 100644 le_audio/system/bt/btif/src/btif_pacs_client.cc create mode 100644 le_audio/system/bt/btif/src/btif_vcp_controller.cc create mode 100644 le_audio/system/bt/btif/src/btif_vmcp.cc create mode 100644 le_audio/system/bt/common/state_machine.h create mode 100644 le_audio/vhal/include/hardware/bluetooth_callcontrol_callbacks.h create mode 100644 le_audio/vhal/include/hardware/bluetooth_callcontrol_interface.h create mode 100644 le_audio/vhal/include/hardware/bt_acm.h create mode 100644 le_audio/vhal/include/hardware/bt_apm.h create mode 100644 le_audio/vhal/include/hardware/bt_ascs_client.h create mode 100644 le_audio/vhal/include/hardware/bt_bap_ba.h create mode 100644 le_audio/vhal/include/hardware/bt_bap_uclient.h create mode 100644 le_audio/vhal/include/hardware/bt_csip.h create mode 100644 le_audio/vhal/include/hardware/bt_mcp.h create mode 100644 le_audio/vhal/include/hardware/bt_pacs_client.h create mode 100644 le_audio/vhal/include/hardware/bt_vcp_controller.h diff --git a/le_audio/certification_tool/bap_uclient_test_tool/Android.bp b/le_audio/certification_tool/bap_uclient_test_tool/Android.bp new file mode 100644 index 00000000000..f7a9399921f --- /dev/null +++ b/le_audio/certification_tool/bap_uclient_test_tool/Android.bp @@ -0,0 +1,28 @@ +cc_binary { + name: "bap_uclient_test", + system_ext_specific: true, + enabled: false, + srcs: ["bap_uclient_test.cpp"], + + include_dirs: [ + ".", + "packages/modules/Bluetooth/system/include", + "packages/modules/Bluetooth/system/types", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/stack/l2cap", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/utils/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system", + "vendor/qcom/opensource/commonsys/bluetooth_lea/vhal/include", + "external/libchrome", + ], + + cflags: ["-DHAS_NO_BDROID_BUILDCFG"], + + shared_libs: [ + "libcutils", + "libchrome", + "libutils", + ], + + static_libs: ["libbluetooth-types-qti"], + +} diff --git a/le_audio/certification_tool/bap_uclient_test_tool/bap_uclient_test.cpp b/le_audio/certification_tool/bap_uclient_test_tool/bap_uclient_test.cpp new file mode 100644 index 00000000000..78db7037b02 --- /dev/null +++ b/le_audio/certification_tool/bap_uclient_test_tool/bap_uclient_test.cpp @@ -0,0 +1,1904 @@ +/*********************************************************************** + * + * Copyright (c) 2014-2015, 2020 The Linux Foundation. All rights reserved. + * + * Copyright (C) 2009-2012 Broadcom Corporation + * + * 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. + * + +******************************************************************************/ + + +/****************************************************************************** +****** + * + * Filename: bap_uclient_test.cpp + * + * Description: bap unicast client test application + * + +******************************************************************************* +****/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +using bluetooth::Uuid; + +constexpr uint8_t ASE_DIRECTION_SINK = 0x01 << 0; +constexpr uint8_t ASE_DIRECTION_SRC = 0x01 << 1; + +constexpr uint8_t ASE_SINK_STEREO = 0x01 << 0; +constexpr uint8_t ASE_SRC_STEREO = 0x01 << 1; + +#ifndef BAP_UNICAST_TEST_APP_INTERFACE +#define BAP_UNICAST_TEST_APP_INTERFACE +/****************************************************************************** +****** +** Constants & Macros +******************************************************************************* +*****/ + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#define PID_FILE "/data/.bdt_pid" + +#ifndef MAX +#define MAX(x, y) ((x) > (y) ? (x) : (y)) +#endif + +#define CASE_RETURN_STR(const) case const: return #const; + + +/****************************************************************************** +****** +** Local type definitions +******************************************************************************* +*****/ + + +/****************************************************************************** +****** +** Static variables +******************************************************************************* +*****/ + +static unsigned char main_done = 0; +static int status; + +#define LE_ACL_MAX_BUFF_SIZE 4096 +static int num_frames = 1; +static unsigned long g_delay = 1; /* Default delay before data transfer */ +static int count = 1; +static uint16_t g_BleEncKeySize = 16; +static int g_le_coc_if = 0; +static int rcv_itration = 0; +static volatile bool cong_status = FALSE; + + +/* Main API */ +const bt_interface_t* sBtInterface = NULL; + +static gid_t groups[] = { AID_NET_BT, AID_INET, AID_NET_BT_ADMIN, + AID_SYSTEM, AID_MISC, AID_SDCARD_RW, + AID_NET_ADMIN, AID_VPN}; + +enum { + DISCONNECT, + CONNECTING, + CONNECTED, + DISCONNECTING +}; + +static unsigned char bt_enabled = 0; +static int g_ConnectionState = DISCONNECT; +static int g_AdapterState = BT_STATE_OFF; +static int g_PairState = BT_BOND_STATE_NONE; + +static int g_conn_id = 0; +static int g_client_if = 0; +static int g_server_if = 0; +static int g_client_if_scan = 0; +static int g_server_if_scan = 0; + + +RawAddress* remote_bd_address; + +static uint16_t g_SecLevel = 0; +static bool g_ConnType = TRUE;//DUT is initiating connection + +/****************************************************************************** +****** +** Static functions +******************************************************************************* +*****/ + +static void process_cmd(char *p, unsigned char is_job); +//static void job_handler(void *param); +static void bdt_log(const char *fmt_str, ...); +static void l2c_connect(RawAddress bd_addr); +static uint16_t do_l2cap_connect(RawAddress bd_addr); + +int GetBdAddr(char *p, RawAddress* pbd_addr); +void bdt_init(void); +int reg_inst_id = -1; +int reg_status = -1; + + +/****************************************************************************** +****** +** ASCS Client Callbacks +******************************************************************************* +*****/ + + +/****************************************************************************** +****** +** PACS client Callbacks +******************************************************************************* +*****/ + + + +/****************************************************************************** +****** +** BAP Unicast client Callbacks +******************************************************************************* +*****/ + +/****************************************************************************** +****** +** Shutdown helper functions +******************************************************************************* +*****/ + +static void bdt_shutdown(void) +{ + bdt_log("shutdown bdroid test app.\n"); + main_done = 1; +} + + +/***************************************************************************** +** Android's init.rc does not yet support applying linux capabilities +*****************************************************************************/ + +static void config_permissions(void) +{ + struct __user_cap_header_struct header; + struct __user_cap_data_struct cap[2]; + + bdt_log("set_aid_and_cap : pid %d, uid %d gid %d", getpid(), getuid(), getgid()); + + header.pid = 0; + + prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); + + setuid(AID_BLUETOOTH); + setgid(AID_BLUETOOTH); + + header.version = _LINUX_CAPABILITY_VERSION_3; + + cap[CAP_TO_INDEX(CAP_NET_RAW)].permitted |= CAP_TO_MASK(CAP_NET_RAW); + cap[CAP_TO_INDEX(CAP_NET_ADMIN)].permitted |= CAP_TO_MASK(CAP_NET_ADMIN); + cap[CAP_TO_INDEX(CAP_NET_BIND_SERVICE)].permitted |= CAP_TO_MASK(CAP_NET_BIND_SERVICE); + cap[CAP_TO_INDEX(CAP_SYS_RAWIO)].permitted |= CAP_TO_MASK(CAP_SYS_RAWIO); + cap[CAP_TO_INDEX(CAP_SYS_NICE)].permitted |= CAP_TO_MASK(CAP_SYS_NICE); + cap[CAP_TO_INDEX(CAP_SETGID)].permitted |= CAP_TO_MASK(CAP_SETGID); + cap[CAP_TO_INDEX(CAP_WAKE_ALARM)].permitted |= CAP_TO_MASK(CAP_WAKE_ALARM); + + cap[CAP_TO_INDEX(CAP_NET_RAW)].effective |= CAP_TO_MASK(CAP_NET_RAW); + cap[CAP_TO_INDEX(CAP_NET_ADMIN)].effective |= CAP_TO_MASK(CAP_NET_ADMIN); + cap[CAP_TO_INDEX(CAP_NET_BIND_SERVICE)].effective |= CAP_TO_MASK(CAP_NET_BIND_SERVICE); + cap[CAP_TO_INDEX(CAP_SYS_RAWIO)].effective |= CAP_TO_MASK(CAP_SYS_RAWIO); + cap[CAP_TO_INDEX(CAP_SYS_NICE)].effective |= CAP_TO_MASK(CAP_SYS_NICE); + cap[CAP_TO_INDEX(CAP_SETGID)].effective |= CAP_TO_MASK(CAP_SETGID); + cap[CAP_TO_INDEX(CAP_WAKE_ALARM)].effective |= CAP_TO_MASK(CAP_WAKE_ALARM); + + capset(&header, &cap[0]); + setgroups(sizeof(groups)/sizeof(groups[0]), groups); +} + + +/***************************************************************************** +** Logger API +*****************************************************************************/ + +void bdt_log(const char *fmt_str, ...) +{ + static char buffer[1024]; + va_list ap; + + va_start(ap, fmt_str); + vsnprintf(buffer, 1024, fmt_str, ap); + va_end(ap); + + fprintf(stdout, "%s\n", buffer); +} + +/****************************************************************************** +* + ** Misc helper functions + +*******************************************************************************/ +static const char* dump_bt_status(int status) +{ + switch(status) + { + CASE_RETURN_STR(BT_STATUS_SUCCESS) + CASE_RETURN_STR(BT_STATUS_FAIL) + CASE_RETURN_STR(BT_STATUS_NOT_READY) + CASE_RETURN_STR(BT_STATUS_NOMEM) + CASE_RETURN_STR(BT_STATUS_BUSY) + CASE_RETURN_STR(BT_STATUS_UNSUPPORTED) + + default: + return "unknown status code"; + } +} + + +/****************************************************************************** +* + ** Console helper functions + +*******************************************************************************/ + +void skip_blanks(char **p) +{ + while (**p == ' ') + (*p)++; +} + +uint32_t get_int(char **p, int DefaultValue) +{ + uint32_t Value = 0; + unsigned char UseDefault; + + UseDefault = 1; + skip_blanks(p); + + while ( ((**p)<= '9' && (**p)>= '0') ) + { + Value = Value * 10 + (**p) - '0'; + UseDefault = 0; + (*p)++; + } + if (UseDefault) + return DefaultValue; + else + return Value; +} + +int get_signed_int(char **p, int DefaultValue) +{ + int Value = 0; + unsigned char UseDefault; + unsigned char NegativeNum = 0; + + UseDefault = 1; + skip_blanks(p); + + if ((**p) == '-') + { + NegativeNum = 1; + (*p)++; + } + while ( ((**p)<= '9' && (**p)>= '0') ) + { + Value = Value * 10 + (**p) - '0'; + UseDefault = 0; + (*p)++; + } + + if (UseDefault) + return DefaultValue; + else + return ((NegativeNum == 0)? Value : -Value); +} + +void get_str_1(char **p, char *Buffer) +{ + skip_blanks(p); + while (**p != 0 && **p != '\0') + { + *Buffer = **p; + (*p)++; + Buffer++; + } + + *Buffer = 0; +} + +void get_str(char **p, char *Buffer) +{ + skip_blanks(p); + while (**p != 0 && **p != ' ') + { + *Buffer = **p; + (*p)++; + Buffer++; + } + + *Buffer = 0; +} + + + +#define is_cmd(str) ((strlen(str) == strlen(cmd)) && strncmp((const char *)&cmd, str, strlen(str)) == 0) +#define if_cmd(str) if (is_cmd(str)) + +typedef void (t_console_cmd_handler) (char *p); + +typedef struct { + const char *name; + t_console_cmd_handler *handler; + const char *help; + unsigned char is_job; +} t_cmd; + +void do_help(char *p); +void do_quit(char *p); +void do_init(char *p); +void do_enable(char *p); +void do_disable(char *p); +void do_cleanup(char *p); +void do_pairing(char *p); +void do_pacs_discovery(char *p); +void do_ascs_discovery(char *p); +void do_bap_connect(char *p); +void do_bap_disconnect(char *p); +void do_bap_start(char *p); +void do_bap_stop(char *p); +void do_bap_disc_in_connecting(char *p); +void do_bap_disc_in_starting(char *p); +void do_bap_disc_in_stopping(char *p); +void do_bap_stop_in_starting(char *p); +void do_bap_update_stream(char *p); + +/******************************************************************* + * + * CONSOLE COMMAND TABLE + * +*/ + +const t_cmd console_cmd_list[] = +{ + /* + * INTERNAL + */ + + { "help", do_help, "lists all available console commands", 0 }, + { "quit", do_quit, "", 0}, + + /* + * API CONSOLE COMMANDS + */ + + /* Init and Cleanup shall be called automatically */ + { "enable", do_enable, "cmd :: enable", 0 }, + { "disable", do_disable, "cmd :: disable", 0 }, + { "pair", do_pairing, "cmd :: pair ", 0 }, + { "pacs_discovery", do_pacs_discovery, "cmd :: pacs_discovery ", 0 }, + { "ascs_discovery", do_ascs_discovery, "cmd :: ascs_discovery ", 0 }, + { "bap_connect", do_bap_connect, "cmd :: bap_connect ", 0 }, + { "bap_disconnect", do_bap_disconnect, "cmd :: bap_disconnect ", 0 }, + { "bap_start", do_bap_start, "cmd :: bap_start ", 0 }, + { "bap_stop", do_bap_stop, "cmd :: bap_stop ", 0 }, + { "bap_disc_in_connecting", do_bap_disc_in_connecting, "cmd :: bap_disc_in_connecting ", 0 }, + { "bap_disc_in_starting", do_bap_disc_in_starting, "cmd :: bap_disc_in_starting ", 0 }, + { "bap_disc_in_stopping", do_bap_disc_in_stopping, "cmd :: bap_disc_in_stopping ", 0 }, + { "bap_stop_in_starting", do_bap_stop_in_starting, "cmd :: bap_stop_in_starting ", 0 }, + { "bap_update_stream", do_bap_update_stream, "cmd :: bap_update_stream ", 0 }, + /* last entry */ + {NULL, NULL, "", 0}, +}; + + +static int console_cmd_maxlen = 0; + +static void *cmdjob_handler(void *param) +{ + char *job_cmd = (char*)param; + + bdt_log("cmdjob starting (%s)", job_cmd); + + process_cmd(job_cmd, 1); + + bdt_log("cmdjob terminating"); + + free(job_cmd); + return NULL; +} + +static int create_cmdjob(char *cmd) +{ + pthread_t thread_id; + char *job_cmd; + + job_cmd = (char*)calloc(1, strlen(cmd)+1); /* freed in job handler */ + if (job_cmd) { + strlcpy(job_cmd, cmd,(strlen(cmd)+1)); + if (pthread_create(&thread_id, NULL, cmdjob_handler, (void *)job_cmd) != 0) + /*if (pthread_create(&thread_id, NULL, + (void*)cmdjob_handler, (void*)job_cmd) !=0)*/ + perror("pthread_create"); + return 0; + } + else + perror("create_Cmdjob malloc failed "); + return -1; +} + +/****************************************************************************** +* + ** Load stack lib + +*******************************************************************************/ +#define BLUETOOTH_LIBRARY_NAME "libbluetooth_qti.so" +int load_bt_lib(const bt_interface_t** interface) { + const char* sym = BLUETOOTH_INTERFACE_STRING; + bt_interface_t* itf = nullptr; + + // Always try to load the default Bluetooth stack on GN builds. + const char* path = BLUETOOTH_LIBRARY_NAME; + void* handle = dlopen(path, RTLD_NOW); + if (!handle) { + //const char* err_str = dlerror(); + printf("failed to load Bluetooth library\n"); + goto error; + } + + // Get the address of the bt_interface_t. + itf = (bt_interface_t*)dlsym(handle, sym); + if (!itf) { + printf("failed to load symbol from Bluetooth library\n"); + goto error; + } + + // Success. + printf(" loaded HAL Success\n"); + *interface = itf; + return 0; + +error: + *interface = NULL; + if (handle) dlclose(handle); + + return -EINVAL; +} + +int HAL_load(void) +{ + if (load_bt_lib((bt_interface_t const**)&sBtInterface)) { + printf("No Bluetooth Library found\n"); + return -1; + } + return 0; +} + +int HAL_unload(void) +{ + int err = 0; + + bdt_log("Unloading HAL lib"); + + sBtInterface = NULL; + + bdt_log("HAL library unloaded (%s)", strerror(err)); + + return err; +} + +/****************************************************************************** +* + ** HAL test functions & callbacks + +*******************************************************************************/ + +void setup_test_env(void) +{ + int i = 0; + + while (console_cmd_list[i].name != NULL) + { + console_cmd_maxlen = MAX(console_cmd_maxlen, (int)strlen(console_cmd_list[i].name)); + i++; + } +} + +void check_return_status(int status) +{ + if (status != BT_STATUS_SUCCESS) + { + bdt_log("HAL REQUEST FAILED status : %d (%s)", status, dump_bt_status(status)); + } + else + { + bdt_log("HAL REQUEST SUCCESS"); + } +} + +static void do_set_localname(char *p) +{ + printf("set name in progress: %s\n", p); + bt_property_t property = {BT_PROPERTY_BDNAME, static_cast(strlen(p)), p}; + status = sBtInterface->set_adapter_property(&property); +} + +static void adapter_state_changed(bt_state_t state) +{ + int V1 = 1000, V2=2; + char V3[] = "bap_uclient_test"; + bt_property_t property = {(bt_property_type_t)9 /* +BT_PROPERTY_DISCOVERY_TIMEOUT*/, 4, &V1}; + bt_property_t property1 = {(bt_property_type_t)7 /*SCAN*/, 2, &V2}; + bt_property_t property2 ={(bt_property_type_t)1,9, &V3}; + printf("ADAPTER STATE UPDATED : %s\n", (state == BT_STATE_OFF)?"OFF":"ON"); + + g_AdapterState = state; + + if (state == BT_STATE_ON) { + bt_enabled = 1; + status = sBtInterface->set_adapter_property(&property1); + status = sBtInterface->set_adapter_property(&property); + status = sBtInterface->set_adapter_property(&property2); + } else { + bt_enabled = 0; + } +} + +static void adapter_properties_changed(bt_status_t status, + int num_properties, bt_property_t *properties) +{ + char Bd_addr[15] = {0}; + if(NULL == properties) + { + printf("properties is null\n"); + return; + } + switch(properties->type) + { + case BT_PROPERTY_BDADDR: + memcpy(Bd_addr, properties->val, properties->len); + break; + default: + printf("property type not used\n"); + } + return; +} + +static void discovery_state_changed(bt_discovery_state_t state) +{ + printf("Discovery State Updated : %s\n", + (state == BT_DISCOVERY_STOPPED)?"STOPPED":"STARTED"); +} + + +static void pin_request_cb(RawAddress* remote_bd_addr, bt_bdname_t *bd_name, + uint32_t cod, bool min_16_digit ) +{ + remote_bd_address = remote_bd_addr; + //bt_pin_code_t pincode = {{0x31, 0x32, 0x33, 0x34}}; + printf("Enter the pin key displayed in the remote device and terminate the key entry with .\n"); + + /*if(BT_STATUS_SUCCESS != sBtInterface->pin_reply(remote_bd_addr, TRUE, 4 +, &pincode)) + { + printf("Pin Reply failed\n"); + }*/ +} +static void ssp_request_cb(RawAddress* remote_bd_addr, bt_bdname_t *bd_name, + uint32_t cod, bt_ssp_variant_t pairing_variant, +uint32_t pass_key) +{ + printf("ssp_request_cb : name=%s variant=%d passkey=%u\n", bd_name->name, +pairing_variant, pass_key); + if(BT_STATUS_SUCCESS != sBtInterface->ssp_reply(remote_bd_addr, +pairing_variant, TRUE, pass_key)) + { + printf("SSP Reply failed\n"); + } +} + +static void bond_state_changed_cb(bt_status_t status, RawAddress* +remote_bd_addr, bt_bond_state_t state) +{ + g_PairState = state; +} + +static void acl_state_changed(bt_status_t status, RawAddress* remote_bd_addr, +bt_acl_state_t state, + bt_hci_error_code_t hci_reason) +{ + printf("acl_state_changed : remote_bd_addr=%02x:%02x:%02x:%02x:%02x:%02x, \ + acl status=%s \n", + remote_bd_addr->address[0], remote_bd_addr->address[1], remote_bd_addr->address[2], + remote_bd_addr->address[3], remote_bd_addr->address[4], remote_bd_addr->address[5], + (state == BT_ACL_STATE_CONNECTED)?"ACL Connected" :"ACL Disconnected"); +} +static void dut_mode_recv(uint16_t opcode, uint8_t *buf, uint8_t len) +{ + bdt_log("DUT MODE RECV : NOT IMPLEMENTED"); +} + +static void le_test_mode(bt_status_t status, uint16_t packet_count) +{ + bdt_log("LE TEST MODE END status:%s number_of_packets:%d", + dump_bt_status(status), packet_count); +} + +extern int timer_create (clockid_t, struct sigevent *__restrict, timer_t * +__restrict); +extern int timer_settime (timer_t, int, const struct itimerspec *__restrict, +struct itimerspec *__restrict); + +static bool set_wake_alarm(uint64_t delay_millis, bool should_wake, alarm_cb +cb, void *data) +{ + + static timer_t timer; + static bool timer_created; + + if (!timer_created) { + struct sigevent sigevent; + memset(&sigevent, 0, sizeof(sigevent)); + sigevent.sigev_notify = SIGEV_THREAD; + sigevent.sigev_notify_function = (void (*)(union sigval))cb; + sigevent.sigev_value.sival_ptr = data; + timer_create(CLOCK_MONOTONIC, &sigevent, &timer); + timer_created = true; + } + + struct itimerspec new_value; + new_value.it_value.tv_sec = delay_millis / 1000; + new_value.it_value.tv_nsec = (delay_millis % 1000) * 1000 * 1000; + new_value.it_interval.tv_sec = 0; + new_value.it_interval.tv_nsec = 0; + timer_settime(timer, 0, &new_value, NULL); + + return TRUE; +} + +static int acquire_wake_lock(const char *lock_name) +{ + return BT_STATUS_SUCCESS; +} + +static int release_wake_lock(const char *lock_name) +{ + return BT_STATUS_SUCCESS; +} + +static bt_callbacks_t bt_callbacks = { + sizeof(bt_callbacks_t), + adapter_state_changed, + adapter_properties_changed, /*adapter_properties_cb */ + NULL, /* remote_device_properties_cb */ + NULL, /* device_found_cb */ + discovery_state_changed, /* discovery_state_changed_cb */ + pin_request_cb, /* pin_request_cb */ + ssp_request_cb, /* ssp_request_cb */ + bond_state_changed_cb, /*bond_state_changed_cb */ + acl_state_changed, /* acl_state_changed_cb */ + NULL, /* thread_evt_cb */ + dut_mode_recv, /*dut_mode_recv_cb */ + le_test_mode, /* le_test_mode_cb */ + NULL /*energy_info_cb*/ +}; + +static bt_os_callouts_t bt_os_callbacks = { + sizeof(bt_os_callouts_t), + set_wake_alarm, + acquire_wake_lock, + release_wake_lock +}; + + +void bdt_enable(void) +{ + bdt_log("ENABLE BT"); + if (bt_enabled) { + bdt_log("Bluetooth is already enabled"); + return; + } + status = sBtInterface->enable(); + + check_return_status(status); +} + +void bdt_disable(void) +{ + bdt_log("DISABLE BT"); + if (!bt_enabled) { + bdt_log("Bluetooth is already disabled"); + return; + } + status = sBtInterface->disable(); + + check_return_status(status); +} + +void do_pairing(char *p) +{ + RawAddress bd_addr = {{0}}; + int transport = GATT_TRANSPORT_LE; + if(FALSE == GetBdAddr(p, &bd_addr)) return; // arg1 + if(BT_STATUS_SUCCESS != sBtInterface->create_bond(&bd_addr, transport)) + { + printf("Failed to Initiate Pairing \n"); + return; + } +} + + +void bdt_cleanup(void) +{ + bdt_log("CLEANUP"); + sBtInterface->cleanup(); +} + +/****************************************************************************** +* + ** Console commands + +*******************************************************************************/ + +void do_help(char *p) +{ + int i = 0; + char line[128]; +// int pos = 0; + + while (console_cmd_list[i].name != NULL) + { + snprintf(line, 128,"%s", (char*)console_cmd_list[i].name); + bdt_log("%s %s\n", (char*)line, (char*)console_cmd_list[i].help); + i++; + } +} + +void do_quit(char *p) +{ + bdt_shutdown(); +} + +/******************************************************************* + * + * BT TEST CONSOLE COMMANDS + * + * Parses argument lists and passes to API test function + * +*/ + +void do_init(char *p) +{ + bdt_init(); +} + +void do_enable(char *p) +{ + bdt_enable(); +} + +using bluetooth::bap::pacs::PacsClientInterface; +using bluetooth::bap::pacs::PacsClientCallbacks; + +static PacsClientInterface* sPacsClientInterface = nullptr; +static uint16_t pacs_client_id = 0; +static uint8_t pacsSearchComplete = 0; +static uint8_t pacsConnectionComplete = 0; +static uint8_t bapConnectionComplete = 0; +static RawAddress pac_bd_addr; + +class PacsClientCallbacksImpl : public PacsClientCallbacks { + public: + ~PacsClientCallbacksImpl() = default; + void OnInitialized(int status, + int client_id) override { + printf("%d\n", client_id); + pacs_client_id = client_id; + } + void OnConnectionState(const RawAddress& bd_addr, + bluetooth::bap::pacs::ConnectionState state) +override { + printf("%s\n", __func__); + if(state == bluetooth::bap::pacs::ConnectionState::CONNECTED) { + printf("%s Connected\n", __func__); + pacsConnectionComplete = 1; + } else if(state == bluetooth::bap::pacs::ConnectionState::DISCONNECTED) { + printf("%s Disconnected\n", __func__); + } + } + void OnAudioContextAvailable(const RawAddress& bd_addr, + uint32_t available_contexts) override { + printf("%s\n", __func__); + } + void OnSearchComplete(int status, const RawAddress& address, + std::vector sink_pac_records, + std::vector src_pac_records, + uint32_t sink_locations, + uint32_t src_locations, + uint32_t available_contexts, + uint32_t supported_contexts) override { + pacsSearchComplete = 1; + printf("%s\n", __func__); + } +}; + +static PacsClientCallbacksImpl sPacsClientCallbacks; + +void do_pacs_discovery(char *p) +{ + if(FALSE == GetBdAddr(p, &pac_bd_addr)) return; // arg1 + sPacsClientInterface = (PacsClientInterface*) + sBtInterface->get_profile_interface(BT_PROFILE_PACS_CLIENT_ID); + sPacsClientInterface->Init(&sPacsClientCallbacks); + sleep(1); + printf("%s going for connect\n", __func__); + sPacsClientInterface->Connect(pacs_client_id, pac_bd_addr); + while(!pacsConnectionComplete) sleep(1); + printf("%s going for discovery\n", __func__); + sPacsClientInterface->StartDiscovery(pacs_client_id, pac_bd_addr); + while(!pacsSearchComplete) sleep(1); + printf("%s going for disconnect\n", __func__); + sleep(5); + sPacsClientInterface->Disconnect(pacs_client_id, pac_bd_addr); +} + +using bluetooth::bap::ascs::AscsClientInterface; +using bluetooth::bap::ascs::AscsClientCallbacks; + +static AscsClientInterface* sAscsClientInterface = nullptr; +static uint16_t ascs_client_id = 0; +static uint8_t ascsSearchComplete = 0; +static uint8_t ascsConnectionComplete = 0; +static RawAddress ascs_bd_addr; + +class AscsClientCallbacksImpl : public AscsClientCallbacks { + public: + ~AscsClientCallbacksImpl() = default; + void OnAscsInitialized(int status, int client_id) override { + printf("%d\n", client_id); + ascs_client_id = client_id; + } + + void OnConnectionState(const RawAddress& address, + bluetooth::bap::ascs::GattState state) override { + printf("%s\n", __func__); + if(state == bluetooth::bap::ascs::GattState::CONNECTED) { + printf("%s Connected\n", __func__); + ascsConnectionComplete = 1; + } else if(state == bluetooth::bap::ascs::GattState::DISCONNECTED) { + printf("%s Disconnected\n", __func__); + } + } + + void OnAseOpFailed(const RawAddress& address, + bluetooth::bap::ascs::AseOpId ase_op_id, + std::vector status) { + printf("%s\n", __func__); + + } + + void OnAseState(const RawAddress& address, + bluetooth::bap::ascs::AseParams ase) override { + printf("%s\n", __func__); + } + + void OnSearchComplete(int status, const RawAddress& address, + std::vector sink_ase_list, + std::vector src_ase_list) override { + printf("%s\n", __func__); + ascsSearchComplete = 1; + } +}; + +static AscsClientCallbacksImpl sAscsClientCallbacks; + +void do_ascs_discovery(char *p) +{ + if(FALSE == GetBdAddr(p, &ascs_bd_addr)) return; // arg1 + sAscsClientInterface = (AscsClientInterface*) + sBtInterface->get_profile_interface(BT_PROFILE_ASCS_CLIENT_ID); + sAscsClientInterface->Init(&sAscsClientCallbacks); + sleep(1); + printf("%s going for connect\n", __func__); + sAscsClientInterface->Connect(ascs_client_id, ascs_bd_addr); + while(!ascsConnectionComplete) sleep(1); + printf("%s going for discovery\n", __func__); + sAscsClientInterface->StartDiscovery(ascs_client_id, ascs_bd_addr); + while(!ascsSearchComplete) sleep(1); + printf("%s going for disconnect\n", __func__); + sAscsClientInterface->Disconnect(ascs_client_id, ascs_bd_addr); +} + +template +std::string loghex(T x) { + std::stringstream tmp; + tmp << "0x" << std::internal << std::hex << std::setfill('0') + << std::setw(sizeof(T) * 2) << (unsigned int)x; + return tmp.str(); +} + +using bluetooth::bap::ucast::UcastClientCallbacks; +using bluetooth::bap::ucast::UcastClientInterface; + +static UcastClientInterface* sUcastClientInterface = nullptr; + +class UcastClientCallbacksImpl : public UcastClientCallbacks { + public: + ~UcastClientCallbacksImpl() = default; + void OnStreamState(const RawAddress &address, + std::vector streams_state_info) override { + for (auto it = streams_state_info.begin(); + it != streams_state_info.end(); it++) { + printf("%s stream type %d\n", __func__, (it->stream_type.type)); + printf("%s stream dir %s\n", __func__, loghex(it->stream_type.direction).c_str()); + printf("%s stream state %d\n", __func__, static_cast (it->stream_state)); + if(static_cast (it->stream_state) == 2 || + static_cast (it->stream_state) == 0) { + bapConnectionComplete = 1; + } + } + } + void OnStreamConfig(const RawAddress &address, + std::vector streams_config_info) override { + printf("%s\n",__func__); + } + void OnStreamAvailable(const RawAddress &address, + uint16_t src_audio_contexts, + uint16_t sink_audio_contexts) override { + printf("%s\n",__func__); + } +}; + +static UcastClientCallbacksImpl sUcastClientCallbacks; + +typedef struct { + char bdAddr[13]; + uint16_t profile; + uint16_t context; + uint8_t direction; +} Servers; + +typedef struct { + uint8_t cnt; + char codecConfig[7]; + char audioConfig[5]; + std::vector serv; +} UserParms; + +typedef struct { + uint8_t audio_dir; + uint8_t stereo; +} AudioType; + +typedef struct { + uint8_t num_servers; + uint8_t num_cises; + std::vector audio_type; +} AudioConfigSettings; + +// + +std::map audioConfigMap = { + {"1_1", {1, 1, {{ASE_DIRECTION_SINK, 0}}}}, // EB streaming + {"2_1", {1, 1, {{ASE_DIRECTION_SRC, 0}}}}, // EB Recording + {"3_1", {1, 1, {{ASE_DIRECTION_SRC|ASE_DIRECTION_SINK, 0}}}}, // EB Call Mono Bi-Dir CIS + {"4_1", {1, 1, {{ASE_DIRECTION_SINK, ASE_SINK_STEREO}}}}, // Stereo Headset stereo streaming + + {"5_1", {1, 1, {{ASE_DIRECTION_SRC|ASE_DIRECTION_SINK, ASE_SINK_STEREO}}}}, // EB Call with speaker stereo mono mic + + {"6_1", {1, 2, {{ASE_DIRECTION_SINK, 0}, {ASE_DIRECTION_SINK, 0}}}}, // TWM Streaming + {"6_2", {2, 2, {{ASE_DIRECTION_SINK, 0}, {ASE_DIRECTION_SINK, 0}}}}, // EBP Streaming same as 1_1 + {"7_1", {1, 2, {{ASE_DIRECTION_SINK, 0}, {ASE_DIRECTION_SRC, 0}}}}, // EB Call with dual CIS ( same as 3_1) + {"7_2", {2, 2, {{ASE_DIRECTION_SINK, 0}, {ASE_DIRECTION_SRC, 0}}}}, // EBP Call with speaker on EB1 and mic on EB2 + {"8_1", {1, 2, {{ASE_DIRECTION_SINK, 0}, {ASE_DIRECTION_SRC|ASE_DIRECTION_SINK, 0}}}}, // Headset Call with single mic + {"8_2", {2, 2, {{ASE_DIRECTION_SRC|ASE_DIRECTION_SINK, 0}, {ASE_DIRECTION_SINK, 0}}}}, // EBP Call with mic from one EB + {"9_1", {1, 2, {{ASE_DIRECTION_SRC, 0}, {ASE_DIRECTION_SRC, 0}}}}, // TWM Recording + {"9_2", {2, 2, {{ASE_DIRECTION_SRC, 0}, {ASE_DIRECTION_SRC, 0}}}}, // EBP Recording +{"10_1", {1, 1, {{ASE_DIRECTION_SRC, ASE_SRC_STEREO}}}}, // EB stereo Recording +{"11_1", {1, 2, {{ASE_DIRECTION_SRC|ASE_DIRECTION_SINK, 0}, {ASE_DIRECTION_SRC|ASE_DIRECTION_SINK, 0}}}}, // TWM Call +{"11_2", {2, 2, {{ASE_DIRECTION_SRC|ASE_DIRECTION_SINK, 0}, {ASE_DIRECTION_SRC|ASE_DIRECTION_SINK, 0}}}}}; // EBP Call + +int getInt(std::string &str) +{ + int ret; + std::stringstream integer(str); + integer >> ret; + return ret; +} + +void parse_parms(char *p, UserParms *ptr) +{ + std::string line(p); + std::vector token; + std::stringstream check1(line); + std::string intermediate; + while(getline(check1, intermediate, ' ')) + { + token.push_back(intermediate); + } + ptr->cnt = token.size(); + if (ptr->cnt == 11) + { + memcpy(ptr->codecConfig, token[1].c_str(), token[1].size()); + memcpy(ptr->audioConfig, token[2].c_str(), token[2].size()); + Servers serv1, serv2; + memcpy(serv1.bdAddr, token[3].c_str(), token[3].size()); + serv1.profile = static_cast(getInt(token[4])); + serv1.direction = static_cast(getInt(token[5])); + serv1.context = static_cast(getInt(token[6])); + ptr->serv.push_back(serv1); + memcpy(serv2.bdAddr, token[7].c_str(), token[7].size()); + serv2.profile = static_cast(getInt(token[8])); + serv2.direction = static_cast(getInt(token[9])); + serv2.context = static_cast(getInt(token[10])); + ptr->serv.push_back(serv2); + } + else if (ptr->cnt == 9) + { + Servers serv1, serv2; + memcpy(serv1.bdAddr, token[1].c_str(), token[1].size()); + serv1.profile = static_cast(getInt(token[2])); + serv1.direction = static_cast(getInt(token[3])); + serv1.context = static_cast(getInt(token[4])); + ptr->serv.push_back(serv1); + memcpy(serv2.bdAddr, token[5].c_str(), token[5].size()); + serv2.profile = static_cast(getInt(token[6])); + serv2.direction = static_cast(getInt(token[7])); + serv2.context = static_cast(getInt(token[8])); + ptr->serv.push_back(serv2); + } + else if (ptr->cnt == 7) + { + memcpy(ptr->codecConfig, token[1].c_str(), token[1].size()); + memcpy(ptr->audioConfig, token[2].c_str(), token[2].size()); + Servers serv1; + memcpy(serv1.bdAddr, token[3].c_str(), token[3].size()); + serv1.profile = static_cast(getInt(token[4])); + serv1.direction = static_cast(getInt(token[5])); + serv1.context = static_cast(getInt(token[6])); + ptr->serv.push_back(serv1); + } + else if (ptr->cnt == 5) + { + Servers serv1; + memcpy(serv1.bdAddr, token[1].c_str(), token[1].size()); + serv1.profile = static_cast(getInt(token[2])); + serv1.direction = static_cast(getInt(token[3])); + serv1.context = static_cast(getInt(token[4])); + ptr->serv.push_back(serv1); + } + else + { + printf("%s ERROR: Input\n", __func__); + } +} + +constexpr uint8_t CONFIG_FRAME_DUR_INDEX = 0x04; +constexpr uint8_t CONFIG_OCTS_PER_FRAME_INDEX = 0x04; +constexpr uint8_t CONFIG_PREF_AUDIO_CONT_INDEX = 0x06; // CS1 + +bool UpdateFrameDuration(bluetooth::bap::pacs::CodecConfig *config , + uint8_t frame_dur) { + uint64_t value = 0xFF; + config->codec_specific_1 &= + ~(value << (CONFIG_FRAME_DUR_INDEX*8)); + config->codec_specific_1 |= + static_cast(frame_dur) << (CONFIG_FRAME_DUR_INDEX * 8); + return true; +} + + +bool UpdatePreferredAudioContext(bluetooth::bap::pacs::CodecConfig *config , + uint16_t pref_audio_context) { + uint64_t value = 0xFFFF; + config->codec_specific_1 &= ~(value << (CONFIG_PREF_AUDIO_CONT_INDEX*8)); + config->codec_specific_1 |= static_cast(pref_audio_context) << + (CONFIG_PREF_AUDIO_CONT_INDEX * 8); + return true; +} + +bool UpdateOctsPerFrame(bluetooth::bap::pacs::CodecConfig *config , + uint16_t octs_per_frame) { + uint64_t value = 0xFFFF; + config->codec_specific_2 &= + ~(value << (CONFIG_OCTS_PER_FRAME_INDEX * 8)); + config->codec_specific_2 |= + static_cast(octs_per_frame) << (CONFIG_OCTS_PER_FRAME_INDEX * 8); + return true; +} + +void set_conn_info(bluetooth::bap::ucast::StreamConnect *conn_info, int type, int context, int dir) +{ + conn_info->stream_type.type = type; + conn_info->stream_type.direction = dir; + conn_info->stream_type.audio_context = context; +} + +bluetooth::bap::pacs::CodecSampleRate get_sample_rate (char *p) +{ + std::string str = p; + if (str.find("16_") != std::string::npos) + return bluetooth::bap::pacs::CodecSampleRate::CODEC_SAMPLE_RATE_16000; + else if (str.find("24_") != std::string::npos) + return bluetooth::bap::pacs::CodecSampleRate::CODEC_SAMPLE_RATE_24000; + else if (str.find("32_") != std::string::npos) + return bluetooth::bap::pacs::CodecSampleRate::CODEC_SAMPLE_RATE_32000; + else if (str.find("48_") != std::string::npos) + return bluetooth::bap::pacs::CodecSampleRate::CODEC_SAMPLE_RATE_48000; + else if (str.find("8_") != std::string::npos) + return bluetooth::bap::pacs::CodecSampleRate::CODEC_SAMPLE_RATE_8000; + else + return bluetooth::bap::pacs::CodecSampleRate::CODEC_SAMPLE_RATE_NONE; +} + +int get_frame_duration (char *p) +{ + std::string str = p; + int ret; + if ((str.find("_1_") != std::string::npos) || + (str.find("_3_") != std::string::npos) || + (str.find("_5_") != std::string::npos)) + ret = static_cast(bluetooth::bap::pacs::CodecFrameDuration::FRAME_DUR_7_5); + else if ((str.find("_2_") != std::string::npos) || + (str.find("_4_") != std::string::npos) || + (str.find("_6_") != std::string::npos)) + ret = static_cast(bluetooth::bap::pacs::CodecFrameDuration::FRAME_DUR_10); + else + ret = -1; + return ret; +} + +int get_sdu_interval (char *p) +{ + std::string str = p; + int ret; + if ((str.find("_1_") != std::string::npos) || + (str.find("_3_") != std::string::npos) || + (str.find("_5_") != std::string::npos)) + ret = 7500; + else if ((str.find("_2_") != std::string::npos) || + (str.find("_4_") != std::string::npos) || + (str.find("_6_") != std::string::npos)) + ret = 10000; + else + ret = -1; + return ret; +} + +std::map octetPerFrame = +{{"8_1", 26},{"8_2", 30},{"16_1", 30},{"16_2", 40}, +{"24_1", 45},{"24_2", 60},{"32_1", 60},{"32_2", 80}, +{"48_1", 75},{"48_2", 100},{"48_3", 90},{"48_4", 120}, +{"48_5", 117},{"48_6", 155}}; + +int get_octetPerFrame (char *p) +{ + std::string str = p; + int ret = -1; + size_t pos = str.rfind('_'); + std::string key = str.substr(0, pos); + for (std::map::iterator it = + octetPerFrame.begin(); it != octetPerFrame.end(); it++) + { + if (key.compare(it->first) == 0) + ret = it->second; + } + return ret; +} + +std::map tport_latency = +{{"8_1_1", 8},{"16_1_1", 8},{"24_1_1", 8},{"32_1_1", 8}, + {"8_2_1", 10},{"16_2_1", 10},{"24_2_1", 10},{"32_2_1", 10}, +{"48_1_1", 15},{"48_3_1", 15},{"48_5_1", 15}, +{"48_2_1", 20},{"48_4_1", 20},{"48_6_1", 20}, + {"8_1_2", 75},{"16_1_2", 75},{"24_1_2", 75}, +{"31_1_2", 75},{"48_1_2", 75},{"48_3_2", 75},{"48_5_2", 75}, + {"8_2_2", 95},{"16_2_2", 95},{"24_2_2", 95}, +{"32_2_2", 95},{"48_2_2", 95}, +{"48_4_2", 100},{"48_6_2", 100}}; + +int get_tport_latency (char *p) +{ + std::string str = p; + for (std::map::iterator it = tport_latency.begin(); + it != tport_latency.end(); it++) + { + if (str.compare(it->first) == 0) + return it->second; + } + return -1; +} + +int get_rtn (char *p) +{ + std::string str = p; + int ret; + size_t pos = str.rfind('_'); + std::string key = str.substr(pos); + if (str.find("_1_2") != std::string::npos || + str.find("_2_2") != std::string::npos || + str.find("_3_2") != std::string::npos || + str.find("_4_2") != std::string::npos || + str.find("_5_2") != std::string::npos || + str.find("_6_2") != std::string::npos ) { + ret = 13; + return ret; + } + if (str.find("48_") != std::string::npos) + ret = 5; + if ((str.find("8_") != std::string::npos) || + (str.find("16_") != std::string::npos) || + (str.find("24_") != std::string::npos) || + (str.find("32_") != std::string::npos)) + ret = 2; + else + ret = -1; + return ret; +} + +int getAudioConfigSettings(char *p, AudioConfigSettings *ptr) +{ + int ret = -1; + std::string key = p; + for (std::map::iterator it = + audioConfigMap.begin(); + it != audioConfigMap.end(); it++) + { + if (key.compare(it->first) == 0) + { + *ptr = it->second; + printf(" %s ERROR: audio type 0 %d \n", __func__, ptr->audio_type[0].audio_dir); + printf(" %s ERROR: audio type 1 %d \n", __func__, ptr->audio_type[1].audio_dir); + //memcpy(ptr, &it->second, sizeof(AudioConfigSettings)); + ret = 0; + } + } + return ret; +} + +typedef struct +{ + uint8_t A; + uint8_t B; + uint8_t C; +} setFormat; + +void set(void *dest, setFormat src) +{ + memcpy(dest, &src, sizeof(setFormat)); +} + +void set_codec_qos_config (bluetooth::bap::ucast::CodecQosConfig *codec_qos_config, + char *codecConfig, AudioConfigSettings *acs, + uint8_t audio_direction, uint16_t context, + uint8_t server_id, + uint8_t server_count, uint8_t total_servers) +{ + int frameDuration, octetPerFrame, tport_latency, sdu_interval, rtn, cis_t; + bluetooth::bap::pacs::CodecSampleRate sampleRate; + bool stereo_t = false; + bluetooth::bap::ucast::CIGConfig cig_config; + sampleRate = get_sample_rate(codecConfig); + printf("Sample Rate %d\n", sampleRate); + printf("server_id %d\n", server_id); + + if (sampleRate == bluetooth::bap::pacs::CodecSampleRate::CODEC_SAMPLE_RATE_NONE) + { + printf(" %s ERROR: sample rate\n", __func__); + exit(0); + } + frameDuration = get_frame_duration(codecConfig); + if (frameDuration < 0) + { + printf(" %s ERROR: frame duration\n", __func__); + exit(0); + } + octetPerFrame = get_octetPerFrame(codecConfig); + if (octetPerFrame < 0) + { + printf(" %s ERROR: octet per frame\n", __func__); + exit(0); + } + tport_latency = get_tport_latency(codecConfig); + if (tport_latency < 0) + { + printf(" %s ERROR: max transport latency\n", __func__); + exit(0); + } + rtn = get_rtn(codecConfig); + if (rtn < 0) + { + printf(" %s ERROR: re-transmission\n", __func__); + exit(0); + } + sdu_interval = get_sdu_interval(codecConfig); + codec_qos_config->codec_config.codec_type = + bluetooth::bap::pacs::CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config->codec_config.codec_priority = + bluetooth::bap::pacs::CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config->codec_config.sample_rate = sampleRate; + UpdateFrameDuration(&codec_qos_config->codec_config, + static_cast(frameDuration)); + UpdatePreferredAudioContext(&codec_qos_config->codec_config, context); + cig_config.cig_id = 1; + cig_config.cis_count = acs->num_cises; + cig_config.packing = 0x01; // interleaved + cig_config.framing = 0x00; // unframed + cig_config.max_tport_latency_m_to_s = static_cast(tport_latency); + cig_config.max_tport_latency_s_to_m = static_cast(tport_latency); + if (sdu_interval == 7500) + { + set(&cig_config.sdu_interval_m_to_s, {0x4C, 0x1D, 0x00}); + set(&cig_config.sdu_interval_s_to_m, {0x4C, 0x1D, 0x00}); + } + else + { + set(&cig_config.sdu_interval_m_to_s, {0x10, 0x27, 0x00}); + set(&cig_config.sdu_interval_s_to_m, {0x10, 0x27, 0x00}); + } + memcpy(&codec_qos_config->qos_config.cig_config, + &cig_config, sizeof(cig_config)); + for (uint8_t i = 0; i < acs->num_cises; i++) { + bluetooth::bap::ucast::CISConfig cis_config; + bluetooth::bap::ucast::ASCSConfig ascs_config; + int max_sdu_m_to_s; + int max_sdu_s_to_m; + cis_config.cis_id = i; + ascs_config.cig_id = 1; + ascs_config.cis_id = i; + max_sdu_m_to_s = max_sdu_s_to_m = get_octetPerFrame(codecConfig); + printf("audio_dir %d\n", acs->audio_type[i].audio_dir); + printf("max_sdu_m_to_s %d\n", max_sdu_m_to_s); + printf("max_sdu_s_to_m %d\n", max_sdu_s_to_m); + if(acs->audio_type[i].stereo & ASE_SRC_STEREO) { + max_sdu_s_to_m *= 2; + if(audio_direction & ASE_DIRECTION_SRC) stereo_t = true; + } + if(acs->audio_type[i].stereo & ASE_SINK_STEREO) { + max_sdu_m_to_s *= 2; + if(audio_direction & ASE_DIRECTION_SINK) stereo_t = true; + } + + if (acs->audio_type[i].audio_dir == (ASE_DIRECTION_SINK|ASE_DIRECTION_SRC)) + { + printf("i %d Filling both m to s and s to m \n", i); + cis_config.max_sdu_m_to_s = static_cast(max_sdu_m_to_s); + cis_config.max_sdu_s_to_m = static_cast(max_sdu_s_to_m); + ascs_config.bi_directional = true; + } + else if (acs->audio_type[i].audio_dir == ASE_DIRECTION_SRC) + { + printf("i %d Filling s to m \n", i); + cis_config.max_sdu_s_to_m = static_cast(max_sdu_s_to_m); + cis_config.max_sdu_m_to_s = 0; + ascs_config.bi_directional = false; + } + else if (acs->audio_type[i].audio_dir == ASE_DIRECTION_SINK) + { + printf("i %d Filling m to s \n", i); + cis_config.max_sdu_m_to_s = static_cast(max_sdu_m_to_s); + cis_config.max_sdu_s_to_m = 0; + ascs_config.bi_directional = false; + } + cis_config.phy_m_to_s = 0x02; + cis_config.phy_s_to_m = 0x02; + cis_config.rtn_m_to_s = static_cast(rtn); + cis_config.rtn_s_to_m = static_cast(rtn); + printf("rtn %d \n", rtn); + set(&ascs_config.presentation_delay, {0x40, 0x9C, 0x00}); + codec_qos_config->qos_config.cis_configs.push_back(cis_config); + + + printf("i %d server_id %d \n", i, server_id); + if(total_servers == 1) { + if(acs->num_cises == 1) { + codec_qos_config->qos_config.ascs_configs.push_back(ascs_config); + } else if(acs->num_cises == 2) { + if(acs->audio_type[i % acs->num_cises].audio_dir == + acs->audio_type[(i + 1) % acs->num_cises].audio_dir) { + codec_qos_config->qos_config.ascs_configs.push_back(ascs_config); + } else if( i == server_count) { + codec_qos_config->qos_config.ascs_configs.push_back(ascs_config); + } + } + } else if(total_servers == 2) { + if(i == server_id) { + codec_qos_config->qos_config.ascs_configs.push_back(ascs_config); + } + } + } + if (stereo_t == true) { + codec_qos_config->codec_config.channel_mode = + bluetooth::bap::pacs::CodecChannelMode::CODEC_CHANNEL_MODE_STEREO; + UpdateOctsPerFrame(&codec_qos_config->codec_config, + static_cast(octetPerFrame*2)); + } else { + codec_qos_config->codec_config.channel_mode = + bluetooth::bap::pacs::CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + UpdateOctsPerFrame(&codec_qos_config->codec_config, + static_cast(octetPerFrame)); + } +} + +void do_bap_connect (char *p) +{ + UserParms args; + parse_parms(p, &args); + bluetooth::bap::ucast::CodecQosConfig codec_qos_config; + bluetooth::bap::ucast::CodecQosConfig codec_qos_config_2; + AudioConfigSettings acs; + bapConnectionComplete = 0; + if (getAudioConfigSettings(args.audioConfig, &acs) < 0) + { + printf("%s ERROR: AudioConfig\n", __func__); + exit(0); + } + //set_codec_qos_config(&codec_qos_config, args.codecConfig, &acs); + for (uint8_t i = 0; i < args.serv.size(); i++) { + RawAddress bap_bd_addr; + bluetooth::bap::ucast::StreamConnect conn_info; + std::vector streams; + codec_qos_config.qos_config.cis_configs.clear(); + codec_qos_config.qos_config.ascs_configs.clear(); + codec_qos_config_2.qos_config.cis_configs.clear(); + codec_qos_config_2.qos_config.ascs_configs.clear(); + if(FALSE == GetBdAddr(args.serv[i].bdAddr, &bap_bd_addr)) return; + if (args.serv[i].direction & ASE_DIRECTION_SINK) + { + set_codec_qos_config(&codec_qos_config, args.codecConfig, + &acs, ASE_DIRECTION_SINK, args.serv[i].context, + i, 0, + args.serv.size()); + set_conn_info(&conn_info, args.serv[i].profile, + args.serv[i].context, ASE_DIRECTION_SINK); + printf("%s ERROR: context %d\n", __func__, args.serv[i].context); + conn_info.codec_qos_config_pair.push_back(codec_qos_config); + streams.push_back(conn_info); + } + if (args.serv[i].direction & ASE_DIRECTION_SRC) + { + set_codec_qos_config(&codec_qos_config_2, args.codecConfig, + &acs, ASE_DIRECTION_SRC, args.serv[i].context, + i, 1, + args.serv.size()); + set_conn_info(&conn_info, args.serv[i].profile, + args.serv[i].context, ASE_DIRECTION_SRC); + printf("%s ERROR: context %d\n", __func__, args.serv[i].context); + conn_info.codec_qos_config_pair.push_back(codec_qos_config_2); + streams.push_back(conn_info); + } + std::vector address; + address.push_back(bap_bd_addr); + sUcastClientInterface->Connect(address, true, streams); + } + while(!bapConnectionComplete) sleep(1); +} + +void do_bap_disconnect (char *p) +{ + UserParms args; + parse_parms(p, &args); + for (uint8_t i = 0; i < ((args.cnt)/4); i++) { + RawAddress bap_bd_addr; + if(FALSE == GetBdAddr(args.serv[i].bdAddr, &bap_bd_addr)) return; + std::vector streams; + if (args.serv[i].direction & 1) { + bluetooth::bap::ucast::StreamType type_1 = + { .type = static_cast(args.serv[i].profile), + .audio_context = args.serv[i].context, + .direction = 1 + }; + streams.push_back(type_1); + } + if (args.serv[i].direction & 2) { + bluetooth::bap::ucast::StreamType type_1 = + { .type = static_cast(args.serv[i].profile), + .audio_context = args.serv[i].context, + .direction = 2 + }; + streams.push_back(type_1); + } + sUcastClientInterface->Disconnect(bap_bd_addr, streams); + } +} + +void do_bap_start (char *p) +{ + UserParms args; + parse_parms(p, &args); + for (uint8_t i = 0; i < ((args.cnt)/4); i++) { + RawAddress bap_bd_addr; + if(FALSE == GetBdAddr(args.serv[i].bdAddr, &bap_bd_addr)) return; + std::vector streams; + if (args.serv[i].direction & 1) { + bluetooth::bap::ucast::StreamType type_1 = + { .type = static_cast(args.serv[i].profile), + .audio_context = args.serv[i].context, + .direction = 1 + }; + streams.push_back(type_1); + } + if (args.serv[i].direction & 2) { + bluetooth::bap::ucast::StreamType type_1 = + { .type = static_cast(args.serv[i].profile), + .audio_context = args.serv[i].context, + .direction = 2 + }; + streams.push_back(type_1); + } + sUcastClientInterface->Start(bap_bd_addr, streams); + } +} + +void do_bap_stop (char *p) +{ + UserParms args; + parse_parms(p, &args); + for (uint8_t i = 0; i < ((args.cnt)/4); i++) { + RawAddress bap_bd_addr; + if(FALSE == GetBdAddr(args.serv[i].bdAddr, &bap_bd_addr)) return; + std::vector streams; + if (args.serv[i].direction & 1) { + bluetooth::bap::ucast::StreamType type_1 = + { .type = static_cast(args.serv[i].profile), + .audio_context = args.serv[i].context, + .direction = 1 + }; + streams.push_back(type_1); + } + if (args.serv[i].direction & 2) { + bluetooth::bap::ucast::StreamType type_1 = + { .type = static_cast(args.serv[i].profile), + .audio_context = args.serv[i].context, + .direction = 2 + }; + streams.push_back(type_1); + } + sUcastClientInterface->Stop(bap_bd_addr, streams); + } +} + +void do_bap_disc_in_connecting (char *p) +{ + int del; + printf("Enter the delay (ms)> "); + std::cin >> del; + do_bap_connect(p); + usleep(del *1000); + do_bap_disconnect(p); +} + +void do_bap_disc_in_starting (char *p) +{ + int del; + printf("Enter the delay (ms)> "); + std::cin >> del; + do_bap_start(p); + usleep(del *1000); + do_bap_disconnect(p); +} + +void do_bap_disc_in_stopping (char *p) +{ + int del; + printf("Enter the delay (ms)> "); + std::cin >> del; + do_bap_stop(p); + usleep(del *1000); + do_bap_disconnect(p); +} + +void do_bap_stop_in_starting (char *p) +{ + int del; + printf("Enter the delay (ms)> "); + std::cin >> del; + do_bap_start(p); + usleep(del *1000); + do_bap_stop(p); +} + +void do_bap_update_stream (char *p) +{ + UserParms args; + parse_parms(p, &args); + for (uint8_t i = 0; i < ((args.cnt)/4); i++) { + RawAddress bap_bd_addr; + if(FALSE == GetBdAddr(args.serv[i].bdAddr, &bap_bd_addr)) return; + std::vector Update_Stream; + if (args.serv[i].direction & 1) { + bluetooth::bap::ucast::StreamType type_1 = + { .type = static_cast(args.serv[i].profile), + .direction = 1 + }; + bluetooth::bap::ucast::StreamUpdate sUpdate = + { + type_1, + bluetooth::bap::ucast::StreamUpdateType::STREAMING_CONTEXT, + args.serv[i].context + }; + Update_Stream.push_back(sUpdate); + } + if (args.serv[i].direction & 2) { + bluetooth::bap::ucast::StreamType type_1 = + { .type = static_cast(args.serv[i].profile), + .direction = 2 + }; + bluetooth::bap::ucast::StreamUpdate sUpdate = + { + type_1, + bluetooth::bap::ucast::StreamUpdateType::STREAMING_CONTEXT, + args.serv[i].context + }; + Update_Stream.push_back(sUpdate); + } + sUcastClientInterface->UpdateStream(bap_bd_addr, Update_Stream); + } +} + +void do_disable(char *p) +{ + bdt_disable(); +} + +void do_cleanup(char *p) +{ + bdt_cleanup(); +} + +void bdt_init(void) +{ + bdt_log("INIT BT "); + status = sBtInterface->init(&bt_callbacks, false, false, 0, nullptr, false); + sleep(1); + if (status == BT_STATUS_SUCCESS) { + status = sBtInterface->set_os_callouts(&bt_os_callbacks); + } + check_return_status(status); +} + +/****************************************************************************** +* + ** GATT SERVER API commands + +*******************************************************************************/ + +/* + * Main console command handler +*/ + +static void process_cmd(char *p, unsigned char is_job) +{ + char cmd[2048]; + int i = 0; + bt_pin_code_t pincode; + char *p_saved = p; + + get_str(&p, cmd); + + /* table commands */ + while (console_cmd_list[i].name != NULL) + { + if (is_cmd(console_cmd_list[i].name)) + { + if (!is_job && console_cmd_list[i].is_job) + create_cmdjob(p_saved); + else + { + console_cmd_list[i].handler(p); + } + return; + } + i++; + } + //pin key + if(cmd[6] == '.') { + for(i=0; i<6; i++) { + pincode.pin[i] = cmd[i]; + } + if(BT_STATUS_SUCCESS != sBtInterface->pin_reply(remote_bd_address, +TRUE, strlen((const char*)pincode.pin), &pincode)) { + printf("Pin Reply failed\n"); + } + //flush the char for pinkey + cmd[6] = 0; + } + else { + bdt_log("%s : unknown command\n", p_saved); + do_help(NULL); + } +} + +int main() +{ + config_permissions(); + bdt_log("\n:::::::::::::::::::::::::::::::::::::::::::::::::::"); + bdt_log(":: Bluedroid test app starting"); + + if ( HAL_load() < 0 ) { + perror("HAL failed to initialize, exit\n"); + unlink(PID_FILE); + exit(0); + } + + setup_test_env(); + + /* Automatically perform the init */ + bdt_init(); + sleep(5); + bdt_enable(); + sleep(5); + + sUcastClientInterface = (UcastClientInterface*) + sBtInterface->get_profile_interface(BT_PROFILE_BAP_UCLIENT_ID); + sUcastClientInterface->Init(&sUcastClientCallbacks); + sPacsClientInterface = (PacsClientInterface*) + sBtInterface->get_profile_interface(BT_PROFILE_PACS_CLIENT_ID); + sPacsClientInterface->Init(&sPacsClientCallbacks); + sAscsClientInterface = (AscsClientInterface*) + sBtInterface->get_profile_interface(BT_PROFILE_ASCS_CLIENT_ID); + sAscsClientInterface->Init(&sAscsClientCallbacks); + + sleep(5); + while(!main_done) + { + char line[2048], *result; + + + /* command prompt */ + printf( ">" ); + fflush(stdout); + + if ((result = fgets (line, 2048, stdin)) == NULL) + { + printf("ERROR: The string is NULL. code %d\n", errno); + exit(0); + } + else + { + printf("UserInput\n"); + } + + if (line[0]!= '\0') + { + /* remove linefeed */ + line[strlen(line)-1] = 0; + + process_cmd(line, 0); + memset(line, '\0', 2048); + } + } + HAL_unload(); + + bdt_log(":: bap uca test app terminating"); + + return 0; +} + +int GetFileName(char *p, char *filename) +{ +// uint8_t i; + int len; + + skip_blanks(&p); + + printf("Input file name = %s\n", p); + + if (p == NULL) + { + printf("\nInvalid File Name... Please enter file name\n"); + return FALSE; + } + len = strlen(p); + + memcpy(filename, p, len); + filename[len] = '\0'; + + return TRUE; +} +uint8_t check_length(char *p) +{ + uint8_t val = 0; + while (*p != ' ' && *p != '\0') + { + val++; + p++; + } + return val; +} +int GetBdAddr(char *p, RawAddress* pbd_addr) +{ + char Arr[13] = {0}; + char *pszAddr = NULL; + uint8_t k1 = 0; + uint8_t k2 = 0; + uint8_t i; + + skip_blanks(&p); + + printf("Input=%s\n", p); + + if(12 != check_length(p)) + { + printf("\nInvalid Bd Address. Format[112233445566]\n"); + return FALSE; + } + memcpy(Arr, p, 12); + + for(i=0; i<12; i++) + { + Arr[i] = tolower(Arr[i]); + } + pszAddr = Arr; + + for(i=0; i<6; i++) + { + k1 = (uint8_t) ( (*pszAddr >= 'a') ? + ( 10 + (uint8_t)( *pszAddr - 'a' )) : (*pszAddr - '0') ); + pszAddr++; + k2 = (uint8_t) ( (*pszAddr >= 'a') ? + ( 10 + (uint8_t)( *pszAddr - 'a' )) : (*pszAddr - '0') ); + pszAddr++; + + if ( (k1>15)||(k2>15) ) + { + return FALSE; + } + pbd_addr->address[i] = (k1<<4 | k2); + } + return TRUE; +} +#endif //BAP_UNICAST_TEST_APP_INTERFACE diff --git a/le_audio/certification_tool/types/Android.bp b/le_audio/certification_tool/types/Android.bp new file mode 100644 index 00000000000..3f7e3370937 --- /dev/null +++ b/le_audio/certification_tool/types/Android.bp @@ -0,0 +1,17 @@ + +cc_library_static { + name: "libbluetooth-types-qti", + vendor_available: true, + enabled: false, + defaults: ["fluoride_types_defaults"], + cflags: [ + /* we export all classes, so change default visibility, instead of having EXPORT_SYMBOL on each class*/ + "-fvisibility=default", + ], + host_supported: true, + srcs: [ + "class_of_device.cc", + "raw_address.cc", + "bluetooth/uuid.cc", + ], +} diff --git a/le_audio/certification_tool/types/bluetooth/uuid.cc b/le_audio/certification_tool/types/bluetooth/uuid.cc new file mode 100644 index 00000000000..d05f4370f0c --- /dev/null +++ b/le_audio/certification_tool/types/bluetooth/uuid.cc @@ -0,0 +1,177 @@ +/****************************************************************************** + * + * Copyright (C) 2017 The Android Open Source Project + * + * 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. + * + ******************************************************************************/ + +#include "uuid.h" + +#include +#include +#include +#include + +namespace bluetooth { + +static_assert(sizeof(Uuid) == 16, "Uuid must be 16 bytes long!"); + +using UUID128Bit = Uuid::UUID128Bit; + +const Uuid Uuid::kEmpty = Uuid::From128BitBE(UUID128Bit{{0x00}}); + +namespace { +constexpr Uuid kBase = Uuid::From128BitBE( + UUID128Bit{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, + 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb}}); +} // namespace + +size_t Uuid::GetShortestRepresentationSize() const { + if (memcmp(uu.data() + kNumBytes32, kBase.uu.data() + kNumBytes32, + kNumBytes128 - kNumBytes32) != 0) { + return kNumBytes128; + } + + if (uu[0] == 0 && uu[1] == 0) return kNumBytes16; + + return kNumBytes32; +} + +bool Uuid::Is16Bit() const { + return GetShortestRepresentationSize() == kNumBytes16; +} + +uint16_t Uuid::As16Bit() const { return (((uint16_t)uu[2]) << 8) + uu[3]; } + +uint32_t Uuid::As32Bit() const { + return (((uint32_t)uu[0]) << 24) + (((uint32_t)uu[1]) << 16) + + (((uint32_t)uu[2]) << 8) + uu[3]; +} + +Uuid Uuid::FromString(const std::string& uuid, bool* is_valid) { + if (is_valid) *is_valid = false; + Uuid ret = kBase; + + if (uuid.empty()) return ret; + + uint8_t* p = ret.uu.data(); + if (uuid.size() == kString128BitLen) { + if (uuid[8] != '-' || uuid[13] != '-' || uuid[18] != '-' || + uuid[23] != '-') { + return ret; + } + + int c; + int rc = + sscanf(uuid.c_str(), + "%02hhx%02hhx%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx" + "-%02hhx%02hhx-%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%n", + &p[0], &p[1], &p[2], &p[3], &p[4], &p[5], &p[6], &p[7], &p[8], + &p[9], &p[10], &p[11], &p[12], &p[13], &p[14], &p[15], &c); + if (rc != 16) return ret; + if (c != kString128BitLen) return ret; + + if (is_valid) *is_valid = true; + } else if (uuid.size() == 8) { + int c; + int rc = sscanf(uuid.c_str(), "%02hhx%02hhx%02hhx%02hhx%n", &p[0], &p[1], + &p[2], &p[3], &c); + if (rc != 4) return ret; + if (c != 8) return ret; + + if (is_valid) *is_valid = true; + } else if (uuid.size() == 4) { + int c; + int rc = sscanf(uuid.c_str(), "%02hhx%02hhx%n", &p[2], &p[3], &c); + if (rc != 2) return ret; + if (c != 4) return ret; + + if (is_valid) *is_valid = true; + } + + return ret; +} + +Uuid Uuid::From16Bit(uint16_t uuid16) { + Uuid u = kBase; + + u.uu[2] = (uint8_t)((0xFF00 & uuid16) >> 8); + u.uu[3] = (uint8_t)(0x00FF & uuid16); + return u; +} + +Uuid Uuid::From32Bit(uint32_t uuid32) { + Uuid u = kBase; + + u.uu[0] = (uint8_t)((0xFF000000 & uuid32) >> 24); + u.uu[1] = (uint8_t)((0x00FF0000 & uuid32) >> 16); + u.uu[2] = (uint8_t)((0x0000FF00 & uuid32) >> 8); + u.uu[3] = (uint8_t)(0x000000FF & uuid32); + return u; +} + +Uuid Uuid::From128BitBE(const uint8_t* uuid) { + UUID128Bit tmp; + memcpy(tmp.data(), uuid, kNumBytes128); + return From128BitBE(tmp); +} + +Uuid Uuid::From128BitLE(const UUID128Bit& uuid) { + Uuid u; + std::reverse_copy(uuid.data(), uuid.data() + kNumBytes128, u.uu.begin()); + return u; +} + +Uuid Uuid::From128BitLE(const uint8_t* uuid) { + UUID128Bit tmp; + memcpy(tmp.data(), uuid, kNumBytes128); + return From128BitLE(tmp); +} + +const UUID128Bit Uuid::To128BitLE() const { + UUID128Bit le; + std::reverse_copy(uu.data(), uu.data() + kNumBytes128, le.begin()); + return le; +} + +const UUID128Bit& Uuid::To128BitBE() const { return uu; } + +Uuid Uuid::GetRandom() { + Uuid uuid; + base::RandBytes(uuid.uu.data(), uuid.uu.size()); + return uuid; +} + +bool Uuid::IsEmpty() const { return *this == kEmpty; } + +void Uuid::UpdateUuid(const Uuid& uuid) { + uu = uuid.uu; +} + +bool Uuid::operator<(const Uuid& rhs) const { + return std::lexicographical_compare(uu.begin(), uu.end(), rhs.uu.begin(), + rhs.uu.end()); +} + +bool Uuid::operator==(const Uuid& rhs) const { return uu == rhs.uu; } + +bool Uuid::operator!=(const Uuid& rhs) const { return uu != rhs.uu; } + +std::string Uuid::ToString() const { + return base::StringPrintf( + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uu[0], uu[1], uu[2], uu[3], uu[4], uu[5], uu[6], uu[7], uu[8], uu[9], + uu[10], uu[11], uu[12], uu[13], uu[14], uu[15]); +} +} // namespace bluetooth diff --git a/le_audio/certification_tool/types/bluetooth/uuid.h b/le_audio/certification_tool/types/bluetooth/uuid.h new file mode 100644 index 00000000000..0fe5b5bd1e4 --- /dev/null +++ b/le_audio/certification_tool/types/bluetooth/uuid.h @@ -0,0 +1,143 @@ +/****************************************************************************** + * + * Copyright (C) 2017 The Android Open Source Project + * + * 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. + * + ******************************************************************************/ + +#pragma once + +#include +#include +#include + +namespace bluetooth { + +// This class is representing Bluetooth UUIDs across whole stack. +// Here are some general endianness rules: +// 1. UUID is internally kept as as Big Endian. +// 2. Bytes representing UUID coming from upper layers, Java or Binder, are Big +// Endian. +// 3. Bytes representing UUID coming from lower layer, HCI packets, are Little +// Endian. +// 4. UUID in storage is always string. +class Uuid final { + public: + static constexpr size_t kNumBytes128 = 16; + static constexpr size_t kNumBytes32 = 4; + static constexpr size_t kNumBytes16 = 2; + + static constexpr size_t kString128BitLen = 36; + + static const Uuid kEmpty; // 00000000-0000-0000-0000-000000000000 + + using UUID128Bit = std::array; + + Uuid() = default; + + // Creates and returns a random 128-bit UUID. + static Uuid GetRandom(); + + // Returns the shortest possible representation of this UUID in bytes. Either + // kNumBytes16, kNumBytes32, or kNumBytes128 + size_t GetShortestRepresentationSize() const; + + // Returns true if this UUID can be represented as 16 bit. + bool Is16Bit() const; + + // Returns 16 bit Little Endian representation of this UUID. Use + // GetShortestRepresentationSize() or Is16Bit() before using this method. + uint16_t As16Bit() const; + + // Returns 32 bit Little Endian representation of this UUID. Use + // GetShortestRepresentationSize() before using this method. + uint32_t As32Bit() const; + + // Converts string representing 128, 32, or 16 bit UUID in + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, xxxxxxxx, or xxxx format to UUID. If + // set, optional is_valid parameter will be set to true if conversion is + // successfull, false otherwise. + static Uuid FromString(const std::string& uuid, bool* is_valid = nullptr); + + // Converts 16bit Little Endian representation of UUID to UUID + static Uuid From16Bit(uint16_t uuid16bit); + + // Converts 32bit Little Endian representation of UUID to UUID + static Uuid From32Bit(uint32_t uuid32bit); + + // Converts 128 bit Big Endian array representing UUID to UUID. + static constexpr Uuid From128BitBE(const UUID128Bit& uuid) { + Uuid u(uuid); + return u; + } + + // Converts 128 bit Big Endian array representing UUID to UUID. |uuid| points + // to beginning of array. + static Uuid From128BitBE(const uint8_t* uuid); + + // Converts 128 bit Little Endian array representing UUID to UUID. + static Uuid From128BitLE(const UUID128Bit& uuid); + + // Converts 128 bit Little Endian array representing UUID to UUID. |uuid| + // points to beginning of array. + static Uuid From128BitLE(const uint8_t* uuid); + + // Returns 128 bit Little Endian representation of this UUID + const UUID128Bit To128BitLE() const; + + // Returns 128 bit Big Endian representation of this UUID + const UUID128Bit& To128BitBE() const; + + // Returns string representing this UUID in + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx format, lowercase. + std::string ToString() const; + + // Returns true if this UUID is equal to kEmpty + bool IsEmpty() const; + + // Update UUID with new value + void UpdateUuid(const Uuid& uuid); + + bool operator<(const Uuid& rhs) const; + bool operator==(const Uuid& rhs) const; + bool operator!=(const Uuid& rhs) const; + + private: + constexpr Uuid(const UUID128Bit& val) : uu{val} {}; + + // Network-byte-ordered ID (Big Endian). + UUID128Bit uu; +}; +} // namespace bluetooth + +inline std::ostream& operator<<(std::ostream& os, const bluetooth::Uuid& a) { + os << a.ToString(); + return os; +} + +// Custom std::hash specialization so that bluetooth::UUID can be used as a key +// in std::unordered_map. +namespace std { + +template <> +struct hash { + std::size_t operator()(const bluetooth::Uuid& key) const { + const auto& uuid_bytes = key.To128BitBE(); + std::hash hash_fn; + return hash_fn(std::string(reinterpret_cast(uuid_bytes.data()), + uuid_bytes.size())); + } +}; + +} // namespace std diff --git a/le_audio/certification_tool/types/class_of_device.cc b/le_audio/certification_tool/types/class_of_device.cc new file mode 100644 index 00000000000..775a412a350 --- /dev/null +++ b/le_audio/certification_tool/types/class_of_device.cc @@ -0,0 +1,78 @@ +/****************************************************************************** + * + * Copyright 2018 The Android Open Source Project + * + * 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. + * + ******************************************************************************/ + +#include "class_of_device.h" + +#include +#include +#include +#include +#include + +static_assert(sizeof(ClassOfDevice) == ClassOfDevice::kLength, + "ClassOfDevice must be 3 bytes long!"); + +ClassOfDevice::ClassOfDevice(const uint8_t (&class_of_device)[kLength]) { + std::copy(class_of_device, class_of_device + kLength, cod); +}; + +std::string ClassOfDevice::ToString() const { + return base::StringPrintf("%03x-%01x-%02x", + (static_cast(cod[2]) << 4) | cod[1] >> 4, + cod[1] & 0x0f, cod[0]); +} + +bool ClassOfDevice::FromString(const std::string& from, ClassOfDevice& to) { + ClassOfDevice new_cod; + if (from.length() != 8) return false; + + std::vector byte_tokens = + base::SplitString(from, "-", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + + if (byte_tokens.size() != 3) return false; + if (byte_tokens[0].length() != 3) return false; + if (byte_tokens[1].length() != 1) return false; + if (byte_tokens[2].length() != 2) return false; + + uint16_t values[3]; + + for (size_t i = 0; i < kLength; i++) { + const auto& token = byte_tokens[i]; + + char* temp = nullptr; + values[i] = strtol(token.c_str(), &temp, 16); + if (*temp != '\0') return false; + } + + new_cod.cod[0] = values[2]; + new_cod.cod[1] = values[1] | ((values[0] & 0xf) << 4); + new_cod.cod[2] = values[0] >> 4; + + to = new_cod; + return true; +} + +size_t ClassOfDevice::FromOctets(const uint8_t* from) { + std::copy(from, from + kLength, cod); + return kLength; +}; + +bool ClassOfDevice::IsValid(const std::string& cod) { + ClassOfDevice tmp; + return ClassOfDevice::FromString(cod, tmp); +} diff --git a/le_audio/certification_tool/types/class_of_device.h b/le_audio/certification_tool/types/class_of_device.h new file mode 100644 index 00000000000..b0fffc88532 --- /dev/null +++ b/le_audio/certification_tool/types/class_of_device.h @@ -0,0 +1,63 @@ +/****************************************************************************** + * + * Copyright 2018 The Android Open Source Project + * + * 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. + * + ******************************************************************************/ + +#pragma once + +#include +#include + +namespace bluetooth { +namespace types { + +/** Bluetooth Class of Device */ +class ClassOfDevice final { + public: + static constexpr unsigned int kLength = 3; + + uint8_t cod[kLength]; + + ClassOfDevice() = default; + ClassOfDevice(const uint8_t (&class_of_device)[kLength]); + + bool operator==(const ClassOfDevice& rhs) const { + return (std::memcmp(cod, rhs.cod, sizeof(cod)) == 0); + } + + std::string ToString() const; + + // Converts |string| to ClassOfDevice and places it in |to|. If |from| does + // not represent a Class of Device, |to| is not modified and this function + // returns false. Otherwise, it returns true. + static bool FromString(const std::string& from, ClassOfDevice& to); + + // Copies |from| raw Class of Device octets to the local object. + // Returns the number of copied octets (always ClassOfDevice::kLength) + size_t FromOctets(const uint8_t* from); + + static bool IsValid(const std::string& class_of_device); +}; + +inline std::ostream& operator<<(std::ostream& os, const ClassOfDevice& c) { + os << c.ToString(); + return os; +} + +} // namespace types +} // namespace bluetooth + +using ::bluetooth::types::ClassOfDevice; // TODO, remove diff --git a/le_audio/certification_tool/types/raw_address.cc b/le_audio/certification_tool/types/raw_address.cc new file mode 100644 index 00000000000..8369f5f36af --- /dev/null +++ b/le_audio/certification_tool/types/raw_address.cc @@ -0,0 +1,73 @@ +/******************************************************************************* + * + * Copyright 2017 The Android Open Source Project + * + * 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. + * + ******************************************************************************/ + +#include "raw_address.h" + +#include +#include +#include +#include +#include + +static_assert(sizeof(RawAddress) == 6, "RawAddress must be 6 bytes long!"); + +const RawAddress RawAddress::kAny{{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; +const RawAddress RawAddress::kEmpty{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; + +RawAddress::RawAddress(const uint8_t (&addr)[6]) { + std::copy(addr, addr + kLength, address); +} + +std::string RawAddress::ToString() const { + return base::StringPrintf("%02x:%02x:%02x:%02x:%02x:%02x", address[0], + address[1], address[2], address[3], address[4], + address[5]); +} + +bool RawAddress::FromString(const std::string& from, RawAddress& to) { + RawAddress new_addr; + if (from.length() != 17) return false; + + std::vector byte_tokens = + base::SplitString(from, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + + if (byte_tokens.size() != 6) return false; + + for (int i = 0; i < 6; i++) { + const auto& token = byte_tokens[i]; + + if (token.length() != 2) return false; + + char* temp = nullptr; + new_addr.address[i] = strtol(token.c_str(), &temp, 16); + if (*temp != '\0') return false; + } + + to = new_addr; + return true; +} + +size_t RawAddress::FromOctets(const uint8_t* from) { + std::copy(from, from + kLength, address); + return kLength; +}; + +bool RawAddress::IsValidAddress(const std::string& address) { + RawAddress tmp; + return RawAddress::FromString(address, tmp); +} diff --git a/le_audio/certification_tool/types/raw_address.h b/le_audio/certification_tool/types/raw_address.h new file mode 100644 index 00000000000..ca750181b6c --- /dev/null +++ b/le_audio/certification_tool/types/raw_address.h @@ -0,0 +1,79 @@ +/****************************************************************************** + * + * Copyright 2017 The Android Open Source Project + * + * 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. + * + ******************************************************************************/ + +#pragma once + +#include +#include +#include + +/** Bluetooth Address */ +class RawAddress final { + public: + static constexpr unsigned int kLength = 6; + + uint8_t address[kLength]; + + RawAddress() = default; + RawAddress(const uint8_t (&addr)[kLength]); + + bool operator<(const RawAddress& rhs) const { + return (std::memcmp(address, rhs.address, sizeof(address)) < 0); + } + bool operator==(const RawAddress& rhs) const { + return (std::memcmp(address, rhs.address, sizeof(address)) == 0); + } + bool operator>(const RawAddress& rhs) const { return (rhs < *this); } + bool operator<=(const RawAddress& rhs) const { return !(*this > rhs); } + bool operator>=(const RawAddress& rhs) const { return !(*this < rhs); } + bool operator!=(const RawAddress& rhs) const { return !(*this == rhs); } + + bool IsEmpty() const { return *this == kEmpty; } + + std::string ToString() const; + + // Converts |string| to RawAddress and places it in |to|. If |from| does + // not represent a Bluetooth address, |to| is not modified and this function + // returns false. Otherwise, it returns true. + static bool FromString(const std::string& from, RawAddress& to); + + // Copies |from| raw Bluetooth address octets to the local object. + // Returns the number of copied octets - should be always RawAddress::kLength + size_t FromOctets(const uint8_t* from); + + static bool IsValidAddress(const std::string& address); + + static const RawAddress kEmpty; // 00:00:00:00:00:00 + static const RawAddress kAny; // FF:FF:FF:FF:FF:FF +}; + +inline std::ostream& operator<<(std::ostream& os, const RawAddress& a) { + os << a.ToString(); + return os; +} + +template <> +struct std::hash { + std::size_t operator()(const RawAddress& val) const { + static_assert(sizeof(uint64_t) >= RawAddress::kLength); + uint64_t int_addr = 0; + memcpy(reinterpret_cast(&int_addr), val.address, + RawAddress::kLength); + return std::hash{}(int_addr); + } +}; diff --git a/le_audio/frameworks/base/Android.bp b/le_audio/frameworks/base/Android.bp new file mode 100644 index 00000000000..6fac52db28a --- /dev/null +++ b/le_audio/frameworks/base/Android.bp @@ -0,0 +1,4 @@ +filegroup { + name: "framework-bluetooth-adva-srcs", + srcs: ["core/**/*.java",], +} diff --git a/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastAudioScanAssistCallback.java b/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastAudioScanAssistCallback.java new file mode 100644 index 00000000000..821a67d3211 --- /dev/null +++ b/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastAudioScanAssistCallback.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ + +package android.bluetooth; + +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Retention; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.le.ScanResult; +import android.annotation.IntDef; +import java.util.Map; +import java.lang.String; +import java.lang.Integer; +import java.util.List; + + + +/** + * Bluetooth LE Broadcast Scan Assistance related callbacks, used to deliver result of + * Broadcast Assist operations performed using {@link BleBroadcastAudioScanAssistManager} + * + * @hide + * @see BleBroadcastAudioScanAssistManager + */ +public abstract class BleBroadcastAudioScanAssistCallback { + + /** @hide */ + @IntDef(prefix = "BASS_STATUS_", value = { + BASS_STATUS_SUCCESS, + BASS_STATUS_FAILURE, + BASS_STATUS_FATAL, + BASS_STATUS_TXN_TIMEOUT, + BASS_STATUS_INVALID_SOURCE_ID, + BASS_STATUS_COLOCATED_SRC_UNAVAILABLE, + BASS_STATUS_INVALID_SOURCE_SELECTED, + BASS_STATUS_SOURCE_UNAVAILABLE, + BASS_STATUS_DUPLICATE_ADDITION, + }) + + @Retention(RetentionPolicy.SOURCE) + public @interface Bass_Status {} + + public static final int BASS_STATUS_SUCCESS = 0x00; + public static final int BASS_STATUS_FAILURE = 0x01; + public static final int BASS_STATUS_FATAL = 0x02; + public static final int BASS_STATUS_TXN_TIMEOUT = 0x03; + + public static final int BASS_STATUS_INVALID_SOURCE_ID = 0x04; + public static final int BASS_STATUS_COLOCATED_SRC_UNAVAILABLE = 0x05; + public static final int BASS_STATUS_INVALID_SOURCE_SELECTED = 0x06; + public static final int BASS_STATUS_SOURCE_UNAVAILABLE = 0x07; + public static final int BASS_STATUS_DUPLICATE_ADDITION = 0x08; + public static final int BASS_STATUS_NO_EMPTY_SLOT = 0x09; + public static final int BASS_STATUS_INVALID_GROUP_OP = 0x10; + + /** + * Callback when BLE broadcast audio source found. + * result of {@link BleBroadcastAudioScanAssistManager#searchforLeAudioBroadcasters} will be + * delivered through this callback + * + * @param scanres {@link ScanResult} object of the scanned result + */ + public void onBleBroadcastSourceFound(ScanResult scanres) { + }; + + + /** + * Callback when BLE broadcast audio source found. + * result of {@link BleBroadcastAudioScanAssistManager#searchforLeAudioBroadcasters} will be + * delivered through this callback + * + * @param status Status of the Broadcast source selection. + * @param broadcastSourceChannels {@link BleBroadcastSourceChannel} List + * containing avaiable broadcast source channels that are being broadcasted from the selected + * broadcast source + * + */ + public void onBleBroadcastSourceSelected(BluetoothDevice device, + @Bass_Status int status, + List broadcastSourceChannels) { + }; + + /** + * Callback when BLE broadcast audio source is been successfully added to the remote Scan delegator. + * result of {@link BleBroadcastAudioScanAssistManager#addBroadcastSource} will be + * delivered through this callback + * + * This callback is an acknowledgement confirming the source information added + * to the Scan delegator. Actual updated source Information values of resulting Broadcast Source Information + * will be notified using {@link BleBroadcastAudioScanAssistManager#ACTION_BROADCAST_RECEIVER_STATE} intent + * + * @param device remote scan delegator for which Source is been added. + * @param srcId source Id of the Broadcast source information added + * @param status true on succesful addition of source Information, false otherwise. + * + */ + public void onBleBroadcastAudioSourceAdded(BluetoothDevice device, + byte srcId, + @Bass_Status int status) { + }; + + /** + * Callback when BLE broadcast audio source Information is been updated to the remote Scan delegator. + * result of {@link BleBroadcastAudioScanAssistManager#updateBroadcastSource} will be + * delivered through this callback + * + * This callback is an acknowledgement confirming the source information update request is succesfully + * written on the Scan delegator. Actual updated source Information values of resulting Broadcast Source Information + * will be notified using {@link BleBroadcastAudioScanAssistManager#ACTION_BROADCAST_RECEIVER_STATE} intent + * + * @param device remote scan delegator for which Source is been updated. + * @param srcId source Id of the Broadcast source information updated. + * @param status true on succesful updating of source Information, false otherwise. + * + */ + public void onBleBroadcastAudioSourceUpdated(BluetoothDevice device, + byte srcId, + @Bass_Status int status) { + }; + + /** + * Callback when BLE broadcast audio source Information is updated with broadcast PIN code to the remote Scan delegator. + * result of {@link BleBroadcastAudioScanAssistManager#setBroadcastCode} will be + * delivered through this callback + * + * This callback is an acknowledgement confirming the Broadcast PIN update request is succesfully + * written to the Scan delegator. Actual updated source Information values of resulting Broadcast Source Information + * will be notified using {@link BleBroadcastAudioScanAssistManager#ACTION_BROADCAST_RECEIVER_STATE} intent. + * Encryption status from the {@link BleBroadcastAudioScanAssistManager#ACTION_BROADCAST_RECEIVER_STATE} will + * confirm the succesfull Broadcast PIN code and resulting decryption of the Broadcast data at the reciver side. + * + * @param device remote scan delegator for which Source is been updated. + * @param srcId source Id of the Broadcast PIN updated. + * @param status true on succesful updating of source Information, false otherwise. + * + */ + public void onBleBroadcastPinUpdated(BluetoothDevice rcvr, + byte srcId, + @Bass_Status int status) { + }; + + /** + * Callback when BLE broadcast audio source Information is removed from the remote Scan delegator. + * result of {@link BleBroadcastAudioScanAssistManager#removeBroadcastSource} will be + * delivered through this callback + * + * This callback is an acknowledgement confirming the Broadcast source infor removal request is succesfully + * written to the Scan delegator. Actual removal of source Information values of resulting Broadcast Source Information + * will be notified using {@link BleBroadcastAudioScanAssistManager#ACTION_BROADCAST_RECEIVER_STATE} intent. + * Deletion of source Information will result is setting all the source information attributes to ZERO other than + * source Id + * + * @param device remote scan delegator for which Source is removed. + * @param srcId source Id of the Broadcast source information removed. + * @param status true on succesful updating of source Information, false otherwise. + * + */ + public void onBleBroadcastAudioSourceRemoved(BluetoothDevice rcvr, + byte srcId, + @Bass_Status int status) { + }; +} diff --git a/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastAudioScanAssistManager.java b/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastAudioScanAssistManager.java new file mode 100644 index 00000000000..eb029d07d00 --- /dev/null +++ b/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastAudioScanAssistManager.java @@ -0,0 +1,621 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ + +package android.bluetooth; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SystemApi; +import android.annotation.IntDef; +import android.annotation.SdkConstant.SdkConstantType; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Retention; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.IBluetoothGatt; +import android.bluetooth.IBluetoothManager; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothAdapter.LeScanCallback; +import android.os.Binder; +import android.os.IBinder; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import java.io.InvalidClassException; +import android.os.DeadObjectException; +import android.util.Log; +import android.content.Context; +import java.util.UUID; +import android.os.ParcelUuid; + +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.ArrayList; +import java.util.List; +import java.util.HashMap; +import java.util.Objects; + +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.BleBroadcastAudioScanAssistManager; + +import android.os.SystemProperties; + +/** + * This class provides methods to perform Broadcast Assistance related + * operations. + *

+ * Use {@link BleBroadcastAudioScanAssistManager()} to get an + * instance of {@link BleBroadcastAudioScanAssistManager}. + *

+ * Note: Most of the methods here require + * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * @hide + */ +public final class BleBroadcastAudioScanAssistManager { + + private static final String TAG = "BleBroadcastAudioScanAssistManager"; + private static final boolean DBG = true; + private static final boolean VDBG = true; + + /** @hide */ + @IntDef(prefix = "SYNC_", value = { + SYNC_METADATA, + SYNC_AUDIO, + SYNC_METADATA_AUDIO + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BroadcastAssistSyncState {} + /** + * Input to {@link BleBroadcastAudioScanAssistManager#addBroadcastSource} method + * where Application wants to synchronize only to Metadata (i.e. Only Periodic advs) and not to + * Broadcsat audio stream (i.e. BIS )from broadcast source + */ + public static final int SYNC_METADATA = 0; + /** + * Input to {@link BleBroadcastAudioScanAssistManager#addBroadcastSource} method + * where Application wants to synchronize only to Broadcast Audio stream (i.e. BIS) and not to + Metadata (i.e. Periodic advs )from broadcast source + */ + public static final int SYNC_AUDIO = 1; + /** + * Input to {@link BleBroadcastAudioScanAssistManager#addBroadcastSource} method + * where Application wants to synchronize to both Broadcast Audio stream (i.e. BIS) and also to + * Metadata (i.e. Periodic advs )from broadcast source + */ + public static final int SYNC_METADATA_AUDIO = 2; + + private BluetoothAdapter mBluetoothAdapter; + BleBroadcastAudioScanAssistCallback mAppCallback; + BluetoothDevice mBluetoothDevice; + int mSyncState = SYNC_METADATA; + BluetoothSyncHelper mBluetoothSyncHelper = null; + BleBroadcastSourceInfo mBroadcastAudioSourceInfo = null; + private byte INVALID_SOURCE_ID = -1; + + /** + * Intent used to broadcast the "Broadcast receiver State" information of a Scan delegator device. + * Whenever there is a change in Broadcast source Information stored at Scan delegator device + * this Itent will be delivered to Application layer + * + * {@link #BluetoothSyncHelper} profile need to be connected to the Scan delegator device + * to get these notifications + * + *

This intent will have two extra: + *

    + *
  • {@link BluetoothDevice#EXTRA_DEVICE} - The remote device for which broadcast reciver + * state information is broadcasted. It can + * be null if no device is active.
  • + *
+ *
    + *
  • {@link BleBroadcastSourceInfo#EXTRA_SOURCE_INFO} - The BleBroadcastSourceInfo Object + * having information Broadcast receiver state
  • + *
+ *
    + *
  • {@link BleBroadcastSourceInfo#EXTRA_SOURCE_INFO_INDEX} - Index of the BleBroadcastSourceInfo + * object broadcasted
  • + *
+ *
    + *
  • {@link BleBroadcastSourceInfo#EXTRA_MAX_NUM_SOURCE_INFOS} - Maximum number of source Informations + * that this Broadcast receiver can hold
  • + *
+ * + *

Requires {@link android.Manifest.permission#BLUETOOTH} permission to + * receive. + * + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_BROADCAST_SOURCE_INFO = + "android.bluetooth.BroadcastAudioSAManager.action.BROADCAST_SOURCE_INFO"; + + + // These callbacks run on the main thread. + private final class BassclientServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + log(TAG, "BassService connected"); + onBluetoothSyncHelperStateChanged(true, proxy); + + } + + public void onServiceDisconnected(int profile) { + log(TAG, "BassService disconnected"); + onBluetoothSyncHelperStateChanged(false, null); + } + } + + private void onBluetoothSyncHelperStateChanged(boolean on, BluetoothProfile proxy) { + if (on) { + mBluetoothSyncHelper = (BluetoothSyncHelper) proxy; + mBluetoothSyncHelper.registerAppCallback(mBluetoothDevice, mAppCallback); + this.notifyAll(); + } else { + mBluetoothSyncHelper = null; + } + } + + /*package*/BleBroadcastAudioScanAssistManager(BluetoothSyncHelper scanOffloader, BluetoothDevice device, + BleBroadcastAudioScanAssistCallback callback + ) { + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + + mAppCallback = callback; + mBluetoothDevice = device; + mBluetoothSyncHelper = scanOffloader; + } + + + /*finalize method to cleanup*/ + protected void finalize() { + log(TAG, "finalize()"); + if (mBluetoothSyncHelper != null) { + mBluetoothSyncHelper.unregisterAppCallback(mBluetoothDevice, mAppCallback); + } + } + + /** + * Search for Le Audio Broadcasters on behalf of the Scan delegator with which this + * {@ BleBroadcastAudioScanAssistManager} is instantiated + * + * search results will be delivered to application using + * {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastSourceFound} + * + * @return returns true if It is successfully initiated the Search for Audio broadcasters, + * false otherwise + * @hide + */ + public boolean searchforLeAudioBroadcasters () { + log(TAG, "searchforLeAudioBroadcasters: "); + if (mBluetoothSyncHelper != null) { + return mBluetoothSyncHelper.searchforLeAudioBroadcasters(mBluetoothDevice); + } else { + Log.e(TAG, "searchforLeAudioBroadcasters: mBluetoothSyncHelper is null"); + } + return false; + } + /** + * Stops an ongoing Bluetooth LE Search for Audio Broadcasters. + * + * @return returns true if It is successfully initiated the Stopped the Search for Audio broadcasters + * false otherwise + * + *@hide + */ + @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + public boolean stopSearchforLeAudioBroadcasters() { + log(TAG, "stopSearchforLeAudioBroadcasters()"); + if (mBluetoothSyncHelper != null) { + return mBluetoothSyncHelper.stopSearchforLeAudioBroadcasters(mBluetoothDevice); + } else { + Log.e(TAG, "stopSearchforLeAudioBroadcasters: mBluetoothSyncHelper is null"); + } + return false; + + } + + /* Internal helper function to convert user input sync state to required internal + * format + */ + private int convertMetadataSyncState(int syncState) { + if (syncState == SYNC_METADATA_AUDIO || syncState == SYNC_METADATA) { + return BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC; + } + return BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IDLE; + } + + /* Internal helper function to convert user input sync state to required internal + * format + */ + private int convertAudioDataSyncState(int syncState) { + if (syncState == SYNC_METADATA_AUDIO || syncState == SYNC_AUDIO) { + return BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED; + } else { + Log.e(TAG, "searchforLeAudioBroadcasters: mBluetoothSyncHelper is null"); + } + return BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED; + } + + /** + * Selects broadcast source for the Scan delegator. This internally performs Periodic + * synchronization to the given Broadcast source device, upon acquision of Synchronization information, + * It will be notified with avaiable Broadcast source channels that can be synchronized in the remote + * device. + * Application should select set of Broadcast channels that need to be synchronized and follow up + * with a call to {@link #addBroadcastSource} operation + * + * Result of selction of Broadcast source will be delivered through + * {@link BleBroadcastAudioScanAssistCallback#OnBroadcastAudioSourceSelected} + * + * If this operation need to be performed over all the members of coordinated set members, isGroupOp + * will be set to true. Select broadcast source operation will be performed on behalf of + * all the Coordinated set devices + * + * + * @param ScanResult {@link #ScanResult} of the Broadcasting source, + * this is the result obtained from the {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastSourceFound} + * @param isGroupOp set to true If Application wants to perform this operation for the whole + * coordinated set members + * + * @return returns true if It is successfully initiated select Broadcast source operation + * false otherwise + * @hide + */ + public boolean selectBroadcastSource(ScanResult scanRes, boolean isGroupOp) { + if (scanRes == null) { + Log.e(TAG, "selectBroadcastSource: Invalid scan res"); + return false; + } + log(TAG, "selectBroadcastSource: " + scanRes); + if (mBluetoothSyncHelper != null) { + return mBluetoothSyncHelper.selectBroadcastSource(mBluetoothDevice, scanRes, isGroupOp); + } else { + Log.e(TAG, "selectBroadcastSource: mBluetoothSyncHelper is null"); + } + return false; + } + + + private boolean isValidBroadcastSourceInfo(BleBroadcastSourceInfo srcInfo) { + boolean ret = true; + List currentSourceInfos = + mBluetoothSyncHelper.getAllBroadcastSourceInformation(mBluetoothDevice); + if (currentSourceInfos == null) { + Log.e(TAG, "no source info details for remote"); + ret = false; + } else { + for (int i=0; i currentSourceInfos = + mBluetoothSyncHelper.getAllBroadcastSourceInformation(mBluetoothDevice); + if (currentSourceInfos == null) { + retVal = false; + } else { + for (int i=0; i selectedBISIndicies) { + if (selectedBISIndicies == null) { + log(TAG, "printSelectedIndicies : no selected indicies"); + return; + } + for (int i=0; i selectedBroadcastChannels, + boolean isGroupOp) { + if (mBluetoothSyncHelper == null) { + log(TAG, "addBroadcastSource: no BluetoothSyncHelper handle"); + return false; + } + + if (syncState != SYNC_METADATA && + syncState != SYNC_METADATA_AUDIO) { + log(TAG, "addBroadcastSource: Invalid syncState" + syncState); + return false; + } + printSelectedIndicies(selectedBroadcastChannels); + int metadataSyncState = -1; + int audioSyncState = -1; + mSyncState = syncState; + metadataSyncState = convertMetadataSyncState (mSyncState); + audioSyncState = convertAudioDataSyncState(mSyncState); + if (mBroadcastAudioSourceInfo == null) { + //all of these will be overriden at service layer later + mBroadcastAudioSourceInfo = new BleBroadcastSourceInfo( + audioSource, + (byte)0xBB, /*advSid*/ + BleBroadcastSourceInfo.BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC, + metadataSyncState, + audioSyncState, + selectedBroadcastChannels); + if (mBroadcastAudioSourceInfo == null) { + Log.e(TAG, "addBroadcastSource: mBroadcastAudioSourceInfo instantiated failure"); + return false; + } + } + if(isValidBroadcastSourceInfo(mBroadcastAudioSourceInfo)) { + mBluetoothSyncHelper.addBroadcastSource(mBluetoothDevice, + mBroadcastAudioSourceInfo, + isGroupOp + ); + } else { + log(TAG, "Similar source information already exists"); + return false; + } + return true; + } + /** + * Updates a broadcast source information in the Scan delegator. + * It will be written on to the Scan delegator's Characteristics + * + * Result of updating of Broadcast source to the scan delegator will be delivered through + * {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastAudioSourceUpdated} + * + * However Successful updating of Broadcast source information will be indicated through Broadcast reciver state information + * update intent through {@link #ACTION_BROADCAST_RECEIVER_STATE} intent + * + * If this operation need to be performed over all the members of coordinated set members, isGroupOp + * will be set to true. Update broadcast source operation will be performed on behalf of + * all the Coordinated set devices + * + * Same Broadcast source Information change will be written on to all the members of Coordinated set and + * PAST will be performed based on the request from remote. + * + * In case of Group Operation, If there are no matching source Information present in any of coordinated set members, + * Update Broadcast source opeation will be failed and result will notified through + * {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastAudioSourceUpdated} + * + * @param sourceId sourceId of the Broadcast Source information which need to be updated + * @param syncState can be one of {@link #SYNC_METADATA}, + * {@link #SYNC_AUDIO}, {@link #SYNC_METADATA_AUDIO} + * + * @param selectedBroadcastChannels is a List of Broadcast channels that need to be synchronized with the given broadcast audio source + * from Avaialble Broadcast indicies. + * Avaiable broadcast indicies are notified application using {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastSourceSelected} + * BroadcastSourceChannel.mStatus set to be TRUE or FALSE based on the need of synchronization. + * + * null value of selectedBroadcastChannels resulting in syncing to all avaialble Broadcast channels. + * check {@link BleurceChannel} for more information + * @param isGroupOp set to true If Application wants to perform this operation for the whole + * coordinated set members, False otherwise + * + * @return returns true if It is successfully initiated update Broadcast source information + * operation + * false otherwise + * @hide + */ + public boolean updateBroadcastSource (byte sourceId, int syncState, + List selectedBroadcastChannels, + boolean isGroupOp) { + if (mBluetoothSyncHelper == null) { + log(TAG, "updateBroadcastSource: no BluetoothSyncHelper handle"); + return false; + } + if (isValidSourceId(sourceId) == false) { + log(TAG, "updateBroadcastSource: Invalid source Id"); + return false; + } + int audioSyncState = -1; + int metadataSyncState = -1; + log(TAG, "updateBroadcastSource: sourceId" + sourceId + ", syncState:" + syncState); + + mSyncState = syncState; + metadataSyncState = convertMetadataSyncState (mSyncState); + audioSyncState = convertAudioDataSyncState(mSyncState); + + printSelectedIndicies(selectedBroadcastChannels); + + log(TAG, "updateBroadcastSource: audioSyncState:" + audioSyncState); + log(TAG, "updateBroadcastSource: metadataSyncState:" + metadataSyncState); + + BleBroadcastSourceInfo sourceInfo = new BleBroadcastSourceInfo(sourceId); + if (sourceInfo != null) { + sourceInfo.setMetadataSyncState(metadataSyncState); + sourceInfo.setAudioSyncState(audioSyncState); + sourceInfo.setSourceId(sourceId); + sourceInfo.setBroadcastChannelsSyncStatus(selectedBroadcastChannels); + } else { + Log.e(TAG, "updateBroadcastSource: sourceInfo not created"); + return false; + } + return mBluetoothSyncHelper.updateBroadcastSource(mBluetoothDevice, + sourceInfo, + isGroupOp); + } + /** + * Sets the Broadcast pin code to the Scan delegator so that It can decrypt + * the synchronized audio at the reciver side + * + * It will be written on to the Scan delegator's Characteristics. + * Result of Setting of Broadcast PIN code to the scan delegator will be delivered through + * {@link BleBroadcastAudioScanAssistCallback#onBroadcastPinUpdated} + * + * If this operation need to be performed over all the members of coordinated set members, isGroupOp + * will be set to true. set Broadcast PIN operation will be performed on all the Coordinated set devices + * + * Same Broadcast PIN code will be written on to all the members of Coordinated set and + * on the request from remote. + * + * In case of Group Operation, If there are no matching source Information(BD address, adv instance) + * present in any of coordinated set members, + * Set Broadcast PIN opeation will be failed and result will notified through + * {@link BleBroadcastAudioScanAssistCallback#onBroadcastPinUpdated} + * + * + * However, Successful updating of Broadcast PIN code will be indicated through Broadcast reciver state information + * update intent through {@link #ACTION_BROADCAST_RECEIVER_STATE} intent. + * + * @param sourceId sourceId of the Broadcast Source information which need to be updated + * @param broadcastCode is the String of maximum 16 characters in length + * @param isGroupOp set to true If Application wants to perform this operation for the whole + * coordinated set members, False otherwise + * + * @return returns true if It is successfully initiated set Broadcast code operation + * false otherwise + * @hide + */ + public boolean setBroadcastCode (byte sourceId, String broadcastCode, boolean isGroupOp) { + if (mBluetoothSyncHelper == null) { + log(TAG, "setBroadcastCode: no BluetoothSyncHelper handle"); + return false; + } + if (isValidSourceId(sourceId) == false) { + log(TAG, "setBroadcastCode: Invalid source Id"); + return false; + } + + log(TAG, "setBroadcastCode: " + "sourceId:" + + sourceId + "BroadcastCode:" + broadcastCode); + BleBroadcastSourceInfo sourceInfo = new BleBroadcastSourceInfo(sourceId); + if (sourceInfo != null) { + sourceInfo.setSourceId(sourceId); + sourceInfo.setBroadcastCode(broadcastCode); + } else { + Log.e(TAG, "setBroadcastCode: sourceInfo not created"); + return false; + } + return mBluetoothSyncHelper.setBroadcastCode(mBluetoothDevice, + sourceInfo, + isGroupOp); + } + /** + * Removes the Broadcast Source Information from the Scan delegator + * It will be written on to the "Scan delegators" Characteristics + * + * Result of removal of Broadcast source to the scan delegator will be delivered through + * {@link BleBroadcastAudioScanAssistCallback#OnBroadcastAudioSourRemoved} + * + * If this operation need to be performed over all the members of coordinated set members, isGroupOp + * will be set to true. remove broadcast operation will be performed on all the Coordinated set devices + * + * Remove Broadcast will be performed on to all the members of Coordinated set + * + * In case of Group Operation, If there are no matching source Information(BD address, adv instance) + * present in any of coordinated set members. + * + * Set Broadcast PIN opeation will be failed and result will notified through + * {@link BleBroadcastAudioScanAssistCallback#onBroadcastPinUpdated} + * Successful removal of Brocast source information will be indicated through + * Broadcast receiver state Information through + * {@link #ACTION_BROADCAST_RECEIVER_STATE} intent + * + * @param sourceId sourceId of the Broadcast Source information which need to be updated + * @param isGroupOp set to true If Application wants to perform this operation for the whole + * coordinated set members, False otherwise + * + * @return returns true if It is successfully initiated remove broadcast source operation + * false otherwise + * @hide + */ + public boolean removeBroadcastSource (byte sourceId, boolean isGroupOp) { + if (mBluetoothSyncHelper == null) { + log(TAG, "removeBroadcastSource: no BluetoothSyncHelper handle"); + return false; + } + if (isValidSourceId(sourceId) == false) { + log(TAG, "removeBroadcastSource: Invalid source Id"); + return false; + } + log(TAG, "removeBroadcastSource: sourceId" + sourceId); + + return mBluetoothSyncHelper.removeBroadcastSource(mBluetoothDevice, + sourceId, + isGroupOp); + } + /** + * Get all the Broadcast Source Information stored in remote Scan delegators + * + * @return returns the List of Broadcast Source Information {@link #BleBroadcastSourceInfo} stored in + * remote and its corresponding state or null in case if there are nothing + * + * @hide + */ + public List getAllBroadcastSourceInformation () { + if (mBluetoothSyncHelper == null) { + log(TAG, "GetNumberOfAcceptableBroadcastSources: no BluetoothSyncHelper handle"); + return null; + } + return mBluetoothSyncHelper.getAllBroadcastSourceInformation(mBluetoothDevice); + } + + private static void log(String TAG, String msg) { + BleBroadcastSourceInfo.BASS_Debug(TAG, msg); + } +} diff --git a/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastSourceChannel.java b/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastSourceChannel.java new file mode 100644 index 00000000000..a66790ccc54 --- /dev/null +++ b/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastSourceChannel.java @@ -0,0 +1,217 @@ + /* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ +package android.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.IBluetoothGatt; +import android.bluetooth.IBluetoothManager; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Retention; +import android.annotation.IntDef; +import android.compat.annotation.UnsupportedAppUsage; + +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; +import java.util.Objects; +import android.util.Log; +import java.util.List; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.Map; + +/** + * This class provides Interface to select the Broadcast source channels + * to be synchronized from the Broadcast source. these Broadcast source channels + * are mapped to the BIS indicies that given Broadcast source is broadcasting with. + * + *

This also acts as general data structure for updating the Broadcast + * source channel information + * + * This class is used to input the User provided data for below operations + * {@link BleBroadcastAudioScanAssistManager#addBroadcastSource}, + * {@link BleBroadcastAudioScanAssistManager#updateBroadcastSource} and + * + * mIndex : index is the Identifier for Broadcast channel + * mDescription: Description describing the type of Broadcast data being broadcasted + * mStatus: TRUE means broadcast source channel need to be synchronized + * FALSE means broadcast source channel need NOT be synchronized + * + * @hide + */ +public final class BleBroadcastSourceChannel implements Parcelable { + + private static final String TAG = "BleBroadcastSourceChannel"; + private int mIndex; + private String mDescription; + private boolean mStatus; + private int mSubGroupId; + private byte[] mMetadata; + + public BleBroadcastSourceChannel (int index, + String description, + boolean st, + int aSubGroupId, + byte[] aMetadata) { + mIndex = index; + mDescription = description; + mStatus = st; + mSubGroupId = aSubGroupId; + if (aMetadata != null && aMetadata.length != 0) { + mMetadata = new byte[aMetadata.length]; + System.arraycopy(aMetadata, 0, mMetadata, 0, aMetadata.length); + } + } + @Override + public boolean equals(Object o) { + if (o instanceof BleBroadcastSourceChannel) { + BleBroadcastSourceChannel other = (BleBroadcastSourceChannel) o; + return (other.mIndex == mIndex + && other.mDescription == mDescription + && other.mStatus == mStatus + ); + } + return false; + } + @Override + public int hashCode() { + return Objects.hash(mIndex, mDescription, mStatus); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return mDescription; + } + + /** + * Gets the Source Id of the BleBroadcastSourceChannel Object + * + * @return byte representing the Source Id of the Broadcast Source Info Object + * {@link #BROADCAST_ASSIST_INVALID_SOURCE_ID} in case if this field is not valid + * @hide + */ + public int getIndex () { + return mIndex; + } + + /** + * Gets the Broadcast source Device object from the BleBroadcastSourceChannel Object + * + * @return BluetoothDevice object for Broadcast source device + * @hide + */ + public String getDescription () { + return mDescription; + } + + /** + * Gets the status of given BleBroadcastSourceChannel + * + * @return true if selected, false otherwise + * @hide + * + * @deprecated + */ + + public boolean getStatus () { + return mStatus; + } + + /** + * Gets the address type of the Broadcast source advertisement for the BleBroadcastSourceChannel Object + * + * @return byte addressType, this can be one of {@link #BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC} OR {@link #BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC} + * @hide + * + * @deprecated + */ + + public byte[] getMetadata () { + return mMetadata; + } + + /** + * Gets the subgroup Id that broadcast Channel belongs + * Internal helper function + * + * @hide + * @deprecated + */ + public int getSubGroupId () { + return mSubGroupId; + } + + /** + * Sets the status of given BleBroadcastSourceChannel + * + * @return true if selected, false otherwise + * @hide + * + * @deprecated + */ + public void setStatus (boolean status) { + mStatus = status; + } + + + public static final @android.annotation.NonNull Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public BleBroadcastSourceChannel createFromParcel(Parcel in) { + + log(TAG, "createFromParcel>"); + final int index = in.readInt(); + final String desc = in.readString(); + final boolean status = in.readBoolean(); + final int subGroupId = in.readInt(); + + final int metadataLength = in.readInt(); + byte[] metadata = null; + if (metadataLength > 0) { + metadata = new byte[metadataLength]; + in.readByteArray(metadata); + } + + BleBroadcastSourceChannel srcChannel = + new BleBroadcastSourceChannel(index, desc, status, subGroupId, metadata); + log(TAG, "createFromParcel:" + srcChannel); + return srcChannel; + } + + public BleBroadcastSourceChannel[] newArray(int size) { + return new BleBroadcastSourceChannel[size]; + } + }; + + + + @Override + public void writeToParcel(Parcel out, int flags) { + log(TAG, "writeToParcel>"); + out.writeInt(mIndex); + out.writeString(mDescription); + out.writeBoolean(mStatus); + out.writeInt(mSubGroupId); + if (mMetadata != null) { + out.writeInt(mMetadata.length); + out.writeByteArray(mMetadata); + } else { + out.writeInt(0); + } + log(TAG, "writeToParcel:" + toString()); + } + private static void log(String TAG, String msg) { + BleBroadcastSourceInfo.BASS_Debug(TAG, msg); + } +}; + diff --git a/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastSourceInfo.java b/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastSourceInfo.java new file mode 100644 index 00000000000..74a114f11c2 --- /dev/null +++ b/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastSourceInfo.java @@ -0,0 +1,1038 @@ + /* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ +package android.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.IBluetoothGatt; +import android.bluetooth.IBluetoothManager; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Retention; +import android.annotation.IntDef; +import android.compat.annotation.UnsupportedAppUsage; + +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; +import java.util.Objects; +import android.util.Log; +import java.util.List; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.HashMap; + +/** + * This class provides methods to get various information of Broadcast + * source information stored in remote. Users can call get/set methods + * enquire the required information + * + *

This also acts as general data structure for updating the Broadcast + * source information + * This class is used to input the User provided data for below operations + * {@link BleBroadcastAudioScanAssistManager#addBroadcastSource}, + * {@link BleBroadcastAudioScanAssistManager#updateBroadcastSource} and + * {@link BleBroadcastAudioScanAssistManager#setBroadcastCode} + * + *

This is also used to pack all Broadcast source information as part of {@link #ACTION_BROADCAST_RECEIVER_STATE} + * Intent. User can retrive the {@link BleBroadcastSourceInfo} using {@link BleBroadcastSourceInfo#EXTRA_RECEIVER_STATE} + * extra field + * @hide + */ +public final class BleBroadcastSourceInfo implements Parcelable { + + private static final String TAG = "BleBroadcastSourceInfo"; + private static final boolean BASS_DBG = Log.isLoggable(TAG, Log.VERBOSE); + + /** @hide + * @deprecated + */ + @Deprecated + @IntDef(prefix = "BROADCAST_ASSIST_ADDRESS_TYPE_", value = { + BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC, + BROADCAST_ASSIST_ADDRESS_TYPE_RANDOM + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BroadcastAssistAddressType {} + + /** + * Address Type of the LE Broadcast Audio Source Device + * Specifies whether LE Broadcast Audio Source device using public OR + * random address for the LE Audio broadcasts + * + * @deprecated + */ + @Deprecated + public static final int BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC = 0; + /** + * Address Type of the LE Broadcast Audio Source Device + * Specifies whether LE Broadcast Audio Source device using public OR + * random address for the LE Audio broadcasts + * + * @deprecated + */ + @Deprecated + public static final int BROADCAST_ASSIST_ADDRESS_TYPE_RANDOM = 1; + /** + * Address Type of the LE Broadcast Audio Source Device + * Specifies whether LE Broadcast Audio Source device using public PR + * random address for the LE Audio broadcasts + * + * @deprecated + */ + @Deprecated + public static final int BROADCAST_ASSIST_ADDRESS_TYPE_INVALID = 0xFFFF; + + /** @hide */ + @IntDef(prefix = "BROADCAST_ASSIST_PA_SYNC_STATE_", value = { + BROADCAST_ASSIST_PA_SYNC_STATE_IDLE, + BROADCAST_ASSIST_PA_SYNC_STATE_SYNCINFO_REQ, + BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC, + BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_FAIL, + BROADCAST_ASSIST_PA_SYNC_STATE_NO_PAST + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BroadcastAssistMetadataSyncState {} + + /** + * Meta data Sync State + * Broadcast receiver sync state w.r.t PA. State IDLE specifies that broadcast + * receiver is not able to sync the Metada/PA + */ + public static final int BROADCAST_ASSIST_PA_SYNC_STATE_IDLE = 0; + /** + * Meta data Sync State + * Broadcast receiver sync state w.r.t PA. State SYNCINFO REQ specifies that broadcast + * receiver requesting for SYNCINFO from the Scan Offloader to synchronie + * to Metadata/PA + */ + public static final int BROADCAST_ASSIST_PA_SYNC_STATE_SYNCINFO_REQ = 1; + /** + * Meta data Sync State + * Broadcast receiver sync state w.r.t PA. State INSYNC specifies that broadcast + * receiver in sync with to Metadata/PA. + */ + public static final int BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC = 2; + /** + * Meta data Sync State + * Broadcast receiver sync state w.r.t PA. State INSYNC specifies that broadcast + * receiver is failed to sync with Metadata/PA. + */ + public static final int BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_FAIL = 3; + /** + * Meta data Sync State + * Broadcast receiver sync state w.r.t PA. State SYNC NOPAST denotes that broadcast + * receiver needs PAST procedure to sync with Metadata. + */ + public static final int BROADCAST_ASSIST_PA_SYNC_STATE_NO_PAST = 4; + /** + * Meta data Sync State + * Broadcast receiver sync state w.r.t PA. State SYNC NOPAST denotes that broadcast + * receiver needs PAST procedure to sync with Metadata. + */ + public static final int BROADCAST_ASSIST_PA_SYNC_STATE_INVALID = 0xFFFF; + + /** @hide */ + @IntDef(prefix = "BROADCAST_ASSIST_AUDIO_SYNC_STATE_", value = { + BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED, + BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BroadcastAssistAudioSyncState {} + + /** + * Broadcast Audio stream Sync State + * Broadcast receiver sync state w.r.t Broadcast Audio stream BIS. denotes + * receiver is not synchronized to LE Audio BIS + */ + public static final int BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED = 0; + /** + * Broadcast Audio stream Sync State + * Broadcast receiver sync state w.r.t Broadcast Audio stream BIS. denotes + * receiver is not synchronized to LE Audio BIS + */ + public static final int BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED = 1; + /** + * Broadcast Audio stream Sync State + * Broadcast receiver sync state w.r.t Broadcast Audio stream BIS. denotes + * receiver is not synchronized to LE Audio BIS + */ + public static final int BROADCAST_ASSIST_AUDIO_SYNC_STATE_INVALID = 0xFFFF; + + + /** @hide */ + @IntDef(prefix = "BROADCAST_ASSIST_ENC_STATE_", value = { + BROADCAST_ASSIST_ENC_STATE_UNENCRYPTED, + BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED, + BROADCAST_ASSIST_ENC_STATE_DECRYPTING + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BroadcastAssistEncryptionState {} + /** + * Encryption Status at the LE Audio broadcast receiver side + * UNENCRYPTED denoted that broadcast receiver is in sync with an uncrypted + * broadcasted audio + */ + public static final int BROADCAST_ASSIST_ENC_STATE_UNENCRYPTED = 0; + /** + * Encryption Status at the LE Audio broadcast receiver side + * PIN_NEEDED denote that the Broadcast receiver needs broadcast PIN + * to sync and listen to Broadcasted Audio + */ + public static final int BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED = 1; + /** + * Encryption Status at the LE Audio broadcast receiver side + * state DECRYPTING denote that the Broadcast receiver is able to decrypt + * and listen to the Broadcasted Audio + */ + public static final int BROADCAST_ASSIST_ENC_STATE_DECRYPTING = 2; + /** + * Encryption Status at the LE Audio broadcast receiver side + * state BADCODE denote that the Broadcast receiver has got bad code + * and not able decrypt + * Incorrect code that Scan delegator tried to decrypt can be retrieved from + * + */ + public static final int BROADCAST_ASSIST_ENC_STATE_BADCODE = 3; + /** + * Encryption Status at the LE Audio broadcast receiver side + * state DECRYPTING denote that the Broadcast receiver is able to decrypt + * and listen to the Broadcasted Audio + */ + public static final int BROADCAST_ASSIST_ENC_STATE_INVALID = 0xFFFF; + + /* + * Invalid Broadcast source Information Id + */ + public static final byte BROADCAST_ASSIST_INVALID_SOURCE_ID = (byte)0x00; + /* + * Invalid Broadcaster Identifier of the given Broadcast Source + */ + public static final int BROADCASTER_ID_INVALID = 0xFFFF; + /** + * Used as an int extra field in {@link BleBroadcastAudioScanAssistManager#ACTION_BROADCAST_RECEIVER_STATE} + * intent notifys the Broadcast Source Information to Application layer + * + *

Source Info object can be extracted using this extra field at Application layer + * + * This is used to read the {@link BleBroadcastSourceInfo } parcelable object + * @hide + */ + public static final String EXTRA_SOURCE_INFO = "android.bluetooth.device.extra.SOURCE_INFO"; + /** + * Used as an int extra field in {@link BleBroadcastAudioScanAssistManager#ACTION_BROADCAST_RECEIVER_STATE} + * intent Broadcast Source Information to Application layer + * + *

Index of the Source Info object can be extracted using this extra field at Application layer + * + * This is used to read the {@link BleBroadcastSourceInfo } parcelable object + * @hide + */ + public static final String EXTRA_SOURCE_INFO_INDEX = "android.bluetooth.device.extra.SOURCE_INFO_INDEX"; + /** + * Used as an int extra field in {@link BleBroadcastAudioScanAssistManager#ACTION_BROADCAST_RECEIVER_STATE} + * intent notifys the Broadcast Source Information to Application layer + * + *

Maximm number of the Broadcast Source Information that given broadcast receiver can hold, can be extracted using + * this extra field at Application layer + * + * @hide + */ + public static final String EXTRA_MAX_NUM_SOURCE_INFOS = "android.bluetooth.device.extra.MAX_NUM_SOURCE_INFOS"; + private byte mSourceId; + private @BroadcastAssistAddressType int mSourceAddressType; + private BluetoothDevice mSourceDevice; + private byte mSourceAdvSid; + private int mBroadcasterId; + private @BroadcastAssistMetadataSyncState int mMetaDataSyncState; + private @BroadcastAssistAudioSyncState int mAudioSyncState; + private Map mAudioBisIndexList = new HashMap (); + private @BroadcastAssistEncryptionState int mEncyptionStatus; + private Map mMetadataList = new HashMap(); + private String mBroadcastCode; + private byte[] mBadBroadcastCode; + private byte mNumSubGroups; + private static final int BIS_NO_PREF = 0xFFFFFFFF; + private static final int BROADCAST_CODE_SIZE = 16; + + /** + * Constructor to create an Empty object of {@link BleBroadcastSourceInfo } with given source Id, + * which contains, Broadcast reciever state information for Broadcast Assistant Usecases. + * + * This is mainly used to represent the Empty Broadcast source entries + * + * @param sourceId Source Id for this broadcast source info object + * + * @deprecated + * @hide + */ + @Deprecated + public BleBroadcastSourceInfo (byte sourceId) { + mSourceId = sourceId; + mMetaDataSyncState = BROADCAST_ASSIST_PA_SYNC_STATE_INVALID; + mAudioSyncState = BROADCAST_ASSIST_AUDIO_SYNC_STATE_INVALID; + mSourceAddressType = BROADCAST_ASSIST_ADDRESS_TYPE_INVALID; + mSourceDevice = null; + mSourceAdvSid = (byte)0x00; + mEncyptionStatus = BROADCAST_ASSIST_ENC_STATE_INVALID; + mBroadcastCode = null; + mBadBroadcastCode = null; + mNumSubGroups = 0; + mBroadcasterId = BROADCASTER_ID_INVALID; + } + /** + * Constructor to create an object of {@link BleBroadcastSourceInfo } which contains + * Broadcast reciever state information for Broadcast Assistant Usecases. + * This is mainly used for input purpose of {@link BleBroadcastAudioScanAssistManager#addBroadcastSource} + * operation + * + * @param audioSource BluetoothDevice object whcih is selected as Broadcast source + * @param advSid advertising Sid of the Broadcast source device for which reciever synchronized with. + * @param addressType type of address. This can be be one of {@link #BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC} or + * {@link #BROADCAST_ASSIST_ADDRESS_TYPE_RANDOM} + * @param metadataSyncState sync status of metadata at the receiver side from this Broadcast source. This can + * be one of {@link #BROADCAST_ASSIST_PA_SYNC_STATE_IDLE}, {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNCINFO_REQ}, + * {@link #BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC}, + * {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_FAIL} OR {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_NO_PAST} + * @param audioSyncState Audio sync status of metadata at the receiver side from this broadcast source. This can be + * one of {@link #BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED} OR + * {@link #BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED} + * @param audioBisIndex Audio BIS index for what Broadcast reciever synchronized with + * @param metadataLength Length of the metadata field + * @prama metadata metadata information about the type Broadcast information being synchronized at receiver side + * + * + * @hide + */ + /*package*/ BleBroadcastSourceInfo (BluetoothDevice audioSource, + byte advSid, + @BroadcastAssistAddressType int addressType, + @BroadcastAssistMetadataSyncState int metadataSyncstate, + @BroadcastAssistAudioSyncState int audioSyncstate, + List selectedBISIndicies + ) { + mMetaDataSyncState = metadataSyncstate; + mAudioSyncState = audioSyncstate; + mSourceAddressType = addressType; + mSourceDevice = audioSource; + mSourceAdvSid = advSid; + mBroadcasterId = BROADCASTER_ID_INVALID; + if (selectedBISIndicies == null) { + BASS_Debug(TAG, "selectedBISIndiciesList is null"); + } else { + for (int i=0; i> selectedBISIndiciesList, + Map metadataList + ) { + mSourceId = sourceId; + mSourceAddressType = addressType; + mSourceDevice = audioSource; + mSourceAdvSid = advSid; + mBroadcasterId = broadcasterId; + mMetaDataSyncState = metadataSyncstate; + mAudioSyncState = audioSyncstate; + mEncyptionStatus = encryptionStatus; + if (badCode != null) { + mBadBroadcastCode = new byte[BROADCAST_CODE_SIZE]; + System.arraycopy(badCode, 0, mBadBroadcastCode, 0, mBadBroadcastCode.length); + } + mNumSubGroups = numSubGroups; + int audioBisIndex = 0; + if (selectedBISIndiciesList != null) { + for (Map.Entry> entry : selectedBISIndiciesList.entrySet()) { + List selectedBISIndicies = entry.getValue(); + if (selectedBISIndicies == null) { + //do nothing + BASS_Debug(TAG, "selectedBISIndiciesList is null"); + } else { + for (int i=0; i entry : metadataList.entrySet()) { + byte[] metadata = entry.getValue(); + if (metadata != null && metadata.length != 0) { + byte[] mD = new byte[metadata.length]; + System.arraycopy(metadata, 0, mD, 0, metadata.length); + } + mMetadataList.put(entry.getKey(), metadata); + } + } + } + + /** + * Constructor override to create an object of {@link BleBroadcastSourceInfo } which contains + * Broadcast reciever state information for Broadcast Assistant Usecases. + * + * This is mainly used for output purpose to create an object from the receiver state information + * read from the remote BASS server. This will be packed and broadcasted as an Intent using + * {@link #ACTION_BROADCAST_RECEIVER_STATE} + * + * @param audioSource BluetoothDevice object whcih is selected as Broadcast source + * @param advSid advertising Sid of the Broadcast source device for which reciever synchronized with + * @param addressType type of address. This can be be one of {@link #BLE_ASSIST_ADDRESS_TYPE_PUBLIC} or + * {@link #BLE_ASSIST_ADDRESS_TYPE_RANDOM} + * @param metadataSyncState sync status of metadata at the receiver side from this Broadcast source. This can + * be one of {@link #BROADCAST_ASSIST_PA_SYNC_STATE_IDLE}, {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNCINFO_REQ}, + * {@link #BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC}, + * {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_FAIL} OR {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_NO_PAST} + * @param audioSyncState Audio sync status of metadata at the receiver side from this broadcast source. This can be + * one of {@link #BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED} OR + * {@link #BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED} + * @param encryptionStatus Encryotion state at Broadcast receiver. This can be one of {@link #BROADCAST_ASSIST_ENC_STATE_UNENCRYPTED}, + * {@link #BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED} OR {@link #BROADCAST_ASSIST_ENC_STATE_DECRYPTING} + * @param audioBisIndex Audio BIS index for what Broadcast reciever synchronized with + * @param metadataLength Length of the metadata field + * @prama metadata metadata information about the type Broadcast information being synchronized at receiver side + * @param broadcastCode Numeric Character String maximum of 16 characters in length, which serves as broadcast PIN code + * + * @hide + */ + /*package*/ BleBroadcastSourceInfo (BluetoothDevice device, + byte sourceId, + byte advSid, + @BroadcastAssistAddressType int addressType, + @BroadcastAssistMetadataSyncState int metadataSyncstate, + @BroadcastAssistAudioSyncState int audioSyncstate, + List selectedBISIndicies, + @BroadcastAssistEncryptionState int encryptionStatus, + String broadcastCode) { + mSourceId = sourceId; + mMetaDataSyncState = metadataSyncstate; + mAudioSyncState = audioSyncstate; + mEncyptionStatus = encryptionStatus; + mSourceAddressType = addressType; + mSourceDevice = device; + mSourceAdvSid = advSid; + mBroadcasterId = BROADCASTER_ID_INVALID; + if (selectedBISIndicies == null) { + BASS_Debug(TAG, "selectedBISIndiciesList is null"); + } else { + for (int i=0; i bisIndiciesList, + Map metadataList + ) { + mSourceId = sourceId; + mMetaDataSyncState = metadataSyncstate; + mAudioSyncState = audioSyncstate; + mEncyptionStatus = encryptionStatus; + mSourceAddressType = addressType; + mSourceDevice = device; + mSourceAdvSid = advSid; + mBroadcasterId = broadcasterId; + mBroadcastCode = broadcastCode; + if (badCode != null && badCode.length != 0) { + mBadBroadcastCode= new byte[badCode.length]; + System.arraycopy(badCode, 0, mBadBroadcastCode, 0, badCode.length); + } + mNumSubGroups = numSubGroups; + mAudioBisIndexList = new HashMap (bisIndiciesList); + mMetadataList = new HashMap (metadataList); + } + + @Override + public boolean equals(Object o) { + if (o instanceof BleBroadcastSourceInfo) { + BleBroadcastSourceInfo other = (BleBroadcastSourceInfo) o; + BASS_Debug(TAG, "other>> " + o.toString()); + BASS_Debug(TAG, "local>> " + toString()); + return (other.mSourceId == mSourceId + && other.mMetaDataSyncState == mMetaDataSyncState + && other.mAudioSyncState == mAudioSyncState + && other.mSourceAddressType == mSourceAddressType + && other.mSourceDevice == mSourceDevice + && other.mSourceAdvSid == mSourceAdvSid + && other.mEncyptionStatus == mEncyptionStatus + && other.mBroadcastCode == mBroadcastCode + && other.mBroadcasterId == mBroadcasterId + ); + } + return false; + } + + public boolean isEmptyEntry() { + boolean ret = false; + if (mMetaDataSyncState == (int)BROADCAST_ASSIST_PA_SYNC_STATE_INVALID && + mAudioSyncState == (int)BROADCAST_ASSIST_AUDIO_SYNC_STATE_INVALID && + mSourceAddressType == (int)BROADCAST_ASSIST_ADDRESS_TYPE_INVALID && + mSourceDevice == null && + mSourceAdvSid == (byte)0 && + mEncyptionStatus == (int)BROADCAST_ASSIST_ENC_STATE_INVALID + ) { + ret = true; + } + BASS_Debug(TAG, "isEmptyEntry returns: " + ret); + return ret; + } + + public boolean matches(BleBroadcastSourceInfo srcInfo) { + boolean ret = false; + if (srcInfo == null) { + ret = false; + } else { + if (mSourceDevice == null) { + if (mSourceAdvSid == srcInfo.getAdvertisingSid() && + mSourceAddressType == srcInfo.getAdvAddressType()) { + ret = true; + } + } else { + if (mSourceDevice.equals(srcInfo.getSourceDevice()) && + mSourceAdvSid == srcInfo.getAdvertisingSid() && + mSourceAddressType == srcInfo.getAdvAddressType() && + mBroadcasterId == srcInfo.getBroadcasterId()) { + ret = true; + } + } + } + BASS_Debug(TAG, "matches returns: " + ret); + return ret; + } + + @Override + public int hashCode() { + return Objects.hash(mSourceId, mMetaDataSyncState, mAudioSyncState, + mSourceAddressType, mSourceDevice, mSourceAdvSid, + mEncyptionStatus, mBroadcastCode); + } + + @Override + public int describeContents() { + return 0; + } + @Override + public String toString() { + return "{BleBroadcastSourceInfo : mSourceId" + mSourceId + + " sourceDevice: " + mSourceDevice + + " addressType: " + mSourceAddressType + + " mSourceAdvSid:" + mSourceAdvSid + + " mMetaDataSyncState:" + mMetaDataSyncState + + " mAudioSyncState" + mAudioSyncState + + " mEncyptionStatus" + mEncyptionStatus + + " mBadBroadcastCode" + mBadBroadcastCode + + " mNumSubGroups" + mNumSubGroups + + " mBroadcastCode" + mBroadcastCode + + " mAudioBisIndexList" + mAudioBisIndexList + + " mMetadataList" + mMetadataList + + " mBroadcasterId" + mBroadcasterId + + "}"; + } + + /** + * Gets the Source Id of the BleBroadcastSourceInfo Object + * + * @return byte representing the Source Id of the Broadcast Source Info Object + * {@link #BROADCAST_ASSIST_INVALID_SOURCE_ID} in case if this field is not valid + * @hide + */ + public byte getSourceId () { + return mSourceId; + } + + /** + * Sets the Source Id of the BleBroadcastSourceInfo Object + * + * @param byte source Id for the BleBroadcastSourceInfo Object + * + * @hide + */ + public void setSourceId (byte sourceId) { + mSourceId = sourceId; + } + + /** + * Sets the Broadcast source device for the BleBroadcastSourceInfo Object + * + * @param BluetoothDevice which need to be set as Broadcast source device + * @hide + */ + public void setSourceDevice(BluetoothDevice sourceDevice) { + mSourceDevice = sourceDevice; + } + + /** + * Gets the Broadcast source Device object from the BleBroadcastSourceInfo Object + * + * @return BluetoothDevice object for Broadcast source device + * @hide + */ + public BluetoothDevice getSourceDevice () { + return mSourceDevice; + } + + /** + * Sets the address type of the Broadcast source advertisement for the BleBroadcastSourceInfo Object + * + * @param byte addressType, this can be one of {@link #BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC} OR {@link #BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC} + * @hide + */ + public void setAdvAddressType(int addressType) { + mSourceAddressType = addressType; + } + + /** + * Gets the address type of the Broadcast source advertisement for the BleBroadcastSourceInfo Object + * + * @return byte addressType, this can be one of {@link #BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC} OR {@link #BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC} + * @hide + * + * @deprecated + */ + @UnsupportedAppUsage + @Deprecated + public int getAdvAddressType () { + return mSourceAddressType; + } + + /** + * Sets the advertising Sid of the Broadcast source advertisement for the BleBroadcastSourceInfo Object + * + * @param byte advertising Sid value + * @hide + */ + public void setAdvertisingSid(byte advSid) { + mSourceAdvSid = advSid; + } + + /** + * Gets the advertising Sid of the Broadcast source advertisement for the BleBroadcastSourceInfo Object + * + * @return byte advertising Sid value + * @hide + */ + public byte getAdvertisingSid () { + return mSourceAdvSid; + } + + /** + * Gets the Broadcast Id of the Broadcast source of the BleBroadcastSourceInfo Object + * + * @return int broadcast source Identifier + * @hide + */ + public int getBroadcasterId () { + return mBroadcasterId; + } + + /** + * Sets the Metadata sync status at the Broadcast receiver side for the BleBroadcastSourceInfo Object + * + * @param BroadcastAssistMetadataSyncState representing the state of Meta data sync status. this can be one of + * {@link #BROADCAST_ASSIST_PA_SYNC_STATE_IDLE}, {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNCINFO_REQ}, + * {@link #BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC}, + * {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_FAIL} OR {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_NO_PAST} + * + * @hide + */ + /*package*/ void setMetadataSyncState(@BroadcastAssistMetadataSyncState int metadataSyncState) { + mMetaDataSyncState = metadataSyncState; + } + + /** + * Gets the Metadata sync status at the Broadcast receiver side from the BleBroadcastSourceInfo Object + * + * @return BroadcastAssistMetadataSyncState representing the state of Meta data sync status. this can be one of + * {@link #BROADCAST_ASSIST_PA_SYNC_STATE_IDLE}, {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNCINFO_REQ}, + * {@link #BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC}, + * {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_FAIL} OR {@link #BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_NO_PAST} + * + * @hide + */ + public @BroadcastAssistMetadataSyncState int getMetadataSyncState () { + return mMetaDataSyncState; + } + + /** + * Sets the Audio sync status at the Broadcast receiver side for the BleBroadcastSourceInfo Object + * + * @param BroadcastAssistAudioSyncState representing the state of Meta data sync status. this can be one of + * {@link #BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED} OR + * {@link #BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED} + * + * @hide + */ + /*package*/ void setAudioSyncState(@BroadcastAssistAudioSyncState int audioSyncState) { + mAudioSyncState = audioSyncState; + } + + /** + * Gets the Audio sync status at the Broadcast receiver side from the BleBroadcastSourceInfo Object + * + * @return BroadcastAssistAudioSyncState representing the state of Meta data sync status. this can be one of + * {@link #BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED} OR + * {@link #BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED} * + * @hide + */ + public @BroadcastAssistAudioSyncState int getAudioSyncState () { + return mAudioSyncState; + } + + /** + * Sets the Encryption status at the Broadcast receiver side for the BleBroadcastSourceInfo Object + * + * @param BroadcastAssistEncryptionState representing the state of Meta data sync status. This can be one of + * {@link #BROADCAST_ASSIST_ENC_STATE_UNENCRYPTED}, + * {@link #BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED}, {@link #BROADCAST_ASSIST_ENC_STATE_DECRYPTING} + * Or {@link #BROADCAST_ASSIST_ENC_STATE_BADCODE} + * @hide + */ + /*package*/ void setEncryptionStatus(@BroadcastAssistEncryptionState int encryptionStatus) { + mEncyptionStatus = encryptionStatus; + } + + /** + * Gets the Audio sync status at the Broadcast receiver side from the BleBroadcastSourceInfo Object + * + * @return BroadcastAssistEncryptionState representing the state of Meta data sync status. This can be one of + * {@link #BROADCAST_ASSIST_ENC_STATE_UNENCRYPTED}, + * {@link #BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED} ,{@link #BROADCAST_ASSIST_ENC_STATE_DECRYPTING} + * Or {@link #BROADCAST_ASSIST_ENC_STATE_BADCODE} + * @hide + */ + public @BroadcastAssistEncryptionState int getEncryptionStatus () { + return mEncyptionStatus; + } + + /** + * Gets the Incorrect Broadcast code with which Scan delegator try + * decrypt the Broadcast audio and failed + * + * This code is valid only if {@link #getEncryptionStatus} returns + * {@link #BROADCAST_ASSIST_ENC_STATE_BADCODE} + * + * @param byte[] byte array containing bad broadcast value + * null if the current Encryptetion status is + * not {@link #BROADCAST_ASSIST_ENC_STATE_BADCODE} + * + * @hide + */ + public byte[] getBadBroadcastCode () { + return mBadBroadcastCode; + } + + /** + * Gets the number of subgroups of the BleBroadcastSourceInfo Object + * + * @return byte number of subgroups + * @hide + * + * @deprecated + */ + @UnsupportedAppUsage + @Deprecated + public byte getNumberOfSubGroups () { + return mNumSubGroups; + } + + /** + * Sets the Audio Broadcast channels to which receiver need to be synchronized with, + * for BleBroadcastSourceInfo Object + * + * + * @param int audioBis Index to which reciever need to be synchronized with + * @hide + */ + /*package*/ void setBroadcastChannelsSyncStatus(List selectedBISIndicies) { + if (selectedBISIndicies == null) { + //set No preference + BASS_Debug(TAG, "selectedBISIndiciesList is null"); + return; + } + for (int i=0; i getBroadcastChannelsSyncStatus () { + List bcastIndicies = new ArrayList(); + for (int i=0; i>1; + index++; + } + } + + BASS_Debug(TAG, "returning Bisindicies:" + bcastIndicies); + return bcastIndicies; + } + + @UnsupportedAppUsage + @Deprecated + public Map getBisIndexList() { + return mAudioBisIndexList; + } + + /*package*/ void setBroadcastCode(String broadcastCode) { + mBroadcastCode = broadcastCode; + } + + @UnsupportedAppUsage + @Deprecated + public void setBroadcasterId(int broadcasterId) { + mBroadcasterId = broadcasterId; + } + + /** + * Gets the broadcastCode value from BleBroadcastSourceInfo Object + * + * @param String broadcast code from the BleBroadcastSourceInfo object + * @hide + * + * @deprecated + */ + @UnsupportedAppUsage + @Deprecated + public String getBroadcastCode () { + return mBroadcastCode; + } + + private void writeMapToParcel(Parcel dest, Map bisIndexList) { + dest.writeInt(bisIndexList.size()); + for (Map.Entry entry : bisIndexList.entrySet()) { + dest.writeInt(entry.getKey()); + dest.writeInt(entry.getValue()); + } + } + + private static void readMapFromParcel(Parcel in, Map bisIndexList) { + int size = in.readInt(); + + for (int i = 0; i < size; i++) { + Integer key = in.readInt(); + Integer value = in.readInt(); + bisIndexList.put(key, value); + } + } + + private void writeMetadataListToParcel(Parcel dest, Map metadataList) { + dest.writeInt(metadataList.size()); + for (Map.Entry entry : metadataList.entrySet()) { + dest.writeInt(entry.getKey()); + byte[] metadata = entry.getValue(); + if (metadata != null) { + dest.writeInt(metadata.length); + dest.writeByteArray(metadata); + } + } + } + + private static void readMetadataListFromParcel(Parcel in, Map metadataList) { + int size = in.readInt(); + + for (int i = 0; i < size; i++) { + Integer key = in.readInt(); + Integer metaDataLen = in.readInt(); + byte[] metadata = null; + if (metaDataLen != 0) { + metadata = new byte[metaDataLen]; + in.readByteArray(metadata); + } + metadataList.put(key, metadata); + } + } + + public static final @android.annotation.NonNull Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public BleBroadcastSourceInfo createFromParcel(Parcel in) { + + BASS_Debug(TAG, "createFromParcel>"); + final byte sourceId = in.readByte(); + final int sourceAddressType = in.readInt(); + final BluetoothDevice sourceDevice = in.readTypedObject( + BluetoothDevice.CREATOR); + final byte sourceAdvSid = in.readByte(); + final int broadcastId = in.readInt(); + BASS_Debug(TAG, "broadcastId" + broadcastId); + final int metaDataSyncState = in.readInt(); + final int audioSyncState = in.readInt(); + BASS_Debug(TAG, "audioSyncState" + audioSyncState); + final int encyptionStatus = in.readInt(); + final int badBroadcastLen = in.readInt(); + byte[] badBroadcastCode = null; + if (badBroadcastLen > 0) { + badBroadcastCode = new byte[badBroadcastLen]; + in.readByteArray(badBroadcastCode); + } + final byte numSubGroups = in.readByte(); + final String broadcastCode = in.readString(); + Map bisIndexList = new HashMap (); + readMapFromParcel(in, bisIndexList); + Map metadataList = new HashMap (); + readMetadataListFromParcel(in, metadataList); + + BleBroadcastSourceInfo srcInfo = new BleBroadcastSourceInfo(sourceDevice, sourceId, sourceAdvSid, broadcastId, + sourceAddressType, metaDataSyncState,audioSyncState, + encyptionStatus, broadcastCode, badBroadcastCode, numSubGroups, bisIndexList, metadataList); + BASS_Debug(TAG, "createFromParcel:" + srcInfo); + return srcInfo; + } + + public BleBroadcastSourceInfo[] newArray(int size) { + return new BleBroadcastSourceInfo[size]; + } + }; + + @Override + public void writeToParcel(Parcel out, int flags) { + BASS_Debug(TAG, "writeToParcel>"); + out.writeByte(mSourceId); + out.writeInt(mSourceAddressType); + out.writeTypedObject(mSourceDevice, 0); + out.writeByte(mSourceAdvSid); + out.writeInt(mBroadcasterId); + out.writeInt(mMetaDataSyncState); + out.writeInt(mAudioSyncState); + out.writeInt(mEncyptionStatus); + if (mBadBroadcastCode != null) { + out.writeInt(mBadBroadcastCode.length); + out.writeByteArray(mBadBroadcastCode); + } else { + //write ZERO to parcel to say no badBroadcastcode + out.writeInt(0); + } + out.writeByte(mNumSubGroups); + out.writeString(mBroadcastCode); + writeMapToParcel(out, mAudioBisIndexList); + writeMetadataListToParcel(out, mMetadataList); + BASS_Debug(TAG, "writeToParcel:" + toString()); + } + + static void BASS_Debug(String TAG, String msg) { + if (BASS_DBG) { + Log.d(TAG, msg); + } + } + +}; + diff --git a/le_audio/frameworks/base/core/java/android/bluetooth/BluetoothBroadcast.java b/le_audio/frameworks/base/core/java/android/bluetooth/BluetoothBroadcast.java new file mode 100644 index 00000000000..b22480b284e --- /dev/null +++ b/le_audio/frameworks/base/core/java/android/bluetooth/BluetoothBroadcast.java @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +package android.bluetooth; + +import android.Manifest; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.content.Context; +import android.os.Binder; +import android.os.Build; +import android.os.IBinder; +import android.os.ParcelUuid; +import android.os.RemoteException; +import android.util.Log; +import android.app.ActivityThread; +import java.util.ArrayList; +import java.util.List; + +/** + * This class provides the public APIs to control the Bluetooth Broadcast + * profile. + * + *

BluetoothBroadcast is a proxy object for controlling the Bluetooth + * Broadcast Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} + * to get the BluetoothBroadcast proxy object. + * + * @hide + */ + +public final class BluetoothBroadcast implements BluetoothProfile{ + private static final String TAG = "BluetoothBroadcast"; + private static final boolean DBG = true; + private static final boolean VDBG = false; + + /** + * Intent used to broadcast the change in broadcast state. + * + *

This intent will have 3 extras: + *

    + *
  • {@link #EXTRA_STATE} - The current state of the profile.
  • + *
  • {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.
  • + *
+ * + *

{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of + * {@link #STATE_Disabled}, {@link #Enabling}, + * {@link #STATE_ENABLED}, {@link #STATE_DISABLING}. + * + *

Requires {@link android.Manifest.permission#BLUETOOTH} permission to + * receive. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_BROADCAST_STATE_CHANGED = + "android.bluetooth.broadcast.profile.action.BROADCAST_STATE_CHANGED"; + + /** + * Intent used to broadcast the change in broadcast audio state. + * + *

This intent will have 3 extras: + *

    + *
  • {@link #EXTRA_STATE} - The current audio state .
  • + *
  • {@link #EXTRA_PREVIOUS_STATE}- The previous audio state.
  • + *
+ * + *

{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of + * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING}, + * + *

Requires {@link android.Manifest.permission#BLUETOOTH} permission to + * receive. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_BROADCAST_AUDIO_STATE_CHANGED = + "android.bluetooth.broadcast.profile.action.BROADCAST_AUDIO_STATE_CHANGED"; + + /** + * Intent used to broadcast encryption key generation status. + * + *

This intent will have 2 extras: + *

    + *
  • {@link #EXTRA_STATE} - The current audio state .
  • + *
+ * + *

{@link #EXTRA_STATE} can be any of + * {@link #TRUE}, {@link #FALSE}, + * + *

Requires {@link android.Manifest.permission#BLUETOOTH} permission to + * receive. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_BROADCAST_ENCRYPTION_KEY_GENERATED = + "android.bluetooth.broadcast.profile.action.BROADCAST_ENCRYPTION_KEY_GENERATED"; + + public static final int STATE_DISABLED = 10; + public static final int STATE_ENABLING = 11; + public static final int STATE_ENABLED = 12; + public static final int STATE_DISABLING = 13; + public static final int STATE_STREAMING = 14; + public static final int STATE_PLAYING = 10; + public static final int STATE_NOT_PLAYING = 11; + + private BluetoothAdapter mAdapter; + private final BluetoothProfileConnector mProfileConnector = + new BluetoothProfileConnector(this, BluetoothProfile.BROADCAST, "BluetoothBroadcast", + IBluetoothBroadcast.class.getName()) { + @Override + public IBluetoothBroadcast getServiceInterface(IBinder service) { + return IBluetoothBroadcast.Stub.asInterface(Binder.allowBlocking(service)); + } + }; + /** + * Create a BluetoothBroadcast proxy object for interacting with the local + * Bluetooth Broadcast service. + * @hide + */ + /*package*/ BluetoothBroadcast(Context context, ServiceListener listener) { + mAdapter = BluetoothAdapter.getDefaultAdapter(); + mProfileConnector.connect(context, listener); + } + + /* + * @hide + */ + //@UnsupportedAppUsage + /*package*/ void close() { + mProfileConnector.disconnect(); + } + + /* + * @hide + */ + private IBluetoothBroadcast getService() { + return mProfileConnector.getService(); + } + + @Override + public void finalize() { + // The empty finalize needs to be kept or the + // cts signature tests would fail. + } + /* + * @hide + */ + //@UnsupportedAppUsage + public boolean SetBroadcastMode(boolean enable) { + if (DBG) log("EnableBroadcast"); + String packageName = ActivityThread.currentPackageName(); + try { + final IBluetoothBroadcast service = getService(); + if (service != null && isEnabled()) { + return service.SetBroadcast(enable, packageName); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + /** + * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} + * with {@link BluetoothProfile#GATT} as argument + * + * @throws UnsupportedOperationException + */ + @Override + public int getConnectionState(BluetoothDevice device) { + throw new UnsupportedOperationException( + "Use BluetoothManager#getConnectedDevices instead."); + } + /** + * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} + * with {@link BluetoothProfile#GATT} as argument + * + * @throws UnsupportedOperationException + */ + @Override + public List getDevicesMatchingConnectionStates(int[] states) { + throw new UnsupportedOperationException( + "Use BluetoothManager#getConnectedDevices instead."); + } + /** + * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} + * with {@link BluetoothProfile#GATT} as argument + * + * @throws UnsupportedOperationException + */ + @Override + public List getConnectedDevices() { + throw new UnsupportedOperationException( + "Use BluetoothManager#getConnectedDevices instead."); + } + + /* + * @hide + */ + public boolean SetEncryption(boolean enable, int enc_len/*4bytes,16bytes*/, boolean use_existing) { + if (DBG) log("SetEncryption"); + String packageName = ActivityThread.currentPackageName(); + try { + final IBluetoothBroadcast service = getService(); + if (service != null && isEnabled()) { + return service.SetEncryption(enable, enc_len, use_existing, packageName); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + /* + * @hide + */ + public byte[] GetEncryptionKey() { + if (DBG) log("GetBroadcastEncryptionKey"); + String packageName = ActivityThread.currentPackageName(); + try { + final IBluetoothBroadcast service = getService(); + if (service != null && isEnabled()) { + return service.GetEncryptionKey(packageName); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return null; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return null; + } + } + /* + * @hide + */ + public int GetBroadcastStatus() { + if (DBG) log("GetBroadcastStatus"); + String packageName = ActivityThread.currentPackageName(); + try { + final IBluetoothBroadcast service = getService(); + if (service != null && isEnabled()) { + return service.GetBroadcastStatus(packageName); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return STATE_DISABLED; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return STATE_DISABLED; + } + } + //@UnsupportedAppUsage +// public @Nullable BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {return null;} + + private boolean isEnabled() { + if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; + return false; + } + + private static void log(String msg) { + Log.d(TAG, msg); + } +} + + diff --git a/le_audio/frameworks/base/core/java/android/bluetooth/BluetoothSyncHelper.java b/le_audio/frameworks/base/core/java/android/bluetooth/BluetoothSyncHelper.java new file mode 100644 index 00000000000..7072891d974 --- /dev/null +++ b/le_audio/frameworks/base/core/java/android/bluetooth/BluetoothSyncHelper.java @@ -0,0 +1,736 @@ +/* + * Copyright (c) 2020 The Linux Foundation. All rights reserved. + + * Copyright (C) 2017 The Android Open Source Project + * + * 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. + */ + +package android.bluetooth; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SystemApi; +import android.annotation.SdkConstant.SdkConstantType; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.IBluetoothGatt; +import android.bluetooth.IBluetoothManager; +import android.bluetooth.IBleBroadcastAudioScanAssistCallback; +import android.bluetooth.IBluetoothSyncHelper; +import android.bluetooth.BluetoothAdapter.LeScanCallback; +import android.os.Binder; +import android.os.IBinder; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; +import android.content.Context; + +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.ArrayList; +import java.util.List; +import java.util.HashMap; + +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.PeriodicAdvertisingCallback; +import android.bluetooth.le.PeriodicAdvertisingManager; +import android.bluetooth.le.PeriodicAdvertisingReport; +import android.bluetooth.le.BluetoothLeScanner; +import android.os.SystemProperties; +import java.util.IdentityHashMap; + +/** + * This class provides methods to perform Broadcast Scan Assistance client Profile related + * operations. + * It uses Bluetooth GATT APIs to achieve Braodcast Scan assistance client operations. Application should ensure + * BASS profile is connected with the given remote device before performing the operations using + * {@link BleBroadcastAudioScanAssistManager} interface operations + * + *

BluetoothSyncHelper is a proxy object for controlling the Bluetooth Scan Offloader (BASS client) + * Service via IPC. + * + *

Use {@link BluetoothAdapter#getProfileProxy} to get + * the BluetoothScanOfflaoder proxy object. Use + * {@link BluetoothAdapter#closeProfileProxy} to close the service connection. + * + *

Use {@link BluetoothAdapter#getProfileProxy} to get + * the BluetoothSyncHelper proxy object. Use + * {@link BluetoothAdapter#closeProfileProxy} to close the service connection. + * + * Note: Most of the methods here require + * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * @hide + */ +public final class BluetoothSyncHelper implements BluetoothProfile { + + private static final String TAG = "BluetoothSyncHelper"; + private static final boolean DBG = true; + + private BluetoothAdapter mBluetoothAdapter; + /* maps callback, to callback wrapper and sync handle */ + private Map mAppCallbackWrappers; + + private Map sBleAssistManagers = null; + private Context mContext = null; + + /** + * Intent used to broadcast the change in connection state of the Bass client + * profile. + * + *

This intent will have 3 extras: + *

    + *
  • {@link #EXTRA_STATE} - The current state of the profile.
  • + *
  • {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.
  • + *
  • {@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
  • + *
+ * + *

{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of + * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, + * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. + * + *

Requires {@link android.Manifest.permission#BLUETOOTH} permission to + * receive. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CONNECTION_STATE_CHANGED = + "android.bluetooth.bc.profile.action.CONNECTION_STATE_CHANGED"; + + + private final BluetoothProfileConnector mProfileConnector = + new BluetoothProfileConnector(this, BluetoothProfile.BC_PROFILE, + "BluetoothSyncHelper", IBluetoothSyncHelper.class.getName()) { + @Override + public IBluetoothSyncHelper getServiceInterface(IBinder service) { + return IBluetoothSyncHelper.Stub.asInterface(Binder.allowBlocking(service)); + } + }; + + /*package*/ void close() { + mProfileConnector.disconnect(); + mAppCallbackWrappers.clear(); + } + + /*package*/ IBluetoothSyncHelper getService() { + return mProfileConnector.getService(); + } + + static boolean isSupported() { + boolean isSupported = SystemProperties.getBoolean("persist.vendor.service.bt.bc", true); + log("BluetoothSyncHelper: isSupported returns " + isSupported); + return isSupported; + } + /** + * Create a BluetoothHeadset proxy object. + */ + /*package*/ BluetoothSyncHelper(Context context, ServiceListener listener) { + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mProfileConnector.connect(context, listener); + BluetoothManager bluetoothManager = context.getSystemService( + BluetoothManager.class); + mAppCallbackWrappers = new IdentityHashMap(); + sBleAssistManagers = new IdentityHashMap(); + mContext = context; + } + + /** + * Interface to get Broadcast Audio Scan assistance for LE Audio usecases.This is instantiated per BluetoothDevice + * which is Scan delegator + * Application will get an Instance of the {@link BleBroadcastAudioScanAssistManager} for the given + * scan delegator device + * + * @param BluetoothDevice Scan Delegator device for which BLE Broadcast SCAN Assistance operations will + * be performed + * @param {@link #BleBroadcastAudioScanAssistCallback} where callbacks related to BLE Broadcast Scan + * assistance will be deliverd + * @hide + */ + public BleBroadcastAudioScanAssistManager getBleBroadcastAudioScanAssistManager( + BluetoothDevice device, + BleBroadcastAudioScanAssistCallback callback) { + if (isSupported() == false) { + Log.e(TAG, "Broadcast scan assistance not supported"); + return null; + } + + BleBroadcastAudioScanAssistManager assistMgr = null; + if (sBleAssistManagers != null) { + assistMgr = sBleAssistManagers.get(device); + } + if (assistMgr == null) { + assistMgr = new BleBroadcastAudioScanAssistManager(this, device, + callback); + } else { + //object already exists, just registers the callback and retrun the same object + log("calling registerAppCb only"); + } + registerAppCallback(device, callback); + return assistMgr; + } + + + /** + * Initiate connection to a BASS server profile of the remote bluetooth device. + * + *

This API returns false in scenarios like the profile on the + * device is already connected or Bluetooth is not turned on. + * When this API returns true, it is guaranteed that + * connection state intent for the profile will be broadcasted with + * the state. Users can get the connection state of the profile + * from this intent. + * + *

Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission. + * + * @param device Remote Bluetooth Device + * @return false on immediate error, true otherwise + * @hide + */ + public boolean connect(BluetoothDevice device) { + log("connect(" + device + ")"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() && isValidDevice(device)) { + return service.connect(device); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + /** + * Initiate disconnection from a profile + * + *

This API will return false in scenarios like the profile on the + * Bluetooth device is not in connected state etc. When this API returns, + * true, it is guaranteed that the connection state change + * intent will be broadcasted with the state. Users can get the + * disconnection state of the profile from this intent. + * + *

If the disconnection is initiated by a remote device, the state + * will transition from {@link #STATE_CONNECTED} to + * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the + * host (local) device the state will transition from + * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to + * state {@link #STATE_DISCONNECTED}. The transition to + * {@link #STATE_DISCONNECTING} can be used to distinguish between the + * two scenarios. + * + *

Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission. + * + * @param device Remote Bluetooth Device + * @return false on immediate error, true otherwise + * @hide + */ + public boolean disconnect(BluetoothDevice device) { + if (DBG) log("disconnect(" + device + ")"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() && isValidDevice(device)) { + return service.disconnect(device); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + /** + * {@inheritDoc} + */ + @Override + public @NonNull List getConnectedDevices() { + log("getConnectedDevices()"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled()) { + return service.getConnectedDevices(); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList(); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return new ArrayList(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public @NonNull List getDevicesMatchingConnectionStates( + @NonNull int[] states) { + log("getDevicesMatchingStates()"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled()) { + return service.getDevicesMatchingConnectionStates(states); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList(); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return new ArrayList(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public @BluetoothProfile.BtProfileState int getConnectionState( + @NonNull BluetoothDevice device) { + log("getState(" + device + ")"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.getConnectionState(device); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return BluetoothProfile.STATE_DISCONNECTED; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return BluetoothProfile.STATE_DISCONNECTED; + } + } + /** + * Get the connection policy of the profile. + * + *

The connection policy can be any of: + * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, + * {@link #CONNECTION_POLICY_UNKNOWN} + * + * @param device Bluetooth device + * @return connection policy of the device + * @hide + */ + //@SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH) + public int getConnectionPolicy(@NonNull BluetoothDevice device) { + log("getConnectionPolicy(" + device + ")"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.getConnectionPolicy(device); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + } + } + + /** + * Set connection policy of the profile + * + *

The device should already be paired. + * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, + * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} + * + * @param device Paired bluetooth device + * @param connectionPolicy is the connection policy to set to for this profile + * @return true if connectionPolicy is set, false on error + * @hide + */ + //@SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + public boolean setConnectionPolicy(@NonNull BluetoothDevice device, + int connectionPolicy) { + if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN + && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + return false; + } + return service.setConnectionPolicy(device, connectionPolicy); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + private IBleBroadcastAudioScanAssistCallback wrap(BleBroadcastAudioScanAssistCallback callback, + Handler handler) { + return new IBleBroadcastAudioScanAssistCallback.Stub() { + public void onBleBroadcastSourceFound(ScanResult scanres) { + handler.post(new Runnable() { + @Override + public void run() { + log("calling onBleBroadcastSourceFound for " + + "scanres:" + scanres); + callback.onBleBroadcastSourceFound( + scanres); + } + }); + } + + public void onBleBroadcastAudioSourceSelected(BluetoothDevice device, int status, + List broadcastSourceChannels) { + handler.post(new Runnable() { + @Override + public void run() { + log("calling onBleBroadcastSourceSelected for " + + "status:" + status); + callback.onBleBroadcastSourceSelected(device, + status, broadcastSourceChannels); + } + }); + } + public void onBleBroadcastAudioSourceAdded(BluetoothDevice rcvr, + byte srcId, + int status) { + handler.post(new Runnable() { + @Override + public void run() { + log("calling onBleBroadcastAudioSourceAdded for " + rcvr + + "srcId:" + srcId + "status:" + status); + callback.onBleBroadcastAudioSourceAdded(rcvr, srcId, + status); + } + }); + } + public void onBleBroadcastAudioSourceUpdated(BluetoothDevice rcvr, + byte srcId, + int status) { + handler.post(new Runnable() { + @Override + public void run() { + log("calling onBleBroadcastAudioSourceUpdated for " + rcvr + + "srcId:" + srcId + "status:" + status); + callback.onBleBroadcastAudioSourceUpdated(rcvr, srcId, + status); + } + }); + } + public void onBleBroadcastPinUpdated(BluetoothDevice rcvr, + byte srcId, + int status) { + handler.post(new Runnable() { + @Override + public void run() { + log("calling onBleBroadcastPinUpdated for " + rcvr + + "srcId:" + srcId + "status:" + status); + callback.onBleBroadcastPinUpdated(rcvr, srcId, + status); + // App can still unregister the sync until notified it's lost. + // Remove callback after app was notifed. + //mCallbackWrappers.remove(callback); + } + }); + } + + public void onBleBroadcastAudioSourceRemoved(BluetoothDevice rcvr, + byte srcId, + int status) { + handler.post(new Runnable() { + @Override + public void run() { + log("calling onBleBroadcastAudioSourceRemoved for " + rcvr + + "srcId:" + srcId + "status:" + status); + callback.onBleBroadcastAudioSourceRemoved(rcvr, srcId, + status); + + } + }); + } + }; + } + + + boolean startScanOffload (BluetoothDevice device, boolean isGroupOp) { + log("startScanOffload(" + device + ", isGroupOp: " + isGroupOp + ")"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.startScanOffload(device, isGroupOp); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + boolean stopScanOffload (BluetoothDevice device, boolean isGroupOp) { + log("stopScanOffload(" + device + ", isGroupOp: " + isGroupOp + ")" ); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.stopScanOffload(device, isGroupOp); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + boolean searchforLeAudioBroadcasters (BluetoothDevice device) { + log("searchforLeAudioBroadcasters(" + device + ")"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.searchforLeAudioBroadcasters(device); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + boolean stopSearchforLeAudioBroadcasters(BluetoothDevice device) { + log("stopSearchforLeAudioBroadcasters(" + device + ")"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.stopSearchforLeAudioBroadcasters(device); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + boolean selectBroadcastSource (BluetoothDevice device, ScanResult scanRes, boolean isGroupOp) { + log("selectBroadcastSource(" + device + ": groupop" + isGroupOp +")"); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.selectBroadcastSource(device, scanRes, isGroupOp); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + + void registerAppCallback(BluetoothDevice device, BleBroadcastAudioScanAssistCallback appCallback) { + log("registerAppCallback device :" + device + "appCB: " + appCallback); + Handler handler = new Handler(Looper.getMainLooper()); + + IBleBroadcastAudioScanAssistCallback wrapped = wrap(appCallback, handler); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + service.registerAppCallback(device, wrapped); + if (mAppCallbackWrappers != null) { + mAppCallbackWrappers.put(appCallback, wrapped); + } + } + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + return; + } + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return; + } + } + + void unregisterAppCallback(BluetoothDevice device, BleBroadcastAudioScanAssistCallback appCallback) { + log("unregisterAppCallback: device" + device + "appCB:" + appCallback); + // Remove callback after app was notifed. + + final IBluetoothSyncHelper service = getService(); + IBleBroadcastAudioScanAssistCallback cb = mAppCallbackWrappers.get(device); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + service.unregisterAppCallback(device, cb); + mAppCallbackWrappers.remove(appCallback); + return; + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return; + } + } + + + boolean addBroadcastSource (BluetoothDevice sinkDevice, + BleBroadcastSourceInfo srcInfo, + boolean isGroupOp) { + log("addBroadcastSource for :" + sinkDevice + + "SourceInfo: " + srcInfo+ "isGroupOp: " + isGroupOp); + boolean ret = false; + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(sinkDevice)) { + + return service.addBroadcastSource(sinkDevice, srcInfo, isGroupOp); + } + if (service == null) + { + Log.w(TAG, "Proxy not attached to service"); + ret = false; + } + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + ret = false; + } + return ret; + } + boolean updateBroadcastSource (BluetoothDevice device, + BleBroadcastSourceInfo srcInfo, + boolean isGroupOp) { + //Same device can have more than one SourceId + log("updateBroadcastSource for :" + device + + "SourceInfo: " + srcInfo+ "isGroupOp: " + isGroupOp); + boolean ret = false; + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.updateBroadcastSource(device, + srcInfo, isGroupOp); + } + if (service == null) + { + Log.w(TAG, "Proxy not attached to service"); + ret = false; + } + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + ret = false; + } + return ret; + } + + boolean setBroadcastCode (BluetoothDevice device, + BleBroadcastSourceInfo srcInfo, + boolean isGroupOp) { + //Same device can have more than one SourceId + log("setBroadcastCode for :" + device); + log("SourceInfo: " + srcInfo+ "isGroupOp: " + isGroupOp); + boolean ret = false; + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.setBroadcastCode(device, + srcInfo, isGroupOp); + } + if (service == null) + { + Log.w(TAG, "Proxy not attached to service"); + ret = false; + } + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + ret = false; + } + return ret; + } + boolean removeBroadcastSource (BluetoothDevice device, + byte sourceId, + boolean isGroupOp + ) { + log("removeBroadcastSource for :" + device + + "SourceId: " + sourceId + "isGroupOp: " + isGroupOp); + final IBluetoothSyncHelper service = getService(); + boolean ret = false; + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.removeBroadcastSource(device, sourceId + , isGroupOp); + } + if (service == null) + { + Log.w(TAG, "Proxy not attached to service"); + ret = false; + } + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + ret = false; + } + return ret; + } + + List getAllBroadcastSourceInformation (BluetoothDevice device) { + log("GetAllBroadcastReceiverStates for :" + device); + final IBluetoothSyncHelper service = getService(); + try { + if (service != null && isEnabled() + && isValidDevice(device)) { + return service.getAllBroadcastSourceInformation(device); + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return null; + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return null; + } + } + private boolean isEnabled() { + if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) return true; + return false; + } + + private boolean isValidDevice(BluetoothDevice device) { + if (device == null) return false; + + if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; + return false; + } + + private static void log(String msg) { + BleBroadcastSourceInfo.BASS_Debug(TAG, msg); + } + +} diff --git a/le_audio/frameworks/base/packages/SettingsLib/Android.bp b/le_audio/frameworks/base/packages/SettingsLib/Android.bp new file mode 100644 index 00000000000..5da2ee0863d --- /dev/null +++ b/le_audio/frameworks/base/packages/SettingsLib/Android.bp @@ -0,0 +1,4 @@ +filegroup { + name: "framework-settingslib-adva-srcs", + srcs: ["src/**/*.java"], +} diff --git a/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BCProfile.java b/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BCProfile.java new file mode 100644 index 00000000000..677186eab17 --- /dev/null +++ b/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BCProfile.java @@ -0,0 +1,301 @@ +/* + * Copyright (c) 2020 The Linux Foundation. All rights reserved. + + * Copyright (C) 2018 The Android Open Source Project + * + * 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 + */ + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothSyncHelper; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.util.Log; +import android.os.ParcelUuid; +import android.bluetooth.BleBroadcastAudioScanAssistManager; +import android.bluetooth.BleBroadcastAudioScanAssistCallback; +import android.content.Intent; +import android.bluetooth.BleBroadcastSourceInfo; + +import com.android.settingslib.R; +import android.os.SystemProperties; +import android.os.Handler; + +import androidx.annotation.Keep; +import java.util.ArrayList; +import java.util.List; + +@Keep +public class BCProfile implements LocalBluetoothProfile { + private static final String TAG = "BCProfile"; + private static boolean V = true; + + private Context mContext; + + private BluetoothSyncHelper mService; + private boolean mIsProfileReady; + + private final CachedBluetoothDeviceManager mDeviceManager; + + static final String NAME = "BCProfile"; + private final LocalBluetoothProfileManager mProfileManager; + + // Order of this profile in device profiles list + private static final int ORDINAL = 1; + + // These callbacks run on the main thread. + private final class BassclientServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + Log.d(TAG, "BassclientService connected"); + mService = (BluetoothSyncHelper) proxy; + // We just bound to the service, so refresh the UI for any connected Bassclient devices. + //List deviceList = mService.getConnectedDevices(); + mIsProfileReady=true;//BassService connected + mProfileManager.callServiceConnectedListeners(); + } + + public void onServiceDisconnected(int profile) { + Log.d(TAG, "BassclientService disconnected"); + mIsProfileReady=false; + } + } + + public boolean isProfileReady() { + return mIsProfileReady; + } + + @Override + public int getProfileId() { + return BluetoothProfile.BC_PROFILE; + } + + @Override + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + boolean isEnabled = false; + if (mService == null) { + return false; + } + if (enabled) { + Log.d(TAG, "BCProfile: " + device + ":" + enabled); + if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + isEnabled = mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED); + } + } else { + isEnabled = mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN); + } + + return isEnabled; + } + + @Override + public boolean isEnabled(BluetoothDevice device) { + if (mService == null) { + return false; + } + return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + + } + + @Override + public int getConnectionPolicy(BluetoothDevice device) { + return BluetoothProfile.CONNECTION_POLICY_ALLOWED; + } + + BCProfile(Context context, CachedBluetoothDeviceManager deviceManager, + LocalBluetoothProfileManager profileManager) { + mContext = context; + mDeviceManager = deviceManager; + mProfileManager = profileManager; + BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, + new BassclientServiceListener(), BluetoothProfile.BC_PROFILE); + } + + public boolean accessProfileEnabled() { + //return true for BASS always so that + //It shows the profile preference in device details + return true; + } + + public boolean isAutoConnectable() { + if (mService == null) return false; + Log.d(TAG, "isAutoConnectable return false"); + return false; + } + + /** + * Get Scan delegator devices matching connection states{ + * @code BluetoothProfile.STATE_CONNECTED, + * @code BluetoothProfile.STATE_CONNECTING, + * @code BluetoothProfile.STATE_DISCONNECTING} + * + * @return Matching device list + */ + public List getConnectedDevices() { + return getDevicesByStates(new int[] { + BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}); + } + + /** + * Get Scan delegator devices matching connection states{ + * @code BluetoothProfile.STATE_DISCONNECTED, + * @code BluetoothProfile.STATE_CONNECTED, + * @code BluetoothProfile.STATE_CONNECTING, + * @code BluetoothProfile.STATE_DISCONNECTING} + * + * @return Matching device list + */ + public List getConnectableDevices() { + return getDevicesByStates(new int[] { + BluetoothProfile.STATE_DISCONNECTED, + BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}); + } + + private List getDevicesByStates(int[] states) { + if (mService == null) { + return new ArrayList(0); + } + return mService.getDevicesMatchingConnectionStates(states); + } + + public boolean connect(BluetoothDevice device) { + Log.d(TAG, "BCProfile Connect to device: " + device); + if (mService == null) return false; + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + Log.d(TAG, "BCProfile disonnect to device: " + device); + if (mService == null) return false; + // Downgrade priority as user is disconnecting the Bassclient. + if (mService.getConnectionPolicy(device) > BluetoothProfile.PRIORITY_ON){ + mService.setConnectionPolicy(device, BluetoothProfile.PRIORITY_ON); + } + return mService.disconnect(device); + } + + public int getConnectionStatus(BluetoothDevice device) { + if (mService == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + return mService.getConnectionState(device); + } + + public int getPreferred(BluetoothDevice device) { + if (mService == null) return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; + return mService.getConnectionPolicy(device); + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + if (mService == null) return; + if (preferred) { + if (mService.getConnectionPolicy(device) != + BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + mService.setConnectionPolicy(device, + BluetoothProfile.CONNECTION_POLICY_ALLOWED); + } + } else { + mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_UNKNOWN); + } + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + return R.string.bluetooth_profile_bc; + } + + public BleBroadcastAudioScanAssistManager getBSAManager(BluetoothDevice device, + BleBroadcastAudioScanAssistCallback callback) { + if (mService == null) { + Log.d(TAG, "getBroadcastAudioScanAssistManager: service is null"); + return null; + } + return mService.getBleBroadcastAudioScanAssistManager(device, callback); + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = getConnectionStatus(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_bc_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_bc_profile_summary_connected; + + default: + return BluetoothUtils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + return com.android.internal.R.drawable.ic_bt_hearing_aid; + } + + protected void finalize() { + Log.d(TAG, "finalize()"); + if (mService != null) { + try { + BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.BC_PROFILE, + mService); + mService = null; + }catch (Throwable t) { + Log.w(TAG, "Error cleaning up BAss client proxy", t); + } + } + } + + static boolean isBCSupported() { + boolean isBCSupported = SystemProperties.getBoolean("persist.vendor.service.bt.bc", true); + Log.d(TAG, "BassClientProfile: isBCSupported returns " + isBCSupported); + return isBCSupported; + } + + static public boolean isBASeeker(BluetoothDevice device) { + //always send true + boolean isSeeker = SystemProperties.getBoolean("persist.vendor.service.bt.baseeker", false); + ParcelUuid[] uuids = null; + if (device != null) { + uuids = device.getUuids(); + } + ParcelUuid sd = ParcelUuid.fromString("0000184F-0000-1000-8000-00805F9B34FB"); + if (isBCSupported()) { + if (uuids != null) { + for (ParcelUuid uid : uuids) { + if (uid.equals(sd)) { + Log.d(TAG, "SD uuid present"); + isSeeker = true; + } + } + } + } + Log.d(TAG,"isBASeeker returns:" + isSeeker); + return isSeeker; + } + +} diff --git a/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastProfile.java b/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastProfile.java new file mode 100644 index 00000000000..5d26352562f --- /dev/null +++ b/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastProfile.java @@ -0,0 +1,166 @@ +/* +*Copyright (c) 2020, The Linux Foundation. All rights reserved. +* +*/ + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothBroadcast; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.util.Log; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; +import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED; +import com.android.settingslib.R; +import androidx.annotation.Keep; + +/** + * BroadcastProfile handles Bluetooth Broadcast profile. + */ +@Keep +public final class BroadcastProfile implements LocalBluetoothProfile { + private static final String TAG = "BroadcastProfile"; + private static boolean V = true; + + private BluetoothBroadcast mService; + private boolean mIsProfileReady = false; + + static final String NAME = "Broadcast"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 0; + + // These callbacks run on the main thread. + private final class BroadcastListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (profile == BluetoothProfile.BROADCAST) { + if (V) Log.d(TAG,"Bluetooth Broadcast service connected"); + mService = (BluetoothBroadcast) proxy; + mIsProfileReady = true; + } + } + + public void onServiceDisconnected(int profile) { + if (profile == BluetoothProfile.BROADCAST) { + if (V) Log.d(TAG,"Bluetooth Broadcast service disconnected"); + mIsProfileReady = false; + } + } + } + + public boolean isProfileReady() { + Log.d(TAG,"isProfileReady = " + mIsProfileReady); + return mIsProfileReady; + } + + @Override + public int getProfileId() { + Log.d(TAG,"getProfileId"); + return BluetoothProfile.BROADCAST; + } + + BroadcastProfile(Context context) { + Log.d(TAG,"BroadcastProfile constructor"); + BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, + new BroadcastListener(), BluetoothProfile.BROADCAST); + } + + public boolean accessProfileEnabled() { + Log.d(TAG,"accessProfileEnabled"); + return false; + } + + public boolean isAutoConnectable() { + return false; + } + + public boolean connect(BluetoothDevice device) { + return false; + } + + public boolean disconnect(BluetoothDevice device) { + return false; + } + + public int getConnectionStatus(BluetoothDevice device) { + return BluetoothProfile.STATE_DISCONNECTED; + } + + public boolean isEnabled(BluetoothDevice device) { + return false; + } + + public int getConnectionPolicy(BluetoothDevice device) { + return CONNECTION_POLICY_FORBIDDEN; + } + + public boolean setEnabled(BluetoothDevice device, boolean enabled) { + return false;//CONNECTION_POLICY_ALLOWED; + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + } + public int getPreferred(BluetoothDevice device) { + return BluetoothProfile.PRIORITY_OFF; + } + public boolean isPreferred(BluetoothDevice device) { + return false; + } + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + return R.string.bluetooth_profile_broadcast; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + return 0; + } + + public int getDrawableResource(BluetoothClass btClass) { + return 0; + } + + public boolean setEncryption(boolean enable, int enc_len, boolean use_existing) { + Log.d(TAG,"setEncryption"); + return mService.SetEncryption(enable, enc_len, use_existing); + } + + public byte[] getEncryptionKey() { + Log.d(TAG,"getEncryptionKey"); + return mService.GetEncryptionKey(); + } + + public int getBroadcastStatus() { + Log.d(TAG,"getBroadcastStatus"); + return mService.GetBroadcastStatus(); + } + + public boolean setBroadcastMode(boolean enable) { + Log.d(TAG,"setBroadcastMode"); + return mService.SetBroadcastMode(enable); + } + + protected void finalize() { + if (V) Log.d(TAG, "finalize()"); + if (mService != null) { + try { + BluetoothAdapter.getDefaultAdapter().closeProfileProxy + (BluetoothProfile.BROADCAST, mService); + mService = null; + } catch (Throwable t) { + Log.w(TAG, "Error cleaning up Broadcast proxy", t); + } + } + } +} diff --git a/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastSourceInfoHandler.java b/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastSourceInfoHandler.java new file mode 100644 index 00000000000..f983e5d4cc2 --- /dev/null +++ b/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastSourceInfoHandler.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * + */ + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.util.Log; +import android.bluetooth.BleBroadcastAudioScanAssistManager; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; +import android.content.Intent; +import android.bluetooth.BleBroadcastSourceInfo; +import android.os.Handler; + +public class BroadcastSourceInfoHandler implements BluetoothEventManager.Handler { + private static final String TAG = "BroadcastSourceInfoHandler"; + private static final boolean V = Log.isLoggable(TAG, Log.VERBOSE); + private final CachedBluetoothDeviceManager mDeviceManager; + BroadcastSourceInfoHandler(CachedBluetoothDeviceManager deviceManager + ) { + mDeviceManager = deviceManager; + } + @Override + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + if (device == null) { + Log.w(TAG, "BroadcastSourceInfoHandler: device is null"); + return; + } + + final String action = intent.getAction(); + if (action == null) { + Log.w(TAG, "BroadcastSourceInfoHandler: action is null"); + return; + } + BleBroadcastSourceInfo sourceInfo = intent.getParcelableExtra( + BleBroadcastSourceInfo.EXTRA_SOURCE_INFO); + + int sourceInfoIdx = intent.getIntExtra( + BleBroadcastSourceInfo.EXTRA_SOURCE_INFO_INDEX, + BluetoothAdapter.ERROR); + + int maxNumOfsrcInfo = intent.getIntExtra( + BleBroadcastSourceInfo.EXTRA_MAX_NUM_SOURCE_INFOS, + BluetoothAdapter.ERROR); + if (V) { + Log.d(TAG, "Rcved :BCAST_RECEIVER_STATE Intent for : " + device); + Log.d(TAG, "Rcvd BroadcastSourceInfo index=" + sourceInfoIdx); + Log.d(TAG, "Rcvd max num of source Info=" + maxNumOfsrcInfo); + Log.d(TAG, "Rcvd BroadcastSourceInfo=" + sourceInfo); + } + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + VendorCachedBluetoothDevice vDevice = + VendorCachedBluetoothDevice.getVendorCachedBluetoothDevice(cachedDevice, null); + if (vDevice != null) { + vDevice.onBroadcastReceiverStateChanged(sourceInfo, + sourceInfoIdx, maxNumOfsrcInfo); + cachedDevice.dispatchAttributesChanged(); + } else { + Log.e(TAG, "No vCachedDevice created for this Device"); + } + } +}; diff --git a/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/VendorCachedBluetoothDevice.java b/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/VendorCachedBluetoothDevice.java new file mode 100644 index 00000000000..f1d0afd9b9e --- /dev/null +++ b/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/VendorCachedBluetoothDevice.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHearingAid; +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.BleBroadcastSourceChannel; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BleBroadcastAudioScanAssistManager; +import android.bluetooth.BleBroadcastAudioScanAssistCallback; +import android.bluetooth.le.ScanResult; +import android.bluetooth.BluetoothUuid; +import android.os.ParcelUuid; +import android.content.Context; +import android.content.SharedPreferences; +import java.util.IdentityHashMap; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelUuid; +import android.os.SystemClock; +import java.util.HashMap; +import java.util.Map; +import java.util.List; +import android.text.TextUtils; +import android.util.EventLog; +import android.util.Log; +import java.lang.Integer; + +import android.os.SystemProperties; +import androidx.annotation.VisibleForTesting; + +import com.android.internal.util.ArrayUtils; +import com.android.settingslib.R; +import com.android.settingslib.Utils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * VendorCachedBluetoothDevice represents a remote Bluetooth device. It contains + * attributes of the device (such as the address, name, RSSI, etc.) and + * functionality that can be performed on the device (connect, pair, disconnect, + * etc.). + */ +public class VendorCachedBluetoothDevice extends CachedBluetoothDevice { + private static final String TAG = "VendorCachedBluetoothDevice"; + private static final boolean V = Log.isLoggable(TAG, Log.VERBOSE); + private ScanResult mScanRes = null; + private BleBroadcastAudioScanAssistManager mScanAssistManager; + static private Map mBleBroadcastReceiverStates + = new HashMap(); + private LocalBluetoothProfileManager mProfileManager = null; + static private Map mVcbdEntries = new IdentityHashMap(); + + public static VendorCachedBluetoothDevice getVendorCachedBluetoothDevice( + CachedBluetoothDevice cachedDevice, + LocalBluetoothProfileManager profileManager) { + VendorCachedBluetoothDevice vCbd = null; + if (mVcbdEntries != null) { + vCbd = mVcbdEntries.get(cachedDevice); + } + //dont create new instance if profileMgr is null + if (vCbd == null && profileManager != null) { + vCbd = new VendorCachedBluetoothDevice(cachedDevice, + profileManager); + Log.d(TAG, "getVendorCachedBluetoothDevice: created new Instance"); + mVcbdEntries.put(cachedDevice, vCbd); + } + return vCbd; + } + + VendorCachedBluetoothDevice(CachedBluetoothDevice cachedDevice,LocalBluetoothProfileManager profileManager) { + super(cachedDevice); + mProfileManager = profileManager; + mBleBroadcastReceiverStates = new HashMap(); + InitializeSAManager(); + } + + VendorCachedBluetoothDevice(Context context, LocalBluetoothProfileManager profileManager, + BluetoothDevice device) { + super(context, profileManager, device); + mProfileManager = profileManager; + mBleBroadcastReceiverStates = new HashMap(); + InitializeSAManager(); + } + + /** + * Describes the current device and profile for logging. + * + * @param profile Profile to describe + * @return Description of the device and profile + */ + private String describe(LocalBluetoothProfile profile) { + StringBuilder sb = new StringBuilder(); + sb.append("Address:").append(mDevice); + if (profile != null) { + sb.append(" Profile:").append(profile); + } + + return sb.toString(); + } + + void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) { + if (V) { + Log.d(TAG, "onProfileStateChanged: profile " + profile + ", device=" + mDevice + + ", newProfileState " + newProfileState); + } + if (profile instanceof BCProfile + && newProfileState == BluetoothProfile.STATE_DISCONNECTED) { + cleanUpSAMananger(); + super.dispatchAttributesChanged(); + } + } + + private BleBroadcastAudioScanAssistCallback mScanAssistCallback = new BleBroadcastAudioScanAssistCallback() { + public void onBleBroadcastSourceFound(ScanResult res) { + if (V) { + Log.d(TAG, "onBleBroadcastSourceFound" + res.getDevice()); + } + setScanResult(res); + }; + + public void onBleBroadcastAudioSourceAdded(BluetoothDevice rcvr, + byte srcId, + int status) { + }; + + public void onBleBroadcastSourceSelected( int status, + List broadcastSourceIndicies) { + }; + + public void onBleBroadcastAudioSourceUpdated(BluetoothDevice rcvr, + byte srcId, + int status) { + }; + + public void onBleBroadcastPinUpdated(BluetoothDevice rcvr, + byte srcId, + int status) { + }; + public void onBleBroadcastAudioSourceRemoved(BluetoothDevice rcvr, + byte srcId, + int status) { + }; + }; + + public BleBroadcastAudioScanAssistManager getScanAssistManager() + { InitializeSAManager(); + return mScanAssistManager; + } + + void InitializeSAManager() { + BCProfile bcProfile = (BCProfile)mProfileManager.getBCProfile(); + mScanAssistManager = bcProfile.getBSAManager( + mDevice, mScanAssistCallback); + } + + void cleanUpSAMananger() { + mScanAssistManager = null; + if (mBleBroadcastReceiverStates != null) { + mBleBroadcastReceiverStates.clear(); + } + } + + void updateBroadcastreceiverStates(BleBroadcastSourceInfo srcInfo, int index, + int maxSourceInfosNum) { + BleBroadcastSourceInfo entry = mBleBroadcastReceiverStates.get(index); + if (entry != null) { + Log.d(TAG, "updateBroadcastreceiverStates: Replacing receiver State Information"); + mBleBroadcastReceiverStates.replace(index, srcInfo); + } else { + mBleBroadcastReceiverStates.put(index, srcInfo); + } + super.dispatchAttributesChanged(); + } + + public int getNumberOfBleBroadcastReceiverStates() { + int ret = 0; + if (mScanAssistManager == null) { + InitializeSAManager(); + if (mScanAssistManager == null) { + return ret; + } + } + List srcInfo = mScanAssistManager.getAllBroadcastSourceInformation(); + if (srcInfo != null) { + ret = srcInfo.size(); + } + if (V) { + Log.d(TAG, "getNumberOfBleBroadcastReceiverStates:"+ ret); + } + return ret; + } + + public Map getAllBleBroadcastreceiverStates() { + if (mScanAssistManager == null) { + InitializeSAManager(); + if (mScanAssistManager == null) { + Log.e(TAG, "SA Manager cant be initialized"); + return null; + } + } + List srcInfos = mScanAssistManager.getAllBroadcastSourceInformation(); + if (srcInfos == null) { + Log.e(TAG, "getAllBleBroadcastreceiverStates: no src Info"); + return null; + } + for (int i=0; i srcInfos = mScanAssistManager.getAllBroadcastSourceInformation(); + if (srcInfos == null) { + Log.e(TAG, "isBroadcastAudioSynced: no src Info"); + return false; + } + for (int i=0; i +#include + +using bluetooth::bap::pacs::CodecIndex; +using bluetooth::bap::pacs::CodecPriority; +using bluetooth::bap::pacs::CodecSampleRate; +using bluetooth::bap::pacs::CodecBPS; +using bluetooth::bap::pacs::CodecChannelMode; + +namespace android { +static jmethodID method_onConnectionStateChanged; +static jmethodID method_onAudioStateChanged; +static jmethodID method_onCodecConfigChanged; + +static struct { + jclass clazz; + jmethodID constructor; + jmethodID getCodecType; + jmethodID getCodecPriority; + jmethodID getSampleRate; + jmethodID getBitsPerSample; + jmethodID getChannelMode; + jmethodID getCodecSpecific1; + jmethodID getCodecSpecific2; + jmethodID getCodecSpecific3; + jmethodID getCodecSpecific4; +} android_bluetooth_BluetoothCodecConfig; + +static const btacm_initiator_interface_t* sBluetoothAcmInterface = nullptr; +static std::shared_timed_mutex interface_mutex; + +static jobject mCallbacksObj = nullptr; +static std::shared_timed_mutex callbacks_mutex; + +static void btacm_connection_state_callback(const RawAddress& bd_addr, + btacm_connection_state_t state, uint16_t contextType) { + ALOGI("%s", __func__); + + 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()) { + ALOGE("%s: Fail to new jbyteArray bd addr", __func__); + return; + } + + sCallbackEnv->SetByteArrayRegion( + addr.get(), 0, sizeof(RawAddress), + reinterpret_cast(bd_addr.address)); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged, + addr.get(), (jint)state, (jint)contextType); +} + +static void btacm_audio_state_callback(const RawAddress& bd_addr, + btacm_audio_state_t state, uint16_t contextType) { + ALOGI("%s", __func__); + + 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()) { + ALOGE("%s: Fail to new jbyteArray bd addr", __func__); + return; + } + + sCallbackEnv->SetByteArrayRegion( + addr.get(), 0, sizeof(RawAddress), + reinterpret_cast(bd_addr.address)); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioStateChanged, + addr.get(), (jint)state, (jint)contextType); +} + +static void btacm_audio_config_callback( + const RawAddress& bd_addr, CodecConfig codec_config, + std::vector codecs_local_capabilities, + std::vector codecs_selectable_capabilities, uint16_t contextType) { + ALOGI("%s", __func__); + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + jobject codecConfigObj = sCallbackEnv->NewObject( + android_bluetooth_BluetoothCodecConfig.clazz, + android_bluetooth_BluetoothCodecConfig.constructor, + (jint)codec_config.codec_type, (jint)codec_config.codec_priority, + (jint)codec_config.sample_rate, (jint)codec_config.bits_per_sample, + (jint)codec_config.channel_mode, (jlong)codec_config.codec_specific_1, + (jlong)codec_config.codec_specific_2, + (jlong)codec_config.codec_specific_3, + (jlong)codec_config.codec_specific_4); + + jsize i = 0; + jobjectArray local_capabilities_array = sCallbackEnv->NewObjectArray( + (jsize)codecs_local_capabilities.size(), + android_bluetooth_BluetoothCodecConfig.clazz, nullptr); + for (auto const& cap : codecs_local_capabilities) { + jobject capObj = sCallbackEnv->NewObject( + android_bluetooth_BluetoothCodecConfig.clazz, + android_bluetooth_BluetoothCodecConfig.constructor, + (jint)cap.codec_type, (jint)cap.codec_priority, (jint)cap.sample_rate, + (jint)cap.bits_per_sample, (jint)cap.channel_mode, + (jlong)cap.codec_specific_1, (jlong)cap.codec_specific_2, + (jlong)cap.codec_specific_3, (jlong)cap.codec_specific_4); + sCallbackEnv->SetObjectArrayElement(local_capabilities_array, i++, capObj); + sCallbackEnv->DeleteLocalRef(capObj); + } + + i = 0; + jobjectArray selectable_capabilities_array = sCallbackEnv->NewObjectArray( + (jsize)codecs_selectable_capabilities.size(), + android_bluetooth_BluetoothCodecConfig.clazz, nullptr); + for (auto const& cap : codecs_selectable_capabilities) { + jobject capObj = sCallbackEnv->NewObject( + android_bluetooth_BluetoothCodecConfig.clazz, + android_bluetooth_BluetoothCodecConfig.constructor, + (jint)cap.codec_type, (jint)cap.codec_priority, (jint)cap.sample_rate, + (jint)cap.bits_per_sample, (jint)cap.channel_mode, + (jlong)cap.codec_specific_1, (jlong)cap.codec_specific_2, + (jlong)cap.codec_specific_3, (jlong)cap.codec_specific_4); + sCallbackEnv->SetObjectArrayElement(selectable_capabilities_array, i++, + capObj); + sCallbackEnv->DeleteLocalRef(capObj); + } + + ScopedLocalRef addr( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(RawAddress::kLength)); + if (!addr.get()) { + ALOGE("%s: Fail to new jbyteArray bd addr", __func__); + return; + } + sCallbackEnv->SetByteArrayRegion( + addr.get(), 0, RawAddress::kLength, + reinterpret_cast(bd_addr.address)); + + sCallbackEnv->CallVoidMethod( + mCallbacksObj, method_onCodecConfigChanged, addr.get(), codecConfigObj, + local_capabilities_array, selectable_capabilities_array, (jint)contextType); +} + +static btacm_initiator_callbacks_t sBluetoothAcmCallbacks = { + sizeof(sBluetoothAcmCallbacks), + btacm_connection_state_callback, + btacm_audio_state_callback, + btacm_audio_config_callback +}; + +static void classInitNative(JNIEnv* env, jclass clazz) { + jclass jniBluetoothCodecConfigClass = + env->FindClass("android/bluetooth/BluetoothCodecConfig"); + android_bluetooth_BluetoothCodecConfig.constructor = + env->GetMethodID(jniBluetoothCodecConfigClass, "", "(IIIIIJJJJ)V"); + android_bluetooth_BluetoothCodecConfig.getCodecType = + env->GetMethodID(jniBluetoothCodecConfigClass, "getCodecType", "()I"); + android_bluetooth_BluetoothCodecConfig.getCodecPriority = + env->GetMethodID(jniBluetoothCodecConfigClass, "getCodecPriority", "()I"); + android_bluetooth_BluetoothCodecConfig.getSampleRate = + env->GetMethodID(jniBluetoothCodecConfigClass, "getSampleRate", "()I"); + android_bluetooth_BluetoothCodecConfig.getBitsPerSample = + env->GetMethodID(jniBluetoothCodecConfigClass, "getBitsPerSample", "()I"); + android_bluetooth_BluetoothCodecConfig.getChannelMode = + env->GetMethodID(jniBluetoothCodecConfigClass, "getChannelMode", "()I"); + android_bluetooth_BluetoothCodecConfig.getCodecSpecific1 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific1", "()J"); + android_bluetooth_BluetoothCodecConfig.getCodecSpecific2 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific2", "()J"); + android_bluetooth_BluetoothCodecConfig.getCodecSpecific3 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific3", "()J"); + android_bluetooth_BluetoothCodecConfig.getCodecSpecific4 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific4", "()J"); + + method_onConnectionStateChanged = + env->GetMethodID(clazz, "onConnectionStateChanged", "([BII)V"); + + method_onAudioStateChanged = + env->GetMethodID(clazz, "onAudioStateChanged", "([BII)V"); + + method_onCodecConfigChanged = + env->GetMethodID(clazz, "onCodecConfigChanged", + "([BLandroid/bluetooth/BluetoothCodecConfig;" + "[Landroid/bluetooth/BluetoothCodecConfig;" + "[Landroid/bluetooth/BluetoothCodecConfig;I)V"); + + ALOGI("%s: succeeds", __func__); +} + +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_BluetoothCodecConfig.clazz)) { + ALOGE("%s: Invalid BluetoothCodecConfig instance", __func__); + continue; + } + jint codecType = env->CallIntMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecType); + jint codecPriority = env->CallIntMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecPriority); + jint sampleRate = env->CallIntMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getSampleRate); + jint bitsPerSample = env->CallIntMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getBitsPerSample); + jint channelMode = env->CallIntMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getChannelMode); + jlong codecSpecific1 = env->CallLongMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecSpecific1); + jlong codecSpecific2 = env->CallLongMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecSpecific2); + jlong codecSpecific3 = env->CallLongMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecSpecific3); + jlong codecSpecific4 = env->CallLongMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecSpecific4); + + CodecConfig codec_config = { + .codec_type = static_cast(codecType), + .codec_priority = + static_cast(codecPriority), + .sample_rate = static_cast(sampleRate), + .bits_per_sample = + static_cast(bitsPerSample), + .channel_mode = + static_cast(channelMode), + .codec_specific_1 = codecSpecific1, + .codec_specific_2 = codecSpecific2, + .codec_specific_3 = codecSpecific3, + .codec_specific_4 = codecSpecific4}; + + codec_preferences.push_back(codec_config); + } + return codec_preferences; +} + +static void initNative(JNIEnv* env, jobject object, + jint maxConnectedAudioDevices, + jobjectArray codecConfigArray) { + std::unique_lock interface_lock(interface_mutex); + std::unique_lock callbacks_lock(callbacks_mutex); + + const bt_interface_t* btInf = getBluetoothInterface(); + if (btInf == nullptr) { + ALOGE("%s: Bluetooth module is not loaded", __func__); + return; + } + + if (sBluetoothAcmInterface != nullptr) { + ALOGW("%s: Cleaning up ACM Interface before initializing...", __func__); + sBluetoothAcmInterface->cleanup(); + sBluetoothAcmInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + ALOGW("%s: Cleaning up ACM callback object", __func__); + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } + + if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) { + ALOGE("%s: Failed to allocate Global Ref for ACM Callbacks", __func__); + return; + } + + android_bluetooth_BluetoothCodecConfig.clazz = (jclass)env->NewGlobalRef( + env->FindClass("android/bluetooth/BluetoothCodecConfig")); + if (android_bluetooth_BluetoothCodecConfig.clazz == nullptr) { + ALOGE("%s: Failed to allocate Global Ref for BluetoothCodecConfig class", + __func__); + return; + } + + sBluetoothAcmInterface = + (btacm_initiator_interface_t*)btInf->get_profile_interface( + BT_PROFILE_ACM_ID); + if (sBluetoothAcmInterface == nullptr) { + ALOGE("%s: Failed to get Bluetooth ACM Interface", __func__); + return; + } + + std::vector codec_priorities = + prepareCodecPreferences(env, object, codecConfigArray); + + bt_status_t status = sBluetoothAcmInterface->init( + &sBluetoothAcmCallbacks, maxConnectedAudioDevices, codec_priorities); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed to initialize Bluetooth ACM, status: %d", __func__, + status); + sBluetoothAcmInterface = nullptr; + return; + } +} + +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) { + ALOGE("%s: Bluetooth module is not loaded", __func__); + return; + } + + if (sBluetoothAcmInterface != nullptr) { + sBluetoothAcmInterface->cleanup(); + sBluetoothAcmInterface = nullptr; + } + + env->DeleteGlobalRef(android_bluetooth_BluetoothCodecConfig.clazz); + android_bluetooth_BluetoothCodecConfig.clazz = nullptr; + + if (mCallbacksObj != nullptr) { + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } +} + +static jboolean connectAcmNative(JNIEnv* env, jobject object, + jbyteArray address, jint contextType, + jint profileType, jint preferredContext) { + ALOGI("%s: sBluetoothAcmInterface: %p", __func__, sBluetoothAcmInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothAcmInterface) { + ALOGE("%s: Failed to get the Bluetooth ACM Interface", __func__); + return JNI_FALSE; + } + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress bd_addr; + bd_addr.FromOctets(reinterpret_cast(addr)); + bt_status_t status = sBluetoothAcmInterface->connect(bd_addr, contextType, + profileType, preferredContext); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed ACM connection, status: %d", __func__, status); + } + env->ReleaseByteArrayElements(address, addr, 0); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean disconnectAcmNative(JNIEnv* env, jobject object, + jbyteArray address, jint contextType) { + ALOGI("%s: sBluetoothAcmInterface: %p", __func__, sBluetoothAcmInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothAcmInterface) { + ALOGE("%s: Failed to get the Bluetooth ACM Interface", __func__); + return JNI_FALSE; + } + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress bd_addr; + bd_addr.FromOctets(reinterpret_cast(addr)); + bt_status_t status = sBluetoothAcmInterface->disconnect(bd_addr, contextType); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed ACM disconnection, status: %d", __func__, status); + } + env->ReleaseByteArrayElements(address, addr, 0); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean startStreamNative(JNIEnv* env, jobject object, + jbyteArray address, jint contextType) { + ALOGI("%s: sBluetoothAcmInterface: %p", __func__, sBluetoothAcmInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothAcmInterface) { + ALOGE("%s: Failed to get the Bluetooth ACM Interface", __func__); + return JNI_FALSE; + } + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + + RawAddress bd_addr = RawAddress::kEmpty; + if (addr) { + bd_addr.FromOctets(reinterpret_cast(addr)); + } + bt_status_t status = sBluetoothAcmInterface->start_stream(bd_addr, contextType); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed ACM set_active_device, status: %d", __func__, status); + } + env->ReleaseByteArrayElements(address, addr, 0); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean stopStreamNative(JNIEnv* env, jobject object, + jbyteArray address, jint contextType) { + ALOGI("%s: sBluetoothAcmInterface: %p", __func__, sBluetoothAcmInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothAcmInterface) { + ALOGE("%s: Failed to get the Bluetooth ACM Interface", __func__); + return JNI_FALSE; + } + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + + RawAddress bd_addr = RawAddress::kEmpty; + if (addr) { + bd_addr.FromOctets(reinterpret_cast(addr)); + } + bt_status_t status = sBluetoothAcmInterface->stop_stream(bd_addr, contextType); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed ACM set_active_device, status: %d", __func__, status); + } + env->ReleaseByteArrayElements(address, addr, 0); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean setActiveDeviceNative(JNIEnv* env, jobject object, + jbyteArray address, jint contextType) { + ALOGI("%s: sBluetoothAcmInterface: %p", __func__, sBluetoothAcmInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothAcmInterface) { + ALOGE("%s: Failed to get the Bluetooth ACM Interface", __func__); + return JNI_FALSE; + } + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + + RawAddress bd_addr = RawAddress::kEmpty; + if (addr) { + bd_addr.FromOctets(reinterpret_cast(addr)); + } + bt_status_t status = sBluetoothAcmInterface->set_active_device(bd_addr, contextType); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed ACM set_active_device, status: %d", __func__, status); + } + env->ReleaseByteArrayElements(address, addr, 0); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean setCodecConfigPreferenceNative(JNIEnv* env, jobject object, + jbyteArray address, + jobjectArray codecConfigArray, + jint contextType, jint preferredContext) { + ALOGI("%s: sBluetoothAcmInterface: %p", __func__, sBluetoothAcmInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothAcmInterface) { + ALOGE("%s: Failed to get the Bluetooth ACM Interface", __func__); + return JNI_FALSE; + } + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress bd_addr; + bd_addr.FromOctets(reinterpret_cast(addr)); + std::vector codec_preferences = + prepareCodecPreferences(env, object, codecConfigArray); + + bt_status_t status = + sBluetoothAcmInterface->config_codec(bd_addr, codec_preferences, contextType, preferredContext); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed codec configuration, status: %d", __func__, status); + } + env->ReleaseByteArrayElements(address, addr, 0); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean ChangeCodecConfigPreferenceNative(JNIEnv* env, jobject object, + jbyteArray address, + jstring message) { + ALOGI("%s: sBluetoothAcmInterface: %p", __func__, sBluetoothAcmInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothAcmInterface) { + ALOGE("%s: Failed to get the Bluetooth ACM Interface", __func__); + return JNI_FALSE; + } + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress bd_addr; + bd_addr.FromOctets(reinterpret_cast(addr)); + const char* c_msg = env->GetStringUTFChars(message, NULL); + bt_status_t status = + sBluetoothAcmInterface->change_config_codec(bd_addr, (char*)c_msg); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed codec configuration, status: %d", __func__, status); + } + env->ReleaseByteArrayElements(address, addr, 0); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static JNINativeMethod sMethods[] = { + {"classInitNative", "()V", (void*)classInitNative}, + {"initNative", "(I[Landroid/bluetooth/BluetoothCodecConfig;)V", + (void*)initNative}, + {"cleanupNative", "()V", (void*)cleanupNative}, + {"connectAcmNative", "([BIII)Z", (void*)connectAcmNative}, + {"disconnectAcmNative", "([BI)Z", (void*)disconnectAcmNative}, + {"startStreamNative", "([BI)Z", (void*)startStreamNative}, + {"stopStreamNative", "([BI)Z", (void*)stopStreamNative}, + {"setActiveDeviceNative", "([BI)Z", (void*)setActiveDeviceNative}, + {"setCodecConfigPreferenceNative", + "([B[Landroid/bluetooth/BluetoothCodecConfig;II)Z", + (void*)setCodecConfigPreferenceNative}, + {"ChangeCodecConfigPreferenceNative", + "([BLjava/lang/String;)Z", + (void*)ChangeCodecConfigPreferenceNative}, +}; + +int register_com_android_bluetooth_acm(JNIEnv* env) { + return jniRegisterNativeMethods( + env, "com/android/bluetooth/acm/AcmNativeInterface", sMethods, + NELEM(sMethods)); +} +} diff --git a/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_apm.cpp b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_apm.cpp new file mode 100644 index 00000000000..3f61996b6fa --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_apm.cpp @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + **************************************************************************/ + +#define LOG_TAG "BluetoothAPM_Jni" + +#define LOG_NDEBUG 0 + +#include "android_runtime/AndroidRuntime.h" +#include "com_android_bluetooth.h" +#include "hardware/bt_apm.h" +#include "utils/Log.h" + +#include +#include + +namespace android { +static jmethodID method_onGetActiveprofileCallback; + +static const bt_apm_interface_t* sBluetoothApmInterface = nullptr; +static std::shared_timed_mutex interface_mutex; + +static jobject mCallbacksObj = nullptr; +static std::shared_timed_mutex callbacks_mutex; + + +static int btapm_active_profile_callback(const RawAddress& bd_addr, uint16_t audio_type) +{ + ALOGI("%s", __func__); + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return -1; + + ScopedLocalRef addr( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress))); + if (!addr.get()) { + ALOGE("%s: Fail to new jbyteArray bd addr", __func__); + return -1; + } + + sCallbackEnv->SetByteArrayRegion( + addr.get(), 0, sizeof(RawAddress), + reinterpret_cast(bd_addr.address)); + return sCallbackEnv->CallIntMethod(mCallbacksObj, method_onGetActiveprofileCallback, + addr.get(), (jint)audio_type); +} + + +static btapm_initiator_callbacks_t sBluetoothApmCallbacks = { + sizeof(sBluetoothApmCallbacks), + btapm_active_profile_callback +}; + +static void classInitNative(JNIEnv* env, jclass clazz) { + + ALOGI("%s: succeeds", __func__); + method_onGetActiveprofileCallback = + env->GetMethodID(clazz, "getActiveProfile", "([BI)I"); +} + +static bool initNative(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) { + ALOGE("%s: Bluetooth module is not loaded", __func__); + return JNI_FALSE; + } + + if (sBluetoothApmInterface != nullptr) { + ALOGW("%s: Cleaning up APM Interface before initializing...", __func__); + sBluetoothApmInterface->cleanup(); + sBluetoothApmInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + ALOGW("%s: Cleaning up APM callback object", __func__); + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } + + if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) { + ALOGE("%s: Failed to allocate Global Ref for APM Callbacks", __func__); + return JNI_FALSE; + } + + sBluetoothApmInterface = + (bt_apm_interface_t*)btInf->get_profile_interface( + BT_APM_MODULE_ID); + if (sBluetoothApmInterface == nullptr) { + ALOGE("%s: Failed to get Bluetooth APM Interface", __func__); + return JNI_FALSE; + } + bt_status_t status = sBluetoothApmInterface->init(&sBluetoothApmCallbacks); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed to initialize Bluetooth APM, status: %d", __func__, + status); + sBluetoothApmInterface = nullptr; + return JNI_FALSE; + } + return JNI_TRUE; +} + +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) { + ALOGE("%s: Bluetooth module is not loaded", __func__); + return; + } + + if (sBluetoothApmInterface != nullptr) { + sBluetoothApmInterface->cleanup(); + sBluetoothApmInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } +} + +static jboolean activeDeviceUpdateNative(JNIEnv* env, jobject object, + jbyteArray address, jint profile, jint audio_type) { + ALOGI("%s: sBluetoothApmInterface: %p", __func__, sBluetoothApmInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothApmInterface) { + ALOGE("%s: Failed to get the Bluetooth APM Interface", __func__); + return JNI_FALSE; + } + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + + RawAddress bd_addr = RawAddress::kEmpty; + if (addr) { + bd_addr.FromOctets(reinterpret_cast(addr)); + } + bt_status_t status = sBluetoothApmInterface->active_device_change(bd_addr, profile, audio_type); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed APM active_device_change, status: %d", __func__, status); + } + env->ReleaseByteArrayElements(address, addr, 0); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean setContentControlNative(JNIEnv* env, jobject object, + jint content_control_id, jint profile) { + ALOGI("%s: sBluetoothApmInterface: %p", __func__, sBluetoothApmInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothApmInterface) { + ALOGE("%s: Failed to get the Bluetooth APM Interface", __func__); + return JNI_FALSE; + } + + bt_status_t status = sBluetoothApmInterface->set_content_control_id(content_control_id, profile); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed APM content control update, status: %d", __func__, status); + } + + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static JNINativeMethod sMethods[] = { + {"classInitNative", "()V", (void*)classInitNative}, + {"initNative", "()V", (void*)initNative}, + {"cleanupNative", "()V", (void*)cleanupNative}, + {"activeDeviceUpdateNative", "([BII)Z", (void*)activeDeviceUpdateNative}, + {"setContentControlNative", "(II)Z", (void*)setContentControlNative}, +}; + +int register_com_android_bluetooth_apm(JNIEnv* env) { + return jniRegisterNativeMethods( + env, "com/android/bluetooth/apm/ApmNativeInterface", sMethods, + NELEM(sMethods)); +} +} diff --git a/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_broadcast.cpp b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_broadcast.cpp new file mode 100644 index 00000000000..9cfd120505a --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_broadcast.cpp @@ -0,0 +1,456 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#define LOG_TAG "BluetoothBapBroadcastServiceJni" + +#define LOG_NDEBUG 0 + +#include "android_runtime/AndroidRuntime.h" +#include "com_android_bluetooth.h" +#include "hardware/bt_av.h" +#include "hardware/bt_bap_ba.h" +#include "utils/Log.h" + +#include +#include + +namespace android { +static jmethodID method_onBroadcastStateChanged; +static jmethodID method_onAudioStateChanged; +static jmethodID method_onCodecConfigChanged; +//static jmethodID method_onIsoDataPathChanged; +static jmethodID method_onEncryptionKeyGenerated; +static jmethodID method_onSetupBIG; +static jmethodID method_onBroadcastIdGenerated; +static struct { + jclass clazz; + jmethodID constructor; + jmethodID getCodecType; + jmethodID getCodecPriority; + jmethodID getSampleRate; + jmethodID getBitsPerSample; + jmethodID getChannelMode; + jmethodID getCodecSpecific1; + jmethodID getCodecSpecific2; + jmethodID getCodecSpecific3; + jmethodID getCodecSpecific4; +} android_bluetooth_BluetoothCodecConfig; + +static const btbap_broadcast_interface_t* sBluetoothBapBroadcastInterface = nullptr; +static std::shared_timed_mutex interface_mutex; + +static jobject mCallbacksObj = nullptr; +static std::shared_timed_mutex callbacks_mutex; + +static void btbap_broadcast_state_callback(jint adv_id, btbap_broadcast_state_t state) { + ALOGI("%s", __func__); + + std::shared_lock lock(callbacks_mutex); + ALOGI("%s: lock acquired", __func__); + CallbackEnv sCallbackEnv(__func__); + ALOGI("%s:got callback env", __func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) { + ALOGI("%s:either callback is not valid or callbackobj is null", __func__); + return; + } + ALOGI("%s: calling method to native interface", __func__); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onBroadcastStateChanged, adv_id, (jint)state); +} + +static void btbap_broadcast_audio_state_callback(jint big_handle, btbap_broadcast_audio_state_t state) { + ALOGI("%s", __func__); + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioStateChanged, big_handle, (jint)state); +} + +static void btbap_broadcast_audio_config_callback(jint adv_id, btav_a2dp_codec_config_t codec_config, + std::vector codec_capabilities) { + ALOGI("%s", __func__); + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + jobject codecConfigObj = sCallbackEnv->NewObject( + android_bluetooth_BluetoothCodecConfig.clazz, + android_bluetooth_BluetoothCodecConfig.constructor, + (jint)codec_config.codec_type, (jint)codec_config.codec_priority, + (jint)codec_config.sample_rate, (jint)codec_config.bits_per_sample, + (jint)codec_config.channel_mode, (jlong)codec_config.codec_specific_1, + (jlong)codec_config.codec_specific_2, + (jlong)codec_config.codec_specific_3, + (jlong)codec_config.codec_specific_4); + + jsize i = 0; + jobjectArray local_capabilities_array = sCallbackEnv->NewObjectArray( + (jsize)codec_capabilities.size(), + android_bluetooth_BluetoothCodecConfig.clazz, nullptr); + for (auto const& cap : codec_capabilities) { + jobject capObj = sCallbackEnv->NewObject( + android_bluetooth_BluetoothCodecConfig.clazz, + android_bluetooth_BluetoothCodecConfig.constructor, + (jint)cap.codec_type, (jint)cap.codec_priority, (jint)cap.sample_rate, + (jint)cap.bits_per_sample, (jint)cap.channel_mode, + (jlong)cap.codec_specific_1, (jlong)cap.codec_specific_2, + (jlong)cap.codec_specific_3, (jlong)cap.codec_specific_4); + sCallbackEnv->SetObjectArrayElement(local_capabilities_array, i++, capObj); + sCallbackEnv->DeleteLocalRef(capObj); + } + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onCodecConfigChanged, + adv_id, codecConfigObj, local_capabilities_array); +} + +/*static void btbap_broadcast_iso_datapath_callback(jint big_handle, jint state) { + ALOGI("%s", __func__); + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onIsoDataPathChanged, big_handle, state); +}*/ + +static void btbap_broadcast_enckey_callback(std::string pin) { + ALOGI("%s", __func__); + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + /*ScopedLocalRef pinkey( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(pin))); + if (!addr.get()) { + ALOGE("%s: Fail to new jbyteArray bd addr", __func__); + return; + } + + sCallbackEnv->SetByteArrayRegion( + pinkey.get(), 0, sizeof(pin), + reinterpret_cast(pin));*/ + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onEncryptionKeyGenerated, + sCallbackEnv->NewStringUTF(pin.c_str())); +} + +static void btbap_broadcast_setup_big_callback(jint setup, jint adv_id, jint big_handle, + jint num_bises, std::vector bis_handles) { + ALOGI("%s", __func__); + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + ScopedLocalRef jc(sCallbackEnv.get(), sCallbackEnv->NewCharArray(bis_handles.size())); + sCallbackEnv->SetCharArrayRegion(jc.get(), 0, bis_handles.size(), (jchar*) bis_handles.data()); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSetupBIG, setup, adv_id, big_handle, num_bises, jc.get()); +} + +static void btbap_broadcast_bid_callback(std::vector broadcast_id) { + ALOGI("%s", __func__); + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + ALOGI("%s: broadcast_id size = %d",__func__,broadcast_id.size()); + ScopedLocalRef jb(sCallbackEnv.get(), sCallbackEnv->NewByteArray(broadcast_id.size())); + if (!jb.get()) { + ALOGI("%s:Failed to allocate byte array"); + return; + } + sCallbackEnv->SetByteArrayRegion(jb.get(), 0, broadcast_id.size(), (jbyte*) broadcast_id.data()); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onBroadcastIdGenerated, jb.get()); +} + +static btbap_broadcast_callbacks_t sBluetoothBapBroadcastCallbacks = { + sizeof(sBluetoothBapBroadcastCallbacks), + btbap_broadcast_state_callback, + btbap_broadcast_audio_state_callback, + btbap_broadcast_audio_config_callback, + //btbap_broadcast_iso_datapath_callback, + btbap_broadcast_enckey_callback, + btbap_broadcast_setup_big_callback, + btbap_broadcast_bid_callback, +}; + +static void classInitNative(JNIEnv* env, jclass clazz) { + jclass jniBluetoothCodecConfigClass = + env->FindClass("android/bluetooth/BluetoothCodecConfig"); + android_bluetooth_BluetoothCodecConfig.constructor = + env->GetMethodID(jniBluetoothCodecConfigClass, "", "(IIIIIJJJJ)V"); + android_bluetooth_BluetoothCodecConfig.getCodecType = + env->GetMethodID(jniBluetoothCodecConfigClass, "getCodecType", "()I"); + android_bluetooth_BluetoothCodecConfig.getCodecPriority = + env->GetMethodID(jniBluetoothCodecConfigClass, "getCodecPriority", "()I"); + android_bluetooth_BluetoothCodecConfig.getSampleRate = + env->GetMethodID(jniBluetoothCodecConfigClass, "getSampleRate", "()I"); + android_bluetooth_BluetoothCodecConfig.getBitsPerSample = + env->GetMethodID(jniBluetoothCodecConfigClass, "getBitsPerSample", "()I"); + android_bluetooth_BluetoothCodecConfig.getChannelMode = + env->GetMethodID(jniBluetoothCodecConfigClass, "getChannelMode", "()I"); + android_bluetooth_BluetoothCodecConfig.getCodecSpecific1 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific1", "()J"); + android_bluetooth_BluetoothCodecConfig.getCodecSpecific2 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific2", "()J"); + android_bluetooth_BluetoothCodecConfig.getCodecSpecific3 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific3", "()J"); + android_bluetooth_BluetoothCodecConfig.getCodecSpecific4 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific4", "()J"); + + method_onBroadcastStateChanged = + env->GetMethodID(clazz, "onBroadcastStateChanged", "(II)V"); + + method_onAudioStateChanged = + env->GetMethodID(clazz, "onAudioStateChanged", "(II)V"); + + method_onCodecConfigChanged = + env->GetMethodID(clazz, "onCodecConfigChanged", + "(ILandroid/bluetooth/BluetoothCodecConfig;" + "[Landroid/bluetooth/BluetoothCodecConfig;)V"); + +// method_onIsoDataPathChanged = +// env->GetMethodID(clazz, "onIsoDataPathChanged","(II)V"); + method_onEncryptionKeyGenerated = + env->GetMethodID(clazz, "onEncryptionKeyGenerated", "(Ljava/lang/String;)V"); + + method_onSetupBIG = env->GetMethodID(clazz, "onSetupBIG", "(IIII[C)V"); + method_onBroadcastIdGenerated = env->GetMethodID(clazz, "onBroadcastIdGenerated", "([B)V"); + + ALOGI("%s: succeeds", __func__); +} +static btav_a2dp_codec_config_t prepare_codec_config( + JNIEnv* env, jobject object,jobject jcodecConfig) { + + /*if (!env->IsInstanceOf(jcodecConfig, + android_bluetooth_BluetoothCodecConfig.clazz)) { + ALOGE("%s: Invalid BluetoothCodecConfig instance", __func__); + return ((btav_a2dp_codec_config_t)NULL); + }*/ + jint codecType = env->CallIntMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecType); + jint codecPriority = env->CallIntMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecPriority); + jint sampleRate = env->CallIntMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getSampleRate); + jint bitsPerSample = env->CallIntMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getBitsPerSample); + jint channelMode = env->CallIntMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getChannelMode); + jlong codecSpecific1 = env->CallLongMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecSpecific1); + jlong codecSpecific2 = env->CallLongMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecSpecific2); + jlong codecSpecific3 = env->CallLongMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecSpecific3); + jlong codecSpecific4 = env->CallLongMethod( + jcodecConfig, android_bluetooth_BluetoothCodecConfig.getCodecSpecific4); + + btav_a2dp_codec_config_t codec_config = { + .codec_type = static_cast(codecType), + .codec_priority = + static_cast(codecPriority), + .sample_rate = static_cast(sampleRate), + .bits_per_sample = + static_cast(bitsPerSample), + .channel_mode = + static_cast(channelMode), + .codec_specific_1 = codecSpecific1, + .codec_specific_2 = codecSpecific2, + .codec_specific_3 = codecSpecific3, + .codec_specific_4 = codecSpecific4}; + return codec_config; +} + +static void initNative(JNIEnv* env, jobject object, + jint maxBroadcast, jobject codecConfig, jint mode) { + std::unique_lock interface_lock(interface_mutex); + std::unique_lock callbacks_lock(callbacks_mutex); + + const bt_interface_t* btInf = getBluetoothInterface(); + if (btInf == nullptr) { + ALOGE("%s: Bluetooth module is not loaded", __func__); + return; + } + + if (sBluetoothBapBroadcastInterface != nullptr) { + ALOGW("%s: Cleaning up BapBroadcast Interface before initializing...", __func__); + sBluetoothBapBroadcastInterface->cleanup(); + sBluetoothBapBroadcastInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + ALOGW("%s: Cleaning up BapBroadcast callback object", __func__); + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } + + if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) { + ALOGE("%s: Failed to allocate Global Ref for bap broadcast Callbacks", __func__); + return; + } + + android_bluetooth_BluetoothCodecConfig.clazz = (jclass)env->NewGlobalRef( + env->FindClass("android/bluetooth/BluetoothCodecConfig")); + if (android_bluetooth_BluetoothCodecConfig.clazz == nullptr) { + ALOGE("%s: Failed to allocate Global Ref for BluetoothCodecConfig class", + __func__); + return; + } + + sBluetoothBapBroadcastInterface = + (btbap_broadcast_interface_t*)btInf->get_profile_interface( + BT_PROFILE_BAP_BROADCAST_ID); + if (sBluetoothBapBroadcastInterface == nullptr) { + ALOGE("%s: Failed to get Bluetooth BapBroadcast Interface", __func__); + return; + } + btav_a2dp_codec_config_t codec_config = + prepare_codec_config(env, object, codecConfig); + /*if (codec_config == NULL) { + ALOGE("%s:Invalid codec config",__func__); + return; + }*/ + bt_status_t status = sBluetoothBapBroadcastInterface->init( + &sBluetoothBapBroadcastCallbacks, maxBroadcast, codec_config, mode); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed to initialize Bluetooth A2DP, status: %d", __func__, + status); + sBluetoothBapBroadcastInterface = nullptr; + return; + } +} + +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) { + ALOGE("%s: Bluetooth module is not loaded", __func__); + return; + } + + if (sBluetoothBapBroadcastInterface != nullptr) { + sBluetoothBapBroadcastInterface->cleanup(); + sBluetoothBapBroadcastInterface = nullptr; + } + + env->DeleteGlobalRef(android_bluetooth_BluetoothCodecConfig.clazz); + android_bluetooth_BluetoothCodecConfig.clazz = nullptr; + + if (mCallbacksObj != nullptr) { + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } +} + +static jboolean setActiveDeviceNative(JNIEnv* env, jobject object, + jboolean enable, jint adv_id) { + ALOGI("%s: sBluetoothBapBroadcastInterface: %p", __func__, sBluetoothBapBroadcastInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothBapBroadcastInterface) { + ALOGE("%s: Failed to get the BapBroadcast Interface", __func__); + return JNI_FALSE; + } + bt_status_t status = sBluetoothBapBroadcastInterface->set_broadcast_active(enable, adv_id); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean enableBroadcastNative(JNIEnv* env, jobject object, jobject codecConfig) { + ALOGI("%s: sBluetoothBapBroadcastInterface: %p", __func__, sBluetoothBapBroadcastInterface); + btav_a2dp_codec_config_t codec_config = + prepare_codec_config(env, object, codecConfig); + std::shared_lock lock(interface_mutex); + if (!sBluetoothBapBroadcastInterface) { + ALOGE("%s: Failed to get the BapBroadcast Interface", __func__); + return JNI_FALSE; + } + bt_status_t status = sBluetoothBapBroadcastInterface->enable_broadcast(codec_config); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean disableBroadcastNative(JNIEnv* env, jobject object, jint adv_id) { + ALOGI("%s: sBluetoothBapBroadcastInterface: %p", __func__, sBluetoothBapBroadcastInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothBapBroadcastInterface) { + ALOGE("%s: Failed to get the BapBroadcast Interface", __func__); + return JNI_FALSE; + } + bt_status_t status = sBluetoothBapBroadcastInterface->disable_broadcast(adv_id); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean setupAudioPathNative(JNIEnv* env, jobject object, jboolean enable,jint adv_id, + jint big_handle, jint num_bises, jintArray bises) { + ALOGI("%s: sBluetoothBapBroadcastInterface: %p", __func__, sBluetoothBapBroadcastInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothBapBroadcastInterface) { + ALOGE("%s: Failed to get the BapBroadcast Interface", __func__); + return JNI_FALSE; + } + jint* bis_handles = env->GetIntArrayElements(bises, NULL); + bt_status_t status = sBluetoothBapBroadcastInterface->setup_audiopath(enable, adv_id, big_handle, num_bises, bis_handles); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jstring getEncryptionKeyNative(JNIEnv* env, jobject object) { + ALOGI("%s: sBluetoothBapBroadcastInterface: %p", __func__, sBluetoothBapBroadcastInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothBapBroadcastInterface) { + ALOGE("%s: Failed to get the BapBroadcast Interface", __func__); + return JNI_FALSE; + } + std::string stdstr = sBluetoothBapBroadcastInterface->get_encryption_key(); + return env->NewStringUTF(stdstr.c_str()); +} + +static jboolean setEncryptionKeyNative(JNIEnv* env, jobject object, jboolean enabled, jint length) { + ALOGI("%s: sBluetoothBapBroadcastInterface: %p", __func__, sBluetoothBapBroadcastInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothBapBroadcastInterface) { + ALOGE("%s: Failed to get the BapBroadcast Interface", __func__); + return JNI_FALSE; + } + bt_status_t status = sBluetoothBapBroadcastInterface->set_encryption(enabled, length); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean setCodecConfigPreferenceNative(JNIEnv* env, jobject object, + jint adv_handle, jobject codecConfig) { + ALOGI("%s: sBluetoothBapBroadcastInterface: %p", __func__, sBluetoothBapBroadcastInterface); + std::shared_lock lock(interface_mutex); + if (!sBluetoothBapBroadcastInterface) { + ALOGE("%s: Failed to get the BapBroadcast Interface", __func__); + return JNI_FALSE; + } + btav_a2dp_codec_config_t codec_config = + prepare_codec_config(env, object, codecConfig); + bt_status_t status = sBluetoothBapBroadcastInterface->codec_config_change(adv_handle, codec_config); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static JNINativeMethod sMethods[] = { + {"classInitNative", "()V", (void*)classInitNative}, + {"initNative", "(ILandroid/bluetooth/BluetoothCodecConfig;I)V", + (void*)initNative}, + {"cleanupNative", "()V", (void*)cleanupNative}, + {"setActiveDeviceNative", "(ZI)Z", (void*)setActiveDeviceNative}, + {"enableBroadcastNative", "(Landroid/bluetooth/BluetoothCodecConfig;)Z", (void*)enableBroadcastNative}, + {"disableBroadcastNative", "(I)Z", (void*)disableBroadcastNative}, + {"getEncryptionKeyNative", "()Ljava/lang/String;", (void*)getEncryptionKeyNative}, + {"setEncryptionKeyNative", "(ZI)Z", (void*)setEncryptionKeyNative}, + {"setupAudioPathNative", "(ZIII[I)Z", (void*)setupAudioPathNative}, + {"setCodecConfigPreferenceNative", + "(ILandroid/bluetooth/BluetoothCodecConfig;)Z", + (void*)setCodecConfigPreferenceNative}, +}; + +int register_com_android_bluetooth_bap_broadcast(JNIEnv* env) { + return jniRegisterNativeMethods( + env, "com/android/bluetooth/broadcast/BroadcastNativeInterface", sMethods, + NELEM(sMethods)); +} + +} diff --git a/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterServiceExt.cpp b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterServiceExt.cpp new file mode 100644 index 00000000000..2d37cc4afff --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterServiceExt.cpp @@ -0,0 +1,62 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +#include "android_runtime/AndroidRuntime.h" +#include "com_android_bluetooth_ext.h" + +namespace android { + + int register_com_android_bluetooth_adv_audio_profiles(JNIEnv* env) { + ALOGE("%s", __func__); + + int status = android::register_com_android_bluetooth_csip_client(env); + if (status < 0) { + ALOGE("jni csip registration failure: %d", status); + return JNI_ERR; + } + + status = android::register_com_android_bluetooth_acm(env); + if (status < 0) { + ALOGE("jni acm registration failure: %d", status); + return JNI_ERR; + } + + status = android::register_com_android_bluetooth_apm(env); + if (status < 0) { + ALOGE("jni APM registration failure: %d", status); + return JNI_ERR; + } + + status = android::register_com_android_bluetooth_bap_broadcast(env); + if (status < 0) { + ALOGE("jni bap broadcast registration failure: %d", status); + return JNI_ERR; + } + + status = android::register_com_android_bluetooth_vcp_controller(env); + if (status < 0) { + ALOGE("jni vcp controller registration failure: %d", status); + return JNI_ERR; + } + + status = android::register_com_android_bluetooth_pacs_client(env); + if (status < 0) { + ALOGE("jni pacs client registration failure: %d", status); + return JNI_ERR; + } + status = android::register_com_android_bluetooth_call_controller(env); + if (status < 0) { + ALOGE("jni CC registration failure: %d", status); + return JNI_ERR; + } + + status = android::register_com_android_bluetooth_mcp(env); + if (status < 0) { + ALOGE("jni mcp registration failure: %d", status); + return JNI_ERR; + } + return JNI_VERSION_1_6; + } +} diff --git a/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_cc.cpp b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_cc.cpp new file mode 100644 index 00000000000..e8203eac2ed --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_cc.cpp @@ -0,0 +1,402 @@ +/* + *Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + + * Copyright 2012 The Android Open Source Project + * + * 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 "BluetoothCCServiceJni" + +#define LOG_NDEBUG 0 + +#include +#include +#include +#include +#include +#include + +#include "android_runtime/AndroidRuntime.h" +#include "base/logging.h" +#include "com_android_bluetooth.h" +#include "hardware/bluetooth.h" +#include "hardware/bluetooth_callcontrol_callbacks.h" +#include "hardware/bluetooth_callcontrol_interface.h" + +using bluetooth::call_control::CallControllerCallbacks; +using bluetooth::call_control::CallControllerInterface; +using bluetooth::Uuid; +static CallControllerInterface* sCallControllerInterface = nullptr; + +namespace android { +static jmethodID method_CallControlInitializedCallback; +static jmethodID method_OnConnectionStateChanged; +static jmethodID method_CallControlPointChangedRequest; +static std::shared_timed_mutex interface_mutex; +static jobject mCallbacksObj = nullptr; +static std::shared_timed_mutex callbacks_mutex; + +class CallControllerCallbacksImpl : public CallControllerCallbacks { + public: + ~CallControllerCallbacksImpl() = default; + void CallControlInitializedCallback(uint8_t state) override { + LOG(INFO) << __func__; + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_CallControlInitializedCallback, + (jint)state); + } + void ConnectionStateCallback(uint8_t state, const RawAddress& bd_addr) override { + LOG(INFO) << __func__; + 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 CallControlCallback(uint8_t op, std::vector p_indices, int count, std::vector uri_data, const RawAddress& bd_addr) override { + LOG(INFO) << __func__; + 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; + } + ScopedLocalRef indices(sCallbackEnv.get(), (jintArray)sCallbackEnv->NewIntArray(count)); + ScopedLocalRef originate_uri( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(uri_data.size())); + if (!originate_uri.get()) { + ALOGE("Error while allocation byte array for uri data in %s", __func__); + return; + } + sCallbackEnv->SetByteArrayRegion(originate_uri.get(), 0, uri_data.size(), + (jbyte*)uri_data.data()); + + sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), + (jbyte*)&bd_addr); + sCallbackEnv->SetIntArrayRegion(indices.get(), 0, count,(jint*)p_indices.data()); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_CallControlPointChangedRequest, + (jint)op, indices.get(), (jint)count, originate_uri.get(), addr.get()); + } +}; + +static CallControllerCallbacksImpl sCallControllerCallbacks; + +static void classInitNative(JNIEnv* env, jclass clazz) { + method_CallControlInitializedCallback = + env->GetMethodID(clazz, "callControlInitializedCallback", "(I)V"); + method_OnConnectionStateChanged = + env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V"); + method_CallControlPointChangedRequest = + env->GetMethodID(clazz, "callControlPointChangedRequest", "(I[II[B[B)V"); + + LOG(INFO) << __func__ << " : succeeds"; +} + +static void initializeNative(JNIEnv* env, jobject object, jstring uuid, + jint max_ccs_clients, jboolean inband_ringing_enabled) { + std::unique_lock interface_lock(interface_mutex); + std::unique_lock callbacks_lock(callbacks_mutex); + + const bt_interface_t* btInf = getBluetoothInterface(); + if (!btInf) { + ALOGE("%s: Bluetooth module is not loaded", __func__); + jniThrowIOException(env, EINVAL); + return; + } + + if (sCallControllerInterface) { + ALOGI("%s: Cleaning up Bluetooth CallControl Interface before initializing", + __func__); + sCallControllerInterface->Cleanup(); + sCallControllerInterface = nullptr; + } + + if (mCallbacksObj) { + ALOGI("%s: Cleaning up Bluetooth CallControl callback object", __func__); + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } + + const char* _uuid = env->GetStringUTFChars(uuid, nullptr); + + sCallControllerInterface = + (CallControllerInterface*)btInf->get_profile_interface( + BT_PROFILE_CC_ID); + if (!sCallControllerInterface) { + ALOGW("%s: Failed to get Bluetooth CallControl Interface", __func__); + jniThrowIOException(env, EINVAL); + return; + } + bt_status_t status = + sCallControllerInterface->Init(&sCallControllerCallbacks, + bluetooth::Uuid::FromString(_uuid), max_ccs_clients, inband_ringing_enabled); + if (status != BT_STATUS_SUCCESS) { + ALOGE("%s: Failed to initialize LE audio Call control Interface, status: %d", + __func__, status); + sCallControllerInterface = nullptr; + return; + } + + env->ReleaseStringUTFChars(uuid, _uuid); + mCallbacksObj = env->NewGlobalRef(object); +} + +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 (sCallControllerInterface != nullptr) { + sCallControllerInterface->Cleanup(); + sCallControllerInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } +} + +static jboolean updateBearerNameNative(JNIEnv* env, jobject object, + jstring operator_str) { + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) { + ALOGW("%s: sCallControllerInterface is null", __func__); + return JNI_FALSE; + } + const char* operator_name = env->GetStringUTFChars(operator_str, nullptr); + bt_status_t status = + sCallControllerInterface->UpdateBearerName((uint8_t*)operator_name); + if (status != BT_STATUS_SUCCESS) { + ALOGE("Failed updateBearerNameNative, status: %d", status); + } + env->ReleaseStringUTFChars(operator_str, operator_name); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean updateBearerTechnologyNative(JNIEnv* env, jobject object, + jint bearer_tech) { + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) { + ALOGW("%s: sCallControllerInterface is null", __func__); + return JNI_FALSE; + } + bt_status_t status = sCallControllerInterface->UpdateBearerTechnology(bearer_tech); + if (status != BT_STATUS_SUCCESS) { + ALOGE("Failed updateBearerTechnologyNative, status: %d", status); + } + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean updateSupportedBearerListNative(JNIEnv* env, jobject object, + jstring bearer_list) { + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) { + ALOGW("%s: sCallControllerInterface is null", __func__); + return JNI_FALSE; + } + const char* list_bearer_string = env->GetStringUTFChars(bearer_list, nullptr); + bt_status_t status = sCallControllerInterface->UpdateSupportedBearerList((uint8_t*)list_bearer_string); + if (status != BT_STATUS_SUCCESS) { + ALOGE("Failed updateSupportedBearerListNative, status: %d", status); + } + env->ReleaseStringUTFChars(bearer_list, list_bearer_string); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + + +static jboolean callControlPointOpcodeSupportedNative(JNIEnv* env, jobject object, + jint feature) { + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) { + ALOGW("%s: sCallControllerInterface is null", __func__); + return JNI_FALSE; + } + bt_status_t status = sCallControllerInterface->CallControlOptionalOpSupported(feature); + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean updateStatusFlagsNative(JNIEnv* env, jobject object, + jint flags) { + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) { + ALOGW("%s: sCallControllerInterface is null", __func__); + return JNI_FALSE; + } + bt_status_t status = sCallControllerInterface->UpdateStatusFlags(flags); + + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean updateSignalStatusNative(JNIEnv* env, jobject object, + jint signal) { + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) { + ALOGW("%s: sCallControllerInterface is null", __func__); + return JNI_FALSE; + } + bt_status_t status = sCallControllerInterface->UpdateSignalStatus(signal); + if (status != BT_STATUS_SUCCESS) { + ALOGE("FAILED updateSignalStatusNative, status: %d", status); + } + return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} +static jboolean updateIncomingCallNative(JNIEnv* env, jobject object, + jint index, jstring uri_str) { + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) { + ALOGW("%s: sCallControllerInterface is null", __func__); + return JNI_FALSE; + } + const char* uri = env->GetStringUTFChars(uri_str, nullptr); + sCallControllerInterface->UpdateIncomingCall(index, (uint8_t*)uri); + env->ReleaseStringUTFChars(uri_str, uri); + return JNI_TRUE; +} + +static jboolean callControlResponseNative(JNIEnv* env, jobject object, + jint op, jint index, jint status, jbyteArray address) { + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) { + ALOGW("%s: sCallControllerInterface is null", __func__); + return JNI_FALSE; + } + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + RawAddress* tmpraw = (RawAddress*)addr; + bt_status_t ret_status = + sCallControllerInterface->CallControlResponse(op, index, status, *tmpraw); + if (ret_status != BT_STATUS_SUCCESS) { + ALOGE("Failed to send callControlResponseNative, status: %d", ret_status); + } + return (ret_status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean setActiveDeviceNative(JNIEnv* env, jobject object, + jint set_id, jbyteArray address) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) return JNI_FALSE; + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + RawAddress* tmpraw = (RawAddress*)addr; + sCallControllerInterface->SetActiveDevice(*tmpraw, set_id); + env->ReleaseByteArrayElements(address, addr, 0); + + return JNI_TRUE; +} + +static jboolean callStateNative(JNIEnv* env, jobject object, jint len, + jbyteArray callList) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + + jbyte* cList = env->GetByteArrayElements(callList, NULL); + if (!cList) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + uint16_t array_len = (uint16_t)env->GetArrayLength(callList); + + std::vector vect_val(cList, cList + array_len); + + if (!sCallControllerInterface) { + ALOGW("%s: sCallControllerInterface is null", __func__); + return JNI_FALSE; + } + sCallControllerInterface->CallState(len, std::move(vect_val)); + env->ReleaseByteArrayElements(callList, cList, 0); + return JNI_TRUE; +} + +static jboolean contentControlIdNative(JNIEnv* env, jobject object, + jint ccid) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) return JNI_FALSE; + + sCallControllerInterface->ContentControlId(ccid); + return JNI_TRUE; +} + +static jboolean disconnectNative(JNIEnv* env, jobject object, + jbyteArray address) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sCallControllerInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + RawAddress* tmpraw = (RawAddress*)addr; + + sCallControllerInterface->Disconnect(*tmpraw); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static JNINativeMethod sMethods[] = { + {"classInitNative", "()V", (void*)classInitNative}, + {"initializeNative", "(Ljava/lang/String;IZ)V", (void*)initializeNative}, + {"cleanupNative", "()V", (void*)cleanupNative}, + {"updateBearerNameNative", "(Ljava/lang/String;)Z", (void*)updateBearerNameNative}, + {"updateBearerTechnologyNative", "(I)Z", (void*)updateBearerTechnologyNative}, + {"updateSupportedBearerListNative", "(Ljava/lang/String;)Z", (void*)updateSupportedBearerListNative}, + {"updateSignalStatusNative", "(I)Z", (void*)updateSignalStatusNative}, + {"updateStatusFlagsNative", "(I)Z", (void*)updateStatusFlagsNative}, + {"updateIncomingCallNative", "(ILjava/lang/String;)Z", (void*)updateIncomingCallNative}, + {"callControlResponseNative", "(III[B)Z", (void*)callControlResponseNative}, + {"callStateNative", "(I[B)Z", (void*)callStateNative}, + {"callControlPointOpcodeSupportedNative", "(I)Z", (void*)callControlPointOpcodeSupportedNative}, + {"setActiveDeviceNative", "(I[B)Z", (void*)setActiveDeviceNative}, + {"contentControlIdNative", "(I)Z", (void*)contentControlIdNative}, + {"disconnectNative", "([B)Z", (void*)disconnectNative}, +}; + +int register_com_android_bluetooth_call_controller(JNIEnv* env) { + return jniRegisterNativeMethods( + env, "com/android/bluetooth/cc/CCNativeInterface", + sMethods, NELEM(sMethods)); +} +} // namespace android diff --git a/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_csip_client.cpp b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_csip_client.cpp new file mode 100644 index 00000000000..1d999492f90 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_csip_client.cpp @@ -0,0 +1,395 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + *****************************************************************************/ + +#define LOG_TAG "BluetoothCsipClientJni" + +#define LOG_NDEBUG 0 + +#include "android_runtime/AndroidRuntime.h" +#include "com_android_bluetooth.h" +#include "hardware/bt_csip.h" +#include "utils/Log.h" + +#include +#include + +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; +} + +static Uuid from_java_uuid(jlong uuid_msb, jlong uuid_lsb) { + std::array uu{}; + for (int i = 0; i < 8; i++) { + uu[7 - i] = (uuid_msb >> (8 * i)) & 0xFF; + uu[15 - i] = (uuid_lsb >> (8 * i)) & 0xFF; + } + return Uuid::From128BitBE(uu); +} + +static RawAddress str2addr(JNIEnv* env, jstring address) { + RawAddress bd_addr; + const char* c_address = env->GetStringUTFChars(address, NULL); + if (!c_address) return bd_addr; + + RawAddress::FromString(std::string(c_address), bd_addr); + env->ReleaseStringUTFChars(address, c_address); + + return bd_addr; +} + +namespace android { +static jmethodID method_onCsipAppRegistered; +static jmethodID method_onConnectionStateChanged; +static jmethodID method_onNewSetFound; +static jmethodID method_onNewSetMemberFound; +static jmethodID method_onLockStatusChanged; +static jmethodID method_onLockAvailable; +static jmethodID method_onSetSirkChanged; +static jmethodID method_onSetSizeChanged; + +static const btcsip_interface_t* sBluetoothCsipInterface = NULL; +static jobject mCallbacksObj = NULL; +static std::shared_timed_mutex mCallbacks_mutex; + +static jstring bdaddr2newjstr(JNIEnv* env, const RawAddress* bda) { + char c_address[32]; + snprintf(c_address, sizeof(c_address), "%02X:%02X:%02X:%02X:%02X:%02X", + bda->address[0], bda->address[1], bda->address[2], bda->address[3], + bda->address[4], bda->address[5]); + + return env->NewStringUTF(c_address); +} + +static void classInitNative(JNIEnv* env, jclass clazz) { + method_onCsipAppRegistered = + env->GetMethodID(clazz, "onCsipAppRegistered", "(IIJJ)V"); + + method_onConnectionStateChanged = + env->GetMethodID(clazz, "onConnectionStateChanged", "(ILjava/lang/String;II)V"); + + method_onNewSetFound = + env->GetMethodID(clazz, "onNewSetFound", "(ILjava/lang/String;I[BJJZ)V"); + + method_onNewSetMemberFound = + env->GetMethodID(clazz, "onNewSetMemberFound", "(ILjava/lang/String;)V"); + + method_onLockStatusChanged = + env->GetMethodID(clazz, "onLockStatusChanged", "(IIII[Ljava/lang/String;)V"); + + method_onLockAvailable = + env->GetMethodID(clazz, "onLockAvailable", "(IILjava/lang/String;)V"); + + method_onSetSirkChanged = + env->GetMethodID(clazz, "onSetSirkChanged", "(I[BLjava/lang/String;)V"); + + method_onSetSizeChanged = + env->GetMethodID(clazz, "onSetSizeChanged", "(IILjava/lang/String;)V"); + + ALOGI("%s: succeeds", __func__); +} + +static void csip_app_registered_callback(uint8_t status, uint8_t app_id, + const bluetooth::Uuid& uuid){ + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid()) return; + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onCsipAppRegistered, + status, app_id, UUID_PARAMS(uuid)); +} + +static void connection_state_changed_callback(uint8_t app_id, RawAddress& addr, + uint8_t state, uint8_t status){ + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid()) return; + + // address + ScopedLocalRef address(sCallbackEnv.get(), + bdaddr2newjstr(sCallbackEnv.get(), &addr)); + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged, + app_id, address.get(), state, status); +} + +static void new_set_found_callback(uint8_t set_id, RawAddress& bd_addr, uint8_t size, + uint8_t* sirk, const bluetooth::Uuid& p_srvc_uuid, + bool lock_support) { + ALOGI("%s: ", __func__); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid()) return; + + // address + ScopedLocalRef address(sCallbackEnv.get(), + bdaddr2newjstr(sCallbackEnv.get(), &bd_addr)); + + ALOGI("%s: new_set_found_callback: process sirk", __func__); + ScopedLocalRef jb(sCallbackEnv.get(), NULL); + jb.reset(sCallbackEnv->NewByteArray(16)); + sCallbackEnv->SetByteArrayRegion(jb.get(), 0, 16, (jbyte*)sirk); + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onNewSetFound, set_id, address.get(), + size, jb.get(), UUID_PARAMS(p_srvc_uuid), lock_support); +} + +static void new_set_member_found_cb(uint8_t set_id, RawAddress& bd_addr) { + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid()) return; + + // address + ScopedLocalRef address(sCallbackEnv.get(), + bdaddr2newjstr(sCallbackEnv.get(), &bd_addr)); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onNewSetMemberFound, set_id, address.get()); +} + +/** Callback for lock status changed event from stack + */ +static void lock_state_changed_callback(uint8_t app_id, uint8_t set_id, + uint8_t value, uint8_t status, + std::vector addr_list) { + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid()) return; + if (!mCallbacksObj) { + ALOGE("mCallbacksObj is NULL. Return."); + return; + } + + int i; + jstring bd_addr; + jobjectArray device_list; + jsize len = addr_list.size(); + device_list = sCallbackEnv->NewObjectArray( + len, sCallbackEnv->FindClass("java/lang/String"), 0); + + for(i = 0; i < len; i++) + { + bd_addr = sCallbackEnv->NewStringUTF(addr_list[i].ToString().c_str()); + sCallbackEnv->SetObjectArrayElement(device_list, i, bd_addr); + } + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onLockStatusChanged, + app_id, set_id, value, status, device_list); +} + +/** Callback when lock is available on earlier denying set member + */ +static void lock_available_callback(uint8_t app_id, uint8_t set_id, + RawAddress& bd_addr) { + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid()) return; + if (!mCallbacksObj) { + ALOGE("mCallbacksObj is NULL. Return."); + return; + } + +// address + ScopedLocalRef address(sCallbackEnv.get(), + bdaddr2newjstr(sCallbackEnv.get(), &bd_addr)); + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onLockAvailable, app_id, set_id, address.get()); +} + +/** Callback when size of coordinated set has been changed + */ +static void set_size_changed_callback(uint8_t set_id, uint8_t size, + RawAddress& bd_addr) { + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid()) return; + if (!mCallbacksObj) { + ALOGE("mCallbacksObj is NULL. Return."); + return; + } + + // address + ScopedLocalRef address(sCallbackEnv.get(), + bdaddr2newjstr(sCallbackEnv.get(), &bd_addr)); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSetSizeChanged, set_id, size, address.get()); +} + +/** Callback when SIRK of coordinated set has been changed + */ +static void set_sirk_changed_callback(uint8_t set_id, uint8_t* sirk, + RawAddress& bd_addr) { + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid()) return; + if (!mCallbacksObj) { + ALOGE("mCallbacksObj is NULL. Return."); + return; + } + + ScopedLocalRef jb(sCallbackEnv.get(), NULL); + jb.reset(sCallbackEnv->NewByteArray(24)); + sCallbackEnv->SetByteArrayRegion(jb.get(), 0, 24, (jbyte*)sirk); + + // address + ScopedLocalRef address(sCallbackEnv.get(), + bdaddr2newjstr(sCallbackEnv.get(), &bd_addr)); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onSetSirkChanged, set_id, jb.get(), address.get()); +} + +static btcsip_callbacks_t sBluetoothCsipCallbacks = { + sizeof(sBluetoothCsipCallbacks), + csip_app_registered_callback, + connection_state_changed_callback, + new_set_found_callback, + new_set_member_found_cb, + lock_state_changed_callback, + lock_available_callback, + set_size_changed_callback, + set_sirk_changed_callback, +}; + +static void initNative(JNIEnv* env, jobject object) { + ALOGI("%s: initNative()", __func__); + + std::unique_lock lock(mCallbacks_mutex); + const bt_interface_t* btInf = getBluetoothInterface(); + if (btInf == NULL) { + ALOGE("Bluetooth module is not loaded"); + return; + } + + if (sBluetoothCsipInterface != NULL) { + ALOGW("Cleaning up Bluetooth CSIP CLIENT Interface before initializing..."); + sBluetoothCsipInterface->cleanup(); + sBluetoothCsipInterface = NULL; + } + + if (mCallbacksObj != NULL) { + ALOGW("Cleaning up Bluetooth CSIP callback object"); + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = NULL; + } + + sBluetoothCsipInterface = + (btcsip_interface_t*)btInf->get_profile_interface(BT_PROFILE_CSIP_CLIENT_ID); + if (sBluetoothCsipInterface == NULL) { + ALOGE("Failed to get Bluetooth CSIPInterface"); + return; + } + + bt_status_t status = sBluetoothCsipInterface->init(&sBluetoothCsipCallbacks); + if (status != BT_STATUS_SUCCESS) { + ALOGE("Failed to initialize Bluetooth CSIP Client, status: %d", status); + sBluetoothCsipInterface = NULL; + return; + } + + mCallbacksObj = env->NewGlobalRef(object); +} + +static void cleanupNative(JNIEnv* env, jobject object) { + ALOGI("%s: cleanupNative()", __func__); + if (!sBluetoothCsipInterface) return; + + sBluetoothCsipInterface->cleanup(); +} + +static jboolean connectSetDeviceNative(JNIEnv* env, jobject object, + jint app_id, jbyteArray address) { + if (!sBluetoothCsipInterface) return JNI_FALSE; + + ALOGI("%s: connectSetDeviceNative()", __func__); + jboolean ret = JNI_TRUE; + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress bd_addr; + bd_addr.FromOctets(reinterpret_cast(addr)); + sBluetoothCsipInterface->connect(app_id, &bd_addr); + return ret; +} + +static jboolean disconnectSetDeviceNative(JNIEnv* env, jobject object, + jint app_id, jbyteArray address) { + if (!sBluetoothCsipInterface) return JNI_FALSE; + + jboolean ret = JNI_TRUE; + ALOGI("%s: disconnectSetDeviceNative()", __func__); + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress bd_addr; + bd_addr.FromOctets(reinterpret_cast(addr)); + sBluetoothCsipInterface->disconnect(app_id, &bd_addr); + return ret; +} + +static void registerCsipAppNative(JNIEnv* env, jobject object, + jlong app_uuid_lsb, jlong app_uuid_msb) { + if (!sBluetoothCsipInterface) return; + + Uuid uuid = from_java_uuid(app_uuid_msb, app_uuid_lsb); + sBluetoothCsipInterface->register_csip_app(uuid); +} + +static void unregisterCsipAppNative(JNIEnv* env, jobject object, jint app_id) { + if (!sBluetoothCsipInterface) return; + + sBluetoothCsipInterface->unregister_csip_app(app_id); +} + +static void setLockValueNative(JNIEnv* env, jobject object, jint app_id, + jint set_id, jint value, jobjectArray devicesList) { + if (!sBluetoothCsipInterface) return; + + std::vector lock_list; + int listCount = env->GetArrayLength(devicesList); + for (int i=0; i < listCount; i++) { + jstring address = (jstring) (env->GetObjectArrayElement(devicesList, i)); + RawAddress bd_addr = str2addr(env, address); + lock_list.push_back(bd_addr); + env->DeleteLocalRef(address); + } + + sBluetoothCsipInterface->set_lock_value(app_id, set_id, value, lock_list); +} + +static JNINativeMethod sMethods[] = { + {"classInitNative", "()V", (void*)classInitNative}, + {"initNative", "()V", (void*)initNative}, + {"cleanupNative", "()V", (void*)cleanupNative}, + {"connectSetDeviceNative", "(I[B)Z", (void*)connectSetDeviceNative}, + {"disconnectSetDeviceNative", "(I[B)Z", (void*)disconnectSetDeviceNative}, + {"registerCsipAppNative", "(JJ)V", (void*)registerCsipAppNative}, + {"unregisterCsipAppNative", "(I)V", (void*)unregisterCsipAppNative}, + {"setLockValueNative", "(III[Ljava/lang/String;)V", (void*)setLockValueNative}, +}; + +int register_com_android_bluetooth_csip_client(JNIEnv* env) { + ALOGE("%s", __func__); + return jniRegisterNativeMethods( + env, "com/android/bluetooth/groupclient/GroupClientNativeInterface", + sMethods, NELEM(sMethods)); +} +} diff --git a/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_ext.h b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_ext.h new file mode 100644 index 00000000000..0a5c18a245f --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_ext.h @@ -0,0 +1,29 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + *****************************************************************************/ +#ifndef COM_ANDROID_BLUETOOTH_EXT +#define COM_ANDROID_BLUETOOTH_EXT + +namespace android { + +int register_com_android_bluetooth_bap_broadcast(JNIEnv* env); + +int register_com_android_bluetooth_acm(JNIEnv* env); + +int register_com_android_bluetooth_apm(JNIEnv* env); + +int register_com_android_bluetooth_csip_client(JNIEnv* env); + +int register_com_android_bluetooth_adv_audio_profiles(JNIEnv* env); + +int register_com_android_bluetooth_vcp_controller(JNIEnv* env); + +int register_com_android_bluetooth_pacs_client(JNIEnv* env); + +int register_com_android_bluetooth_mcp(JNIEnv* env); + +int register_com_android_bluetooth_call_controller(JNIEnv* env); +} + +#endif + diff --git a/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_mcp.cpp b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_mcp.cpp new file mode 100644 index 00000000000..35da9829485 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_mcp.cpp @@ -0,0 +1,417 @@ +/****************************************************************************** + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +#define LOG_TAG "BluetoothMCPService_jni" + +#define LOG_NDEBUG 0 + +#include +#include +#include +#include +#include +#include + + +#include "android_runtime/AndroidRuntime.h" +#include "base/logging.h" +#include "com_android_bluetooth.h" +#include "hardware/bt_mcp.h" +#include "hardware/bluetooth.h" + + + +using bluetooth::mcp_server::McpServerCallbacks; +using bluetooth::mcp_server::McpServerInterface; +using bluetooth::Uuid; +static McpServerInterface* sMcpServerInterface = nullptr; + + +namespace android { +static jmethodID method_OnConnectionStateChanged; +static jmethodID method_MediaControlPointChangedRequest; +static jmethodID method_TrackPositionChangedRequest; +static jmethodID method_PlayingOrderChangedRequest; + + +static std::shared_timed_mutex interface_mutex; + +static jobject mCallbacksObj = nullptr; +static std::shared_timed_mutex callbacks_mutex; + +class McpServerCallbacksImpl : public McpServerCallbacks { + public: + ~McpServerCallbacksImpl() = default; + + void OnConnectionStateChange(int state, const RawAddress& bd_addr) override { + LOG(INFO) << __func__; + 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 MediaControlPointChangeReq(uint8_t state, const RawAddress& bd_addr) override { + LOG(INFO) << __func__; + + 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_MediaControlPointChangedRequest, + (jint)state, addr.get()); + } + + void TrackPositionChangeReq(int32_t position) override { + LOG(INFO) << __func__; + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_TrackPositionChangedRequest, + (jint)position); + } + + void PlayingOrderChangeReq(uint32_t order) override { + LOG(INFO) << __func__; + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_PlayingOrderChangedRequest, + (jint)order); + } +}; + + +static McpServerCallbacksImpl sMcpServerCallbacks; + +static void classInitNative(JNIEnv* env, jclass clazz) { + LOG(INFO) << __func__ << ": class init native"; + method_OnConnectionStateChanged = + env->GetMethodID(clazz, "OnConnectionStateChanged", "(I[B)V"); + LOG(INFO) << __func__ << ": class init native 1"; + method_MediaControlPointChangedRequest = + env->GetMethodID(clazz, "MediaControlPointChangedRequest", "(I[B)V"); + LOG(INFO) << __func__ << ": class init native 2"; + method_TrackPositionChangedRequest = + env->GetMethodID(clazz, "TrackPositionChangedRequest", "(I)V"); + method_PlayingOrderChangedRequest = + env->GetMethodID(clazz, "PlayingOrderChangedRequest", "(I)V"); + + LOG(INFO) << __func__ << ": succeeds"; +} + +// uuid not fixed +Uuid uuid = Uuid::FromString("00008fd1-0000-1000-8000-00805F9B34FB"); + +static void initNative(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 (sMcpServerInterface != nullptr) { + LOG(INFO) << "Cleaning up McpServer Interface before initializing..."; + sMcpServerInterface->Cleanup(); + sMcpServerInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + LOG(INFO) << "Cleaning up McpServer callback object"; + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } + + if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) { + LOG(ERROR) << "Failed to allocate Global Ref for Mcp Controller Callbacks"; + return; + } + LOG(INFO) << "mcs callback initialized"; + sMcpServerInterface = (McpServerInterface* )btInf->get_profile_interface( + BT_PROFILE_MCP_ID); + if (sMcpServerInterface == nullptr) { + LOG(ERROR) << "Failed to get Bluetooth Hearing Aid Interface"; + return; + } + + sMcpServerInterface->Init(&sMcpServerCallbacks, uuid); +} + +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 (sMcpServerInterface != nullptr) { + sMcpServerInterface->Cleanup(); + sMcpServerInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } +} + + + +static jboolean mediaControlPointOpcodeSupportedNative(JNIEnv* env, jobject object, + jint feature) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + sMcpServerInterface->MediaControlPointOpcodeSupported(feature); + return JNI_TRUE; +} + +static jboolean mediaControlPointNative(JNIEnv* env, jobject object, + jint value) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + sMcpServerInterface->MediaControlPoint(value); + return JNI_TRUE; +} + +static jboolean mediaStateNative(JNIEnv* env, jobject object, + jint state) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + sMcpServerInterface->MediaState(state); + return JNI_TRUE; +} + +static jboolean mediaPlayerNameNative(JNIEnv* env, jobject object, + jstring playerName) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + + const char *nativeString = env->GetStringUTFChars(playerName, nullptr); + if (!nativeString) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + sMcpServerInterface->MediaPlayerName((uint8_t*)nativeString); + env->ReleaseStringUTFChars(playerName, nativeString); + return JNI_TRUE; +} + +static jboolean trackChangedNative(JNIEnv* env, jobject object, + jint status) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + sMcpServerInterface->TrackChanged((bool)status); + return JNI_TRUE; +} + +static jboolean trackPositionNative(JNIEnv* env, jobject object, + jint playPosition) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + + sMcpServerInterface->TrackPosition(playPosition); + return JNI_TRUE; +} + +static jboolean trackDurationNative(JNIEnv* env, jobject object, + jint duration) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + sMcpServerInterface->TrackDuration(duration); + + return JNI_TRUE; +} + + + +static jboolean trackTitleNative(JNIEnv* env, jobject object, + jstring title) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + const char *nativeString = env->GetStringUTFChars(title, nullptr); + if (!nativeString) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + sMcpServerInterface->TrackTitle((uint8_t*)nativeString); + env->ReleaseStringUTFChars(title, nativeString); + return JNI_TRUE; +} + +static jboolean setActiveDeviceNative(JNIEnv* env, jobject object, + jint profile, jint set_id, + jbyteArray address) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + RawAddress bd_addr = RawAddress::kEmpty; + if (addr) { + bd_addr.FromOctets(reinterpret_cast(addr)); + } + if (bd_addr == RawAddress::kEmpty) { + LOG(INFO) << __func__ << " active device is null"; + } + + sMcpServerInterface->SetActiveDevice(bd_addr, set_id, profile); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static jboolean bondStateChangeNative(JNIEnv* env, jobject object, + jint state, jbyteArray address) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + RawAddress* tmpraw = (RawAddress*)addr; + + sMcpServerInterface->BondStateChange(*tmpraw, state); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static jboolean playingOrderSupportedNative(JNIEnv* env, jobject object, + jint order) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + sMcpServerInterface->PlayingOrderSupported(order); + + return JNI_TRUE; +} + +static jboolean playingOrderNative(JNIEnv* env, jobject object, + jint order) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + sMcpServerInterface->PlayingOrder(order); + + return JNI_TRUE; +} + +static jboolean contentControlIdNative(JNIEnv* env, jobject object, + jint ccid) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + sMcpServerInterface->ContentControlId(ccid); + return JNI_TRUE; +} + +static jboolean disconnectMcpNative(JNIEnv* env, jobject object, + jbyteArray address) { + + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sMcpServerInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + RawAddress* tmpraw = (RawAddress*)addr; + + sMcpServerInterface->DisconnectMcp(*tmpraw); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static JNINativeMethod sMethods[] = { + {"classInitNative", "()V", (void*)classInitNative}, + {"initNative", "()V", (void*)initNative}, + {"cleanupNative", "()V", (void*)cleanupNative}, + {"mediaStateNative", "(I)Z", (void*)mediaStateNative}, + {"mediaPlayerNameNative", "(Ljava/lang/String;)Z", (void*)mediaPlayerNameNative}, + {"mediaControlPointOpcodeSupportedNative", "(I)Z", (void*)mediaControlPointOpcodeSupportedNative}, + {"mediaControlPointNative", "(I)Z", (void*)mediaControlPointNative}, + {"trackChangedNative", "(I)Z", (void*)trackChangedNative}, + {"trackTitleNative", "(Ljava/lang/String;)Z", (void*)trackTitleNative}, + {"trackPositionNative", "(I)Z", (void*)trackPositionNative}, + {"trackDurationNative", "(I)Z", (void*)trackDurationNative}, + {"playingOrderSupportedNative", "(I)Z", (void*)playingOrderSupportedNative}, + {"playingOrderNative", "(I)Z", (void*)playingOrderNative}, + {"setActiveDeviceNative", "(II[B)Z", (void*)setActiveDeviceNative}, + {"contentControlIdNative", "(I)Z", (void*)contentControlIdNative}, + {"disconnectMcpNative", "([B)Z", (void*)disconnectMcpNative}, + {"bondStateChangeNative", "(I[B)Z", (void*)bondStateChangeNative}, +}; + + + +int register_com_android_bluetooth_mcp(JNIEnv* env) { + return jniRegisterNativeMethods( + env, "com/android/bluetooth/mcp/McpNativeInterface", + sMethods, NELEM(sMethods)); +} +} // namespace android + + diff --git a/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_pacs_client.cpp b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_pacs_client.cpp new file mode 100644 index 00000000000..c65a5d09d20 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_pacs_client.cpp @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + + * Copyright 2018 The Android Open Source Project + * + * 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 "BluetoothPacsClienServiceJni" + +#define LOG_NDEBUG 0 + +#include "android_runtime/AndroidRuntime.h" +#include "base/logging.h" +#include "com_android_bluetooth.h" +#include "hardware/bt_pacs_client.h" + +#include +#include + +using bluetooth::bap::pacs::ConnectionState; +using bluetooth::bap::pacs::PacsClientInterface; +using bluetooth::bap::pacs::PacsClientCallbacks; + +namespace android { + +static jmethodID method_OnInitialized; +static jmethodID method_onConnectionStateChanged; +static jmethodID method_OnAudioContextAvailable; +static jmethodID method_onServiceDiscovery; + +static struct { + jclass clazz; + jmethodID constructor; + jmethodID getCodecType; + jmethodID getCodecPriority; + jmethodID getSampleRate; + jmethodID getBitsPerSample; + jmethodID getChannelMode; + jmethodID getCodecSpecific1; + jmethodID getCodecSpecific2; + jmethodID getCodecSpecific3; + jmethodID getCodecSpecific4; +} android_bluetooth_pacs_record; + +static PacsClientInterface* sPacsClientInterface = nullptr; +static std::shared_timed_mutex interface_mutex; + +static jobject mCallbacksObj = nullptr; +static std::shared_timed_mutex callbacks_mutex; + +class PacsClientCallbacksImpl : public PacsClientCallbacks { + public: + ~PacsClientCallbacksImpl() = default; + void OnInitialized(int status, + int client_id) override { + LOG(INFO) << __func__; + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_OnInitialized, + (jint)status, (jint)client_id); + } + + void OnConnectionState(const RawAddress& bd_addr, + ConnectionState state) override { + LOG(INFO) << __func__; + + 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, + addr.get(), (jint)state); + } + + void OnAudioContextAvailable(const RawAddress& bd_addr, + uint32_t available_contexts) override { + LOG(INFO) << __func__; + + 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 available audio context"; + return; + } + + sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), + (jbyte*)&bd_addr); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_OnAudioContextAvailable, + addr.get(), (jint)available_contexts); + } + + void OnSearchComplete(int status, const RawAddress& address, + std::vector sink_pac_records, + std::vector src_pac_records, + uint32_t sink_locations, + uint32_t src_locations, + uint32_t available_contexts, + uint32_t supported_contexts) override { + + LOG(INFO) << __func__; + + std::shared_lock lock(callbacks_mutex); + CallbackEnv sCallbackEnv(__func__); + if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return; + + + jsize i = 0; + jobjectArray sink_pac_records_array = sCallbackEnv->NewObjectArray( + (jsize)sink_pac_records.size(), + android_bluetooth_pacs_record.clazz, nullptr); + for (auto const& cap : sink_pac_records) { + jobject capObj = sCallbackEnv->NewObject( + android_bluetooth_pacs_record.clazz, + android_bluetooth_pacs_record.constructor, + (jint)cap.codec_type, (jint)cap.codec_priority, (jint)cap.sample_rate, + (jint)cap.bits_per_sample, (jint)cap.channel_mode, + (jlong)cap.codec_specific_1, (jlong)cap.codec_specific_2, + (jlong)cap.codec_specific_3, (jlong)cap.codec_specific_4); + sCallbackEnv->SetObjectArrayElement(sink_pac_records_array, i++, capObj); + sCallbackEnv->DeleteLocalRef(capObj); + } + + i = 0; + jobjectArray src_pac_records_array = sCallbackEnv->NewObjectArray( + (jsize)src_pac_records.size(), + android_bluetooth_pacs_record.clazz, nullptr); + for (auto const& cap : src_pac_records) { + jobject capObj = sCallbackEnv->NewObject( + android_bluetooth_pacs_record.clazz, + android_bluetooth_pacs_record.constructor, + (jint)cap.codec_type, (jint)cap.codec_priority, (jint)cap.sample_rate, + (jint)cap.bits_per_sample, (jint)cap.channel_mode, + (jlong)cap.codec_specific_1, (jlong)cap.codec_specific_2, + (jlong)cap.codec_specific_3, (jlong)cap.codec_specific_4); + sCallbackEnv->SetObjectArrayElement(src_pac_records_array, i++, + capObj); + sCallbackEnv->DeleteLocalRef(capObj); + } + + ScopedLocalRef addr( + sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress))); + if (!addr.get()) { + LOG(ERROR) << "Failed to new jbyteArray bd addr"; + return; + } + + sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), + (jbyte*)&address); + sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onServiceDiscovery, + sink_pac_records_array, src_pac_records_array, (jint)sink_locations, + (jint)src_locations, (jint)available_contexts, (jint)supported_contexts, + (jint)status, addr.get()); + } +}; + +static PacsClientCallbacksImpl sPacsClientCallbacks; + +static void classInitNative(JNIEnv* env, jclass clazz) { + + jclass jniBluetoothCodecConfigClass = + env->FindClass("android/bluetooth/BluetoothCodecConfig"); + android_bluetooth_pacs_record.constructor = + env->GetMethodID(jniBluetoothCodecConfigClass, "", "(IIIIIJJJJ)V"); + android_bluetooth_pacs_record.getCodecType = + env->GetMethodID(jniBluetoothCodecConfigClass, "getCodecType", "()I"); + android_bluetooth_pacs_record.getCodecPriority = + env->GetMethodID(jniBluetoothCodecConfigClass, "getCodecPriority", "()I"); + android_bluetooth_pacs_record.getSampleRate = + env->GetMethodID(jniBluetoothCodecConfigClass, "getSampleRate", "()I"); + android_bluetooth_pacs_record.getBitsPerSample = + env->GetMethodID(jniBluetoothCodecConfigClass, "getBitsPerSample", "()I"); + android_bluetooth_pacs_record.getChannelMode = + env->GetMethodID(jniBluetoothCodecConfigClass, "getChannelMode", "()I"); + android_bluetooth_pacs_record.getCodecSpecific1 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific1", "()J"); + android_bluetooth_pacs_record.getCodecSpecific2 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific2", "()J"); + android_bluetooth_pacs_record.getCodecSpecific3 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific3", "()J"); + android_bluetooth_pacs_record.getCodecSpecific4 = env->GetMethodID( + jniBluetoothCodecConfigClass, "getCodecSpecific4", "()J"); + + method_OnInitialized = + env->GetMethodID(clazz, "OnInitialized", "(II)V"); + + method_onConnectionStateChanged = + env->GetMethodID(clazz, "onConnectionStateChanged", "([BI)V"); + + method_OnAudioContextAvailable = + env->GetMethodID(clazz, "OnAudioContextAvailable", "([BI)V"); + + method_onServiceDiscovery = + env->GetMethodID(clazz, "onServiceDiscovery", "([Landroid/bluetooth/BluetoothCodecConfig;" + "[Landroid/bluetooth/BluetoothCodecConfig;" + "IIIII[B)V"); + + LOG(INFO) << __func__ << ": succeeds"; +} + +static void initNative(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 (sPacsClientInterface != nullptr) { + LOG(INFO) << "Cleaning up PacsClient Interface before initializing..."; + sPacsClientInterface->Cleanup(0); + sPacsClientInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + LOG(INFO) << "Cleaning up PacsClient callback object"; + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } + + if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) { + LOG(ERROR) << "Failed to allocate Global Ref for pacs client Callbacks"; + return; + } + + android_bluetooth_pacs_record.clazz = (jclass)env->NewGlobalRef( + env->FindClass("android/bluetooth/BluetoothCodecConfig")); + if (android_bluetooth_pacs_record.clazz == nullptr) { + ALOGE("%s: Failed to allocate Global Ref for BluetoothCodecConfig class", + __func__); + return; + } + + sPacsClientInterface = (PacsClientInterface*)btInf->get_profile_interface( + BT_PROFILE_PACS_CLIENT_ID); + if (sPacsClientInterface == nullptr) { + LOG(ERROR) << "Failed to get Bluetooth pacs client Interface"; + return; + } + + sPacsClientInterface->Init(&sPacsClientCallbacks); +} + +static void cleanupNative(JNIEnv* env, jobject object, jint client_id) { + 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 (sPacsClientInterface != nullptr) { + sPacsClientInterface->Cleanup(client_id); + sPacsClientInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } + env->DeleteGlobalRef(android_bluetooth_pacs_record.clazz); + android_bluetooth_pacs_record.clazz = nullptr; +} + +static jboolean connectPacsClientNative(JNIEnv* env, jobject object, + jint client_id, jbyteArray address) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sPacsClientInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress* tmpraw = (RawAddress*)addr; + sPacsClientInterface->Connect(client_id, *tmpraw); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static jboolean disconnectPacsClientNative(JNIEnv* env, jobject object, + jint client_id, jbyteArray address) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sPacsClientInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress* tmpraw = (RawAddress*)addr; + sPacsClientInterface->Disconnect(client_id, *tmpraw); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static jboolean startDiscoveryNative(JNIEnv* env, jobject object, + jint client_id, jbyteArray address) { + std::shared_lock lock(interface_mutex); + if (!sPacsClientInterface) return JNI_FALSE; + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress* tmpraw = (RawAddress*)addr; + sPacsClientInterface->StartDiscovery(client_id, *tmpraw); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static void GetAvailableAudioContextsNative(JNIEnv* env, jobject object, + jint client_id, jbyteArray address) { + std::shared_lock lock(interface_mutex); + if (!sPacsClientInterface) return; + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return; + } + + RawAddress* tmpraw = (RawAddress*)addr; + sPacsClientInterface->GetAvailableAudioContexts(client_id, *tmpraw); + env->ReleaseByteArrayElements(address, addr, 0); +} + + +static JNINativeMethod sMethods[] = { + {"classInitNative", "()V", (void*)classInitNative}, + {"initNative", "()V", (void*)initNative}, + {"cleanupNative", "(I)V", (void*)cleanupNative}, + {"connectPacsClientNative", "(I[B)Z", (void*)connectPacsClientNative}, + {"disconnectPacsClientNative", "(I[B)Z", (void*)disconnectPacsClientNative}, + {"startDiscoveryNative", "(I[B)Z", (void*)startDiscoveryNative}, + {"GetAvailableAudioContextsNative", "(I[B)Z", (void*)GetAvailableAudioContextsNative}, +}; + +int register_com_android_bluetooth_pacs_client(JNIEnv* env) { + return jniRegisterNativeMethods( + env, "com/android/bluetooth/pc/PacsClientNativeInterface", + sMethods, NELEM(sMethods)); +} +} // namespace android diff --git a/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_vcp_controller.cpp b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_vcp_controller.cpp new file mode 100644 index 00000000000..552fd8ad6be --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_vcp_controller.cpp @@ -0,0 +1,295 @@ +/* + *Copyright (c) 2020, The Linux Foundation. All rights reserved. + + * Copyright 2018 The Android Open Source Project + * + * 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 "BluetoothVCPControllerJni" + +#define LOG_NDEBUG 0 + +#include "android_runtime/AndroidRuntime.h" +#include "base/logging.h" +#include "com_android_bluetooth.h" +#include "hardware/bt_vcp_controller.h" + +#include +#include + +using bluetooth::vcp_controller::ConnectionState; +using bluetooth::vcp_controller::VcpControllerCallbacks; +using bluetooth::vcp_controller::VcpControllerInterface; + +namespace android { +static jmethodID method_onConnectionStateChanged; +static jmethodID method_onVolumeStateChange; +static jmethodID method_onVolumeFlagsChange; + +static VcpControllerInterface* sVcpControllerInterface = nullptr; +static std::shared_timed_mutex interface_mutex; + +static jobject mCallbacksObj = nullptr; +static std::shared_timed_mutex callbacks_mutex; + +class VcpControllerCallbacksImpl : public VcpControllerCallbacks { + public: + ~VcpControllerCallbacksImpl() = default; + + void OnConnectionState(ConnectionState state, + const RawAddress& bd_addr) override { + LOG(INFO) << __func__; + + 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 OnVolumeStateChange(uint8_t volume, uint8_t mute, + const RawAddress& bd_addr) override { + LOG(INFO) << __func__; + + 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_onVolumeStateChange, + (jint)volume, (jboolean)mute, addr.get()); + } + + void OnVolumeFlagsChange(uint8_t flags, + const RawAddress& bd_addr) override { + LOG(INFO) << __func__; + + 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_onVolumeFlagsChange, + (jint)flags, addr.get()); + } +}; + +static VcpControllerCallbacksImpl sVcpControllerCallbacks; + +static void classInitNative(JNIEnv* env, jclass clazz) { + method_onConnectionStateChanged = + env->GetMethodID(clazz, "onConnectionStateChanged", "(I[B)V"); + + method_onVolumeStateChange = + env->GetMethodID(clazz, "OnVolumeStateChange", "(II[B)V"); + + method_onVolumeFlagsChange = + env->GetMethodID(clazz, "OnVolumeFlagsChange", "(I[B)V"); + + LOG(INFO) << __func__ << ": succeeds"; +} + +static void initNative(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 (sVcpControllerInterface != nullptr) { + LOG(INFO) << "Cleaning up VcpController Interface before initializing..."; + sVcpControllerInterface->Cleanup(); + sVcpControllerInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + LOG(INFO) << "Cleaning up VcpController callback object"; + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } + + if ((mCallbacksObj = env->NewGlobalRef(object)) == nullptr) { + LOG(ERROR) << "Failed to allocate Global Ref for Vcp Controller Callbacks"; + return; + } + + sVcpControllerInterface = (VcpControllerInterface*)btInf->get_profile_interface( + BT_PROFILE_VOLUME_CONTROL_ID); + if (sVcpControllerInterface == nullptr) { + LOG(ERROR) << "Failed to get Bluetooth Hearing Aid Interface"; + return; + } + + sVcpControllerInterface->Init(&sVcpControllerCallbacks); +} + +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 (sVcpControllerInterface != nullptr) { + sVcpControllerInterface->Cleanup(); + sVcpControllerInterface = nullptr; + } + + if (mCallbacksObj != nullptr) { + env->DeleteGlobalRef(mCallbacksObj); + mCallbacksObj = nullptr; + } +} + +static jboolean connectVcpNative(JNIEnv* env, jobject object, + jbyteArray address, jboolean isDirect) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sVcpControllerInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress* tmpraw = (RawAddress*)addr; + sVcpControllerInterface->Connect(*tmpraw, isDirect); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static jboolean disconnectVcpNative(JNIEnv* env, jobject object, + jbyteArray address) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sVcpControllerInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress* tmpraw = (RawAddress*)addr; + sVcpControllerInterface->Disconnect(*tmpraw); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static jboolean setAbsVolumeNative(JNIEnv* env, jobject object, + jint volume, jbyteArray address) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sVcpControllerInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress* tmpraw = (RawAddress*)addr; + sVcpControllerInterface->SetAbsVolume(volume, *tmpraw); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static jboolean muteNative(JNIEnv* env, jobject object, + jbyteArray address) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sVcpControllerInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress* tmpraw = (RawAddress*)addr; + sVcpControllerInterface->Mute(*tmpraw); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static jboolean unmuteNative(JNIEnv* env, jobject object, + jbyteArray address) { + LOG(INFO) << __func__; + std::shared_lock lock(interface_mutex); + if (!sVcpControllerInterface) return JNI_FALSE; + + jbyte* addr = env->GetByteArrayElements(address, nullptr); + if (!addr) { + jniThrowIOException(env, EINVAL); + return JNI_FALSE; + } + + RawAddress* tmpraw = (RawAddress*)addr; + sVcpControllerInterface->Unmute(*tmpraw); + env->ReleaseByteArrayElements(address, addr, 0); + return JNI_TRUE; +} + +static JNINativeMethod sMethods[] = { + {"classInitNative", "()V", (void*)classInitNative}, + {"initNative", "()V", (void*)initNative}, + {"cleanupNative", "()V", (void*)cleanupNative}, + {"connectVcpNative", "([BZ)Z", (void*)connectVcpNative}, + {"disconnectVcpNative", "([B)Z", (void*)disconnectVcpNative}, + {"setAbsVolumeNative", "(I[B)Z", (void*)setAbsVolumeNative}, + {"muteNative", "([B)Z", (void*)muteNative}, + {"unmuteNative", "([B)Z", (void*)unmuteNative}, +}; + +int register_com_android_bluetooth_vcp_controller(JNIEnv* env) { + return jniRegisterNativeMethods( + env, "com/android/bluetooth/vcp/VcpControllerNativeInterface", + sMethods, NELEM(sMethods)); +} +} // namespace android + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmCodecConfig.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmCodecConfig.java new file mode 100644 index 00000000000..8a87efe7887 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmCodecConfig.java @@ -0,0 +1,127 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +package com.android.bluetooth.acm; + +import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothCodecStatus; +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.Resources.NotFoundException; +import android.os.SystemProperties; +import android.util.Log; +import com.android.bluetooth.R; +import com.android.bluetooth.btservice.AdapterService; + +import java.util.Arrays; +import java.util.Objects; +/* + * ACM Codec Configuration setup. + */ +class AcmCodecConfig { + private static final boolean DBG = true; + private static final String TAG = "AcmCodecConfig"; + static final int CONTEXT_TYPE_UNKNOWN = 0; + static final int CONTEXT_TYPE_MUSIC = 1; + static final int CONTEXT_TYPE_VOICE = 2; + static final int CONTEXT_TYPE_MUSIC_VOICE = 3; + + private Context mContext; + private AcmNativeInterface mAcmNativeInterface; + + private BluetoothCodecConfig[] mCodecConfigPriorities; + private int mAcmSourceCodecPriorityLC3 = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT; + + private int assigned_codec_length = 0; + AcmCodecConfig(Context context, AcmNativeInterface acmNativeInterface) { + mContext = context; + mAcmNativeInterface = acmNativeInterface; + mCodecConfigPriorities = assignCodecConfigPriorities(); + } + + BluetoothCodecConfig[] codecConfigPriorities() { + return mCodecConfigPriorities; + } + + void setCodecConfigPreference(BluetoothDevice device, + BluetoothCodecConfig newCodecConfig, + int contextType) { + //Objects.requireNonNull(codecStatus); + + /*// Check whether the codecConfig is selectable for this Bluetooth device. + BluetoothCodecConfig[] selectableCodecs = codecStatus.getCodecsSelectableCapabilities(); + if (!Arrays.asList(selectableCodecs).stream().anyMatch(codec -> + codec.isMandatoryCodec())) { + // Do not set codec preference to native if the selectableCodecs not contain mandatory + // codec. The reason could be remote codec negotiation is not completed yet. + Log.w(TAG, "setCodecConfigPreference: must have mandatory codec before changing."); + return; + } + if (!codecStatus.isCodecConfigSelectable(newCodecConfig)) { + Log.w(TAG, "setCodecConfigPreference: invalid codec " + + Objects.toString(newCodecConfig)); + return; + } + + // Check whether the codecConfig would change current codec config. + int prioritizedCodecType = getPrioitizedCodecType(newCodecConfig, selectableCodecs); + BluetoothCodecConfig currentCodecConfig = codecStatus.getCodecConfig(); + if (prioritizedCodecType == currentCodecConfig.getCodecType() + && (prioritizedCodecType != newCodecConfig.getCodecType() + || (currentCodecConfig.similarCodecFeedingParameters(newCodecConfig) + && currentCodecConfig.sameCodecSpecificParameters(newCodecConfig)))) { + // Same codec with same parameters, no need to send this request to native. + Log.w(TAG, "setCodecConfigPreference: codec not changed."); + return; + }*/ + + BluetoothCodecConfig[] codecConfigArray = new BluetoothCodecConfig[1]; + codecConfigArray[0] = newCodecConfig; + mAcmNativeInterface.setCodecConfigPreference(device, codecConfigArray, contextType, contextType); + } + + // Get the codec type of the highest priority of selectableCodecs and codecConfig. + private int getPrioitizedCodecType(BluetoothCodecConfig codecConfig, + BluetoothCodecConfig[] selectableCodecs) { + BluetoothCodecConfig prioritizedCodecConfig = codecConfig; + for (BluetoothCodecConfig config : selectableCodecs) { + if (prioritizedCodecConfig == null) { + prioritizedCodecConfig = config; + } + if (config.getCodecPriority() > prioritizedCodecConfig.getCodecPriority()) { + prioritizedCodecConfig = config; + } + } + return prioritizedCodecConfig.getCodecType(); + } + + // Assign the ACM Source codec config priorities + private BluetoothCodecConfig[] assignCodecConfigPriorities() { + Resources resources = mContext.getResources(); + if (resources == null) { + return null; + } + + int value; + mAcmSourceCodecPriorityLC3 = BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST; + + BluetoothCodecConfig codecConfig; + BluetoothCodecConfig[] codecConfigArray; + int codecCount = 0; + codecConfigArray = + new BluetoothCodecConfig[BluetoothCodecConfig.SOURCE_QVA_CODEC_TYPE_MAX]; + + codecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, + mAcmSourceCodecPriorityLC3, BluetoothCodecConfig.SAMPLE_RATE_NONE, + BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, BluetoothCodecConfig + .CHANNEL_MODE_NONE, 0 /* codecSpecific1 */, + 0 /* codecSpecific2 */, 0 /* codecSpecific3 */, 0 /* codecSpecific4 */); + codecConfigArray[codecCount++] = codecConfig; + assigned_codec_length = codecCount; + return codecConfigArray; + } +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmNativeInterface.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmNativeInterface.java new file mode 100644 index 00000000000..47a91a3dde0 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmNativeInterface.java @@ -0,0 +1,224 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +package com.android.bluetooth.acm; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothCodecStatus; +import android.bluetooth.BluetoothDevice; +import android.util.Log; +import java.util.List; + +import com.android.bluetooth.Utils; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +/** + * ACM Native Interface to/from JNI. + */ +public class AcmNativeInterface { + private static final String TAG = "AcmNativeInterface"; + private static final boolean DBG = true; + private BluetoothAdapter mAdapter; + static final int CONTEXT_TYPE_UNKNOWN = 0; + static final int CONTEXT_TYPE_MUSIC = 1; + static final int CONTEXT_TYPE_VOICE = 2; + static final int CONTEXT_TYPE_MUSIC_VOICE = 3; + @GuardedBy("INSTANCE_LOCK") + private static AcmNativeInterface sInstance; + private static final Object INSTANCE_LOCK = new Object(); + + static { + classInitNative(); + } + + @VisibleForTesting + private AcmNativeInterface() { + mAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mAdapter == null) { + Log.wtf(TAG, "No Bluetooth Adapter Available"); + } + } + + /** + * Get singleton instance. + */ + public static AcmNativeInterface getInstance() { + synchronized (INSTANCE_LOCK) { + if (sInstance == null) { + sInstance = new AcmNativeInterface(); + } + return sInstance; + } + } + + /** + * Initializes the native interface. + * + * @param maxConnectedAudioDevices maximum number of A2DP Sink devices that can be connected + * simultaneously + * @param codecConfigPriorities an array with the codec configuration + * priorities to configure. + */ + public void init(int maxConnectedAudioDevices, BluetoothCodecConfig[] codecConfigPriorities) { + initNative(maxConnectedAudioDevices, codecConfigPriorities); + } + + + /** + * Initiates ACM connection to a remote device. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + public boolean connectAcm(BluetoothDevice device, int contextType, int profileType, int preferredContext) { + return connectAcmNative(getByteAddress(device), contextType, profileType, preferredContext); + } + + /** + * Disconnects ACM from a remote device. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + public boolean disconnectAcm(BluetoothDevice device, int contextType) { + return disconnectAcmNative(getByteAddress(device), contextType); + } + + /** + * Sets a connected ACM group/remote as active. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + public boolean setActiveDevice(BluetoothDevice device, int contextType) { + return setActiveDeviceNative(getByteAddress(device), contextType); + } + + /** + * Sends Start stream to remote group/remote for voice call. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + public boolean startStream(BluetoothDevice device, int contextType) { + return startStreamNative(getByteAddress(device), contextType); + } + + /** + * Sends Stop stream to remote group/remote for voice call. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + public boolean stopStream(BluetoothDevice device, int contextType) { + return stopStreamNative(getByteAddress(device), contextType); + } + + /** + * Sets the codec configuration preferences. + * + * @param device the remote Bluetooth device + * @param codecConfigArray an array with the codec configurations to + * configure. + * @return true on success, otherwise false. + */ + public boolean setCodecConfigPreference(BluetoothDevice device, BluetoothCodecConfig[] codecConfigArray, + int contextType, int preferredContext) { + return setCodecConfigPreferenceNative(getByteAddress(device), codecConfigArray, + contextType, preferredContext); + } + + public boolean ChangeCodecConfigPreference(BluetoothDevice device, + String message) { + return ChangeCodecConfigPreferenceNative(getByteAddress(device), message); + } + /** + * Cleanup the native interface. + */ + public void cleanup() { + cleanupNative(); + } + + private BluetoothDevice getDevice(byte[] address) { + return mAdapter.getRemoteDevice(address); + } + + 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(AcmStackEvent event) { + AcmService service = AcmService.getAcmService(); + if (service != null) { + service.messageFromNative(event); + } else { + Log.w(TAG, "Event ignored, service not available: " + 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 onConnectionStateChanged(byte[] address, int state, int contextType) { + AcmStackEvent event = + new AcmStackEvent(AcmStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); + event.device = getDevice(address); + event.valueInt1 = state; + event.valueInt2 = contextType; + + if (DBG) { + Log.d(TAG, "onConnectionStateChanged: " + event); + } + sendMessageToService(event); + } + + private void onAudioStateChanged(byte[] address, int state, int contextType) { + AcmStackEvent event = new AcmStackEvent(AcmStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED); + event.device = getDevice(address); + event.valueInt1 = state; + event.valueInt2 = contextType; + + if (DBG) { + Log.d(TAG, "onAudioStateChanged: " + event); + } + sendMessageToService(event); + } + + private void onCodecConfigChanged(byte[] address, + BluetoothCodecConfig newCodecConfig, + BluetoothCodecConfig[] codecsLocalCapabilities, + BluetoothCodecConfig[] codecsSelectableCapabilities, int contextType) { + AcmStackEvent event = new AcmStackEvent(AcmStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED); + event.device = getDevice(address); + event.codecStatus = new BluetoothCodecStatus(newCodecConfig, + codecsLocalCapabilities, + codecsSelectableCapabilities); + event.valueInt2 = contextType; + if (DBG) { + Log.d(TAG, "onCodecConfigChanged: " + event); + } + sendMessageToService(event); + } + + // Native methods that call into the JNI interface + private static native void classInitNative(); + private native void initNative(int maxConnectedAudioDevices, + BluetoothCodecConfig[] codecConfigPriorities); + private native boolean connectAcmNative(byte[] address, int contextType, int profileType, int preferredContext); + private native boolean disconnectAcmNative(byte[] address, int contextType); + private native boolean setActiveDeviceNative(byte[] address, int contextType); + private native boolean startStreamNative(byte[] address, int contextType); + private native boolean stopStreamNative(byte[] address, int contextType); + private native boolean setCodecConfigPreferenceNative(byte[] address, + BluetoothCodecConfig[] codecConfigArray, int contextType, int preferredContext); + private native boolean ChangeCodecConfigPreferenceNative(byte[] address, String Id); + private native void cleanupNative(); +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmService.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmService.java new file mode 100644 index 00000000000..31670a2e88f --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmService.java @@ -0,0 +1,1852 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +package com.android.bluetooth.acm; + +import static android.Manifest.permission.BLUETOOTH_CONNECT; + +import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission; + +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothA2dp.OptionalCodecsPreferenceStatus; +import android.bluetooth.BluetoothA2dp.OptionalCodecsSupportStatus; +import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothCodecStatus; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.os.HandlerThread; +import android.os.Handler; +import android.os.Binder; +import android.os.IBinder; +import android.os.ParcelUuid; +import android.util.Log; +import android.os.Message; +import android.bluetooth.BluetoothGroupCallback; +import com.android.bluetooth.groupclient.GroupService; +import android.bluetooth.DeviceGroup; +import android.bluetooth.BluetoothDeviceGroup; +import com.android.bluetooth.apm.ActiveDeviceManagerService; +import com.android.bluetooth.apm.VolumeManager; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import android.os.SystemProperties; +import com.android.bluetooth.BluetoothMetricsProto; +import com.android.bluetooth.BluetoothStatsLog; +import com.android.bluetooth.Utils; +import android.bluetooth.BluetoothAdapter; +import com.android.bluetooth.apm.ApmConst; +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.MetricsLogger; +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.btservice.ServiceFactory; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; +import java.util.Iterator; +import java.util.ArrayList; +import java.util.List; +import java.util.HashMap; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import com.android.bluetooth.vcp.VcpController; +/** + * Provides Bluetooth ACM profile, as a service in the Bluetooth application. + * @hide + */ +public class AcmService extends ProfileService { + private static final boolean DBG = true; + private static final String TAG = "AcmService"; + private String mAcmName; + public static final int ACM_AUDIO_UNICAST = 25; + public static final int INVALID_SET_ID = 0x10; + private static AcmService sAcmService; + private BluetoothAdapter mAdapter; + private AdapterService mAdapterService; + private HandlerThread mStateMachinesThread; + private static final int LOCK_RELEASED = 0; // (LOCK Released successfully) + private static final int LOCK_RELEASED_TIMEOUT = 1; // (LOCK Released by timeout) + private static final int ALL_LOCKS_ACQUIRED = 2; // (LOCK Acquired for all requested set members) + private static final int SOME_LOCKS_ACQUIRED_REASON_TIMEOUT = 3; // (Request timeout for some set members) + private static final int SOME_LOCKS_ACQUIRED_REASON_DISC = 4; // (Some of the set members were disconnected) + private static final int LOCK_DENIED = 5; // (Denied by one of the set members) + private static final int INVALID_REQUEST_PARAMS = 6; // (Upper layer provided invalid parameters) + private static final int LOCK_RELEASE_NOT_ALLOWED = 7; // (Response from remote (PTS)) + private static final int INVALID_VALUE = 8; + @VisibleForTesting + AcmNativeInterface mAcmNativeInterface; + @VisibleForTesting + ServiceFactory mFactory = new ServiceFactory(); + + static final int CONTEXT_TYPE_UNKNOWN = 0; + static final int CONTEXT_TYPE_MUSIC = 1; + static final int CONTEXT_TYPE_VOICE = 2; + static final int CONTEXT_TYPE_MUSIC_VOICE = 3; + static final int CONTEXT_TYPE_BROADCAST_AUDIO = 6; + + private AcmCodecConfig mAcmCodecConfig; + private final Object mAudioManagerLock = new Object(); + private final Object mBtLeaLock = new Object(); + private final Object mBtAcmLock = new Object(); + private String mLeaChannelMode = "stereo"; + private AudioManager mAudioManager; + @GuardedBy("mStateMachines") + private BluetoothDevice mGroupBdAddress = null; + private BluetoothDevice mActiveDevice = null; + private BluetoothDevice mActiveDeviceVoice = null; + private int mActiveDeviceProfile = 0; + private int mActiveDeviceVoiceProfile = 0; + private final ConcurrentMap mStateMachines = + new ConcurrentHashMap<>(); + private HashMap mAcmDevices = + new HashMap(); + + // Upper limit of all ACM devices: Bonded or Connected + private static final int MAX_ACM_STATE_MACHINES = 50; + // Upper limit of all ACM devices that are Connected or Connecting + private int mMaxConnectedAudioDevices = 1; + CsipManager mCsipManager = null; + boolean mIsCsipRegistered = false; + boolean mShoPend = false; + boolean mVoiceShoPend = false; + //volume + private int mAudioStreamMax; + private int mActiveDeviceLocalMediaVol; + private int mActiveDeviceLocalVoiceVol; + private boolean mActiveDeviceIsMuted; + private static final int VCP_MAX_VOL = 255; + private VcpController mVcpController; + + private BroadcastReceiver mBondStateChangedReceiver; + private final ReentrantReadWriteLock mAcmNativeInterfaceLock = new ReentrantReadWriteLock(); + public int mCsipAppId = -1; + + private static final int SET_EBMONO_CFG = 1; + private static final int SET_EBSTEREO_CFG = 2; + private static final int MonoCfg_Timeout = 3000; + private static final int StereoCfg_Timeout = 3000; + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) + { + synchronized(mBtLeaLock) { + switch (msg.what) { + case SET_EBMONO_CFG: + Log.d(TAG, "setparameters to Mono"); + synchronized (mAudioManagerLock) { + if(mAudioManager != null) + mAudioManager.setParameters("LEAMono=true"); + } + mLeaChannelMode = "mono"; + break; + case SET_EBSTEREO_CFG: + Log.d(TAG, "setparameters to stereo"); + synchronized (mAudioManagerLock) { + if(mAudioManager != null) + mAudioManager.setParameters("LEAMono=false"); + } + mLeaChannelMode = "stereo"; + break; + default: + break; + } + } + } + }; + + @Override + protected void create() { + Log.i(TAG, "create()"); + } + + @Override + protected boolean start() { + Log.i(TAG, "start()"); + String propValue; + + if (sAcmService != null) { + Log.w(TAG, "AcmService is already running"); + return true; + } + + // Step 1: Get AdapterService, AcmNativeInterface. + // None of them can be null. + mAdapter = BluetoothAdapter.getDefaultAdapter(); + mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), + "AdapterService cannot be null when AcmService starts"); + mAcmNativeInterface = Objects.requireNonNull(AcmNativeInterface.getInstance(), + "AcmNativeInterface cannot be null when AcmService starts"); + + mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), + "AdapterService cannot be null when StreamAudioService starts"); + + Log.i(TAG, "mAdapterService.isHostAdvAudioUnicastFeatureSupported() returned " + + mAdapterService.isHostAdvAudioUnicastFeatureSupported()); + Log.i(TAG, "mAdapterService.isHostAdvAudioStereoRecordingFeatureSupported() returned " + + mAdapterService.isHostAdvAudioStereoRecordingFeatureSupported()); + Log.i(TAG, "mAdapterService.isAdvUnicastAudioFeatEnabled() returned " + + mAdapterService.isAdvUnicastAudioFeatEnabled()); + + // SOC supports unicast, host supports unicast and stereo recording + if (mAdapterService.isHostAdvAudioUnicastFeatureSupported() && + mAdapterService.isHostAdvAudioStereoRecordingFeatureSupported() && + mAdapterService.isAdvUnicastAudioFeatEnabled()) { + + Log.i(TAG, "SOC supports unicast, host supports unicast, stereo recording"); + // set properties only if they are not set to allow user enable/disable + // the features explicitly + propValue = SystemProperties.get("persist.vendor.service.bt.bap.enable_ucast"); + + if (propValue == null || propValue.length() == 0 || !propValue.equals("false")) { + SystemProperties.set("persist.vendor.service.bt.bap.enable_ucast", "true"); + } else { + Log.i(TAG, "persist.vendor.service.bt.bap.enable_ucast is already set to " + + propValue); + } + + propValue = SystemProperties.get("persist.vendor.service.bt.recording_supported"); + if (propValue == null || propValue.length() == 0 || !propValue.equals("false")) { + SystemProperties.set("persist.vendor.service.bt.recording_supported", "true"); + Log.i(TAG, "persist.vendor.service.bt.recording_supported set to true"); + } else { + Log.i(TAG, "persist.vendor.service.bt.recording_supported is already set to " + + propValue); + } + } + + Log.i(TAG, "mAdapterService.isHostQHSFeatureSupported() returned " + + mAdapterService.isHostQHSFeatureSupported()); + + // SOC supports unicast, host supports unicast and QHS + if (mAdapterService.isHostAdvAudioUnicastFeatureSupported() && + mAdapterService.isHostQHSFeatureSupported() && + mAdapterService.isAdvUnicastAudioFeatEnabled()) { + + Log.i(TAG, "SOC supports unicast, host supports unicast, QHS"); + // set properties only if they are not set to allow user enable/disable + // the features explicitly + propValue = SystemProperties.get("persist.vendor.btstack.qhs_enable"); + + if (propValue == null || propValue.length() == 0 || !propValue.equals("false")) { + SystemProperties.set("persist.vendor.btstack.qhs_enable", "true"); + } else { + Log.i(TAG, "persist.vendor.service.bt.bap.enable_ucast is already set to " + + propValue); + } + } + + Log.i(TAG, "isHostAdvAudioLC3QFeatureSupported(): " + + mAdapterService.isHostAdvAudioLC3QFeatureSupported()); + + // SOC supports unicast, host supports unicast and LC3Q + if (mAdapterService.isHostAdvAudioUnicastFeatureSupported() && + mAdapterService.isHostAdvAudioLC3QFeatureSupported() && + mAdapterService.isAdvUnicastAudioFeatEnabled()) { + + Log.i(TAG, "host supports LC3Q"); + // set properties only if they are not set to allow user enable/disable + // the features explicitly + propValue = SystemProperties.get("persist.vendor.service.bt.is_lc3q_supported"); + + if (propValue == null || propValue.length() == 0 || !propValue.equals("false")) { + SystemProperties.set("persist.vendor.service.bt.is_lc3q_supported", "true"); + } else { + Log.i(TAG, "persist.vendor.service.bt.is_lc3q_supported is already set to " + + propValue); + } + } + + // Step 2: Get maximum number of connected audio devices + mMaxConnectedAudioDevices = mAdapterService.getMaxConnectedAudioDevices(); + Log.i(TAG, "Max connected audio devices set to " + mMaxConnectedAudioDevices); + + + String LeaChannelMode = SystemProperties.get("persist.vendor.btstack.Lea.defaultchannelmode"); + if (!LeaChannelMode.isEmpty() && "mono".equals(LeaChannelMode)) { + mLeaChannelMode = "mono"; + } + Log.d(TAG, "Default LEA ChannelMode: " + LeaChannelMode); + // Step 3: Start handler thread for state machines + mStateMachines.clear(); + mStateMachinesThread = new HandlerThread("AcmService.StateMachines"); + mStateMachinesThread.start(); + + // Step 4: Setup codec config + mAcmCodecConfig = new AcmCodecConfig(this, mAcmNativeInterface); + + if (mAdapterService.isAdvUnicastAudioFeatEnabled()) { + Log.d(TAG, "Initialize AcmNativeInterface"); + // Step 5: Initialize native interface + mAcmNativeInterface.init(mMaxConnectedAudioDevices, + mAcmCodecConfig.codecConfigPriorities()); + } + + // Step 6: Setup broadcast receivers + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + mBondStateChangedReceiver = new BondStateChangedReceiver(); + registerReceiver(mBondStateChangedReceiver, filter); + synchronized (mAudioManagerLock) { + mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + Objects.requireNonNull(mAudioManager, + "AudioManager cannot be null when AcmService starts"); + mAudioStreamMax = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + } + // Step 7: Mark service as started + setAcmService(this); + + //step 8: Register CSIP module + mCsipManager = new CsipManager(); + + //step 9: Get Vcp Controller + mVcpController = VcpController.make(this); + Objects.requireNonNull(mVcpController, "mVcpController cannot be null when AcmService starts"); + return true; + } + + @Override + protected boolean stop() { + Log.i(TAG, "stop()"); + if (sAcmService == null) { + Log.w(TAG, "stop() called before start()"); + return true; + } + + // Step 9: do quit Vcp Controller + if (mVcpController != null) { + mVcpController.doQuit(); + } + + // Step 8: Mark service as stopped + setAcmService(null); + + unregisterReceiver(mBondStateChangedReceiver); + mBondStateChangedReceiver = null; + // Step 6: Cleanup native interface + mAcmNativeInterface.cleanup(); + mAcmNativeInterface = null; + + // Step 5: Clear codec config + mAcmCodecConfig = null; + + // Step 4: Destroy state machines and stop handler thread + synchronized (mStateMachines) { + for (AcmStateMachine sm : mStateMachines.values()) { + sm.doQuit(); + sm.cleanup(); + } + mStateMachines.clear(); + } + mStateMachinesThread.quitSafely(); + mStateMachinesThread = null; + + // Step 2: Reset maximum number of connected audio devices + mMaxConnectedAudioDevices = 1; + + // Step 1: Clear AdapterService, AcmNativeInterface, AudioManager + mAcmNativeInterface = null; + mAdapterService = null; + if (mAcmDevices != null) + mAcmDevices.clear(); + + mCsipManager.unregisterCsip(); + mCsipManager = null; + return true; + } + + @Override + protected void cleanup() { + Log.i(TAG, "cleanup()"); + } + + @Override + protected IProfileServiceBinder initBinder() { + return new AcmBinder(this); + } + + private class BluetoothAcmDevice { + private BluetoothDevice mGrpDevice; // group bd address + private int mState; + private int msetID; + + BluetoothAcmDevice(BluetoothDevice device, int state, int setID) { + mGrpDevice = device; + mState = state; + msetID = setID; + } + } + + private BluetoothDevice getAddressFromString(String address) { + return mAdapter.getRemoteDevice(address); + } + + public BluetoothDevice makeGroupBdAddress(BluetoothDevice device, int state, int setid) { + Log.i(TAG, " Set id : " + setid + " Num of connected acm devices: " + mAcmDevices.size()); + boolean setIdMatched = false; + if (setid == INVALID_SET_ID) { + Log.d(TAG, "Device is not part of any group"); + BluetoothAcmDevice acmDevice = new BluetoothAcmDevice(device, state, setid); + mAcmDevices.put(device, acmDevice); + mGroupBdAddress = acmDevice.mGrpDevice; + return mGroupBdAddress; + } + // BluetoothDevice bdaddr = null; + if (mAcmDevices == null) { + Log.d(TAG, "Hash Map is NULL"); + return mGroupBdAddress; + } + if (mAcmDevices.containsKey(device)) { + Log.d(TAG, "Device is available in Hash Map"); + BluetoothAcmDevice acmDevice = mAcmDevices.get(device); + mGroupBdAddress = acmDevice.mGrpDevice; + return mGroupBdAddress; + } + if (mAcmDevices.size() != 0) { + for (BluetoothDevice dm : mAcmDevices.keySet()) { + BluetoothAcmDevice d = mAcmDevices.get(dm); + if (d.msetID == setid) { + setIdMatched = true; + Log.d(TAG, "Device is part of same set ID"); + BluetoothAcmDevice acmDevice = new BluetoothAcmDevice(d.mGrpDevice, state, setid); + mAcmDevices.put(device, acmDevice); + mGroupBdAddress = acmDevice.mGrpDevice; + break; + } + } + } + if (!setIdMatched) { + Log.d(TAG, "create new group or device is not part of existing set ID"); + String address = "9E:8B:00:00:00:0"; + BluetoothDevice bdaddr = getAddressFromString(address + setid); + BluetoothAcmDevice acmDevice = new BluetoothAcmDevice(bdaddr, state, setid); + mAcmDevices.put(device, acmDevice); + mGroupBdAddress = bdaddr; + } + return mGroupBdAddress; + } + + public void handleAcmDeviceStateChange(BluetoothDevice device, int state, int setid) { + Log.d(TAG, "handleAcmDeviceStateChange: device: " + device + ", state: " + state + + " Set id : " + setid); + Log.i(TAG, " Num of connected ACM devices: " + mAcmDevices.size()); + boolean update = false; + if (device == null || mAcmDevices.size() == 0) + return; + BluetoothAcmDevice acmDevice = mAcmDevices.get(device); + //check if current active group address is same as this device group address + if (acmDevice != null && mGroupBdAddress != acmDevice.mGrpDevice) { + Log.d(TAG, "Inactive device is disconnected"); + update = true; + } + if (state == BluetoothProfile.STATE_DISCONNECTED) { + Log.d(TAG, "Remove Device from hash map"); + mAcmDevices.remove(device); + } else { + acmDevice.mState = state; + Log.d(TAG, "Update state"); + } + for (BluetoothDevice dm : mAcmDevices.keySet()) { + BluetoothAcmDevice d = mAcmDevices.get(dm); + if (d.msetID == setid) { + if (d.mState == BluetoothProfile.STATE_CONNECTED) { + update = true; + Log.d(TAG, "Atleast one member is connected"); + break; + } + } + } + if (!update) { + /*if (!mAcmNativeInterface.setActiveDevice(null, 0)) {//send unknown context type + Log.w(TAG, "setActiveDevice(null): Cannot remove active device in native layer"); + }*/ + } + } + + public static synchronized AcmService getAcmService() { + if (sAcmService == null) { + Log.w(TAG, "getAcmService(): service is null"); + return null; + } + if (!sAcmService.isAvailable()) { + Log.w(TAG, "getAcmService(): service is not available"); + return null; + } + return sAcmService; + } + + private static synchronized void setAcmService(AcmService instance) { + if (DBG) { + Log.d(TAG, "setAcmService(): set to: " + instance); + } + sAcmService = instance; + } + + public boolean connect(BluetoothDevice device, int contextType, + int profileType, int preferredContext) { + + if (DBG) { + Log.d(TAG, "connect(): " + device + " contextType: " + contextType + + " profileType: " + profileType + " preferredContext: " + preferredContext); + } + if (device.getAddress().contains("9E:8B:00:00:00")) { + Log.d(TAG, "Connect request for group"); + byte[] addrByte = Utils.getByteAddress(device); + int set_id = addrByte[5]; + List d = mCsipManager.getSetMembers(set_id); + if (d == null) { + Log.d(TAG, "No set member found"); + return false; + } + Iterator members = d.iterator(); + if (members != null) { + while (members.hasNext()) { + BluetoothDevice addr = members.next(); + Log.d(TAG, "connect member: " + addr); + synchronized (mStateMachines) { + if (!connectionAllowedCheckMaxDevices(addr)) { + // when mMaxConnectedAudioDevices is one, disconnect current device first. + if (mMaxConnectedAudioDevices == 1) { + List sinks = getDevicesMatchingConnectionStates( + new int[] {BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}); + for (BluetoothDevice sink : sinks) { + if (sink.equals(addr)) { + Log.w(TAG, "Connecting to device " + addr + " : disconnect skipped"); + continue; + } + disconnect(sink, contextType); + } + } else { + Log.e(TAG, "Cannot connect to " + addr + " : too many connected devices"); + return false; + } + } + AcmStateMachine smConnect = getOrCreateStateMachine(addr); + if (smConnect == null) { + Log.e(TAG, "Cannot connect to " + addr + " : no state machine"); + return false; + } + Message msg = smConnect.obtainMessage(AcmStateMachine.CONNECT); + msg.obj = preferredContext; + msg.arg1 = contextType; + msg.arg2 = profileType; + smConnect.sendMessage(msg); + } + } + } + return true; + } + synchronized (mStateMachines) { + if (!connectionAllowedCheckMaxDevices(device)) { + // when mMaxConnectedAudioDevices is one, disconnect current device first. + if (mMaxConnectedAudioDevices == 1) { + List sinks = getDevicesMatchingConnectionStates( + new int[] {BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}); + for (BluetoothDevice sink : sinks) { + if (sink.equals(device)) { + Log.w(TAG, "Connecting to device " + device + " : disconnect skipped"); + continue; + } + disconnect(sink, contextType); + } + } else { + Log.e(TAG, "Cannot connect to " + device + " : too many connected devices"); + return false; + } + } + AcmStateMachine smConnect = getOrCreateStateMachine(device); + if (smConnect == null) { + Log.e(TAG, "Cannot connect to " + device + " : no state machine"); + return false; + } + Message msg = smConnect.obtainMessage(AcmStateMachine.CONNECT); + msg.obj = preferredContext; + msg.arg1 = contextType; + msg.arg2 = profileType; + smConnect.sendMessage(msg); + return true; + } + } + + /** + * Disconnects Acm for the remote bluetooth device + * + * @param device is the device with which we would like to disconnect acm + * @return true if profile disconnected, false if device not connected over acm + */ + public boolean disconnect(BluetoothDevice device, int contextType) { + + if (DBG) { + Log.d(TAG, "disconnect(): " + device); + } + + if (device.getAddress().contains("9E:8B:00:00:00")) { + Log.d(TAG, "Disonnect request for group"); + byte[] addrByte = Utils.getByteAddress(device); + int set_id = addrByte[5]; + List d = mCsipManager.getSetMembers(set_id); + if (d == null) { + Log.d(TAG, "No set member found"); + return false; + } + Iterator members = d.iterator(); + if (members != null) { + while (members.hasNext()) { + BluetoothDevice addr = members.next(); + Log.d(TAG, "disconnect member: " + device); + synchronized (mStateMachines) { + AcmStateMachine sm = mStateMachines.get(addr); + if (sm == null) { + Log.e(TAG, "Ignored disconnect request for " + addr + " : no state machine"); + return false; + } + Message msg = sm.obtainMessage(AcmStateMachine.DISCONNECT); + msg.obj = contextType; + sm.sendMessage(msg); + } + } + return true; + } + } + synchronized (mStateMachines) { + AcmStateMachine sm = mStateMachines.get(device); + if (sm == null) { + Log.e(TAG, "Ignored disconnect request for " + device + " : no state machine"); + return false; + } + Message msg = sm.obtainMessage(AcmStateMachine.DISCONNECT); + msg.obj = contextType; + sm.sendMessage(msg); + return true; + } + } + + public List getConnectedDevices() { + + synchronized (mStateMachines) { + List devices = new ArrayList<>(); + for (AcmStateMachine sm : mStateMachines.values()) { + if (sm.isConnected()) { + devices.add(sm.getDevice()); + } + } + return devices; + } + } + + //check if it can be a list ? + public BluetoothDevice getCsipLockRequestedDevice() { + + synchronized (mStateMachines) { + BluetoothDevice device = null; + for (AcmStateMachine sm : mStateMachines.values()) { + if (sm.isCsipLockRequested()) { + device = sm.getDevice(); + } + } + return device; + } + } + + public boolean IsLockSupportAvailable(BluetoothDevice device) { + boolean isLockSupported = false; + /*int setId = mSetCoordinator.getRemoteSetId(device, ACM_UUID); + DeviceGroup set = mSetCoordinator.getDeviceGroup(setId); + isLockSupported = set.mLockSupport;*/ + //isLockSupported = mAdapterService.isCsipLockSupport(device); + Log.d(TAG, "Exclusive Access SupportAvaible for:" + device + "returns " + isLockSupported); + return isLockSupported; + } + + /** + * Check whether can connect to a peer device. + * The check considers the maximum number of connected peers. + * + * @param device the peer device to connect to + * @return true if connection is allowed, otherwise false + */ + private boolean connectionAllowedCheckMaxDevices(BluetoothDevice device) { + int connected = 0; + // Count devices that are in the process of connecting or already connected + synchronized (mStateMachines) { + for (AcmStateMachine sm : mStateMachines.values()) { + switch (sm.getConnectionState()) { + case BluetoothProfile.STATE_CONNECTING: + case BluetoothProfile.STATE_CONNECTED: + if (Objects.equals(device, sm.getDevice())) { + return true; // Already connected or accounted for + } + connected++; + break; + default: + break; + } + } + } + return (connected < mMaxConnectedAudioDevices); + } + + List getDevicesMatchingConnectionStates(int[] states) { + + List devices = new ArrayList<>(); + if (states == null) { + return devices; + } + final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); + if (bondedDevices == null) { + return devices; + } + synchronized (mStateMachines) { + for (BluetoothDevice device : bondedDevices) { + /*if (!ArrayUtils.contains(mAdapterService.getRemoteUuids(device), + BluetoothUuid.ACM_SINK)) { + continue; + }*/ + int connectionState = BluetoothProfile.STATE_DISCONNECTED; + AcmStateMachine sm = mStateMachines.get(device); + if (sm != null) { + connectionState = sm.getConnectionState(); + } + for (int state : states) { + if (connectionState == state) { + devices.add(device); + break; + } + } + } + return devices; + } + } + + /** + * Get the list of devices that have state machines. + * + * @return the list of devices that have state machines + */ + @VisibleForTesting + List getDevices() { + List devices = new ArrayList<>(); + synchronized (mStateMachines) { + for (AcmStateMachine sm : mStateMachines.values()) { + devices.add(sm.getDevice()); + } + return devices; + } + } + + public int getConnectionState(BluetoothDevice device) { + + synchronized (mStateMachines) { + AcmStateMachine sm = mStateMachines.get(device); + if (sm == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + return sm.getConnectionState(); + } + } + + public int getCsipConnectionState(BluetoothDevice device) { + + synchronized (mStateMachines) { + AcmStateMachine sm = mStateMachines.get(device); + if (sm == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + return sm.getCsipConnectionState(); + } + } + + // Handle messages from native (JNI) to Java + void messageFromNative(AcmStackEvent stackEvent) { + Objects.requireNonNull(stackEvent.device, + "Device should never be null, event: " + stackEvent); + synchronized (mStateMachines) { + BluetoothDevice device = stackEvent.device; + AcmStateMachine sm = mStateMachines.get(device); + if (sm == null) { + if (stackEvent.type == AcmStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { + switch (stackEvent.valueInt1) { + case AcmStackEvent.CONNECTION_STATE_CONNECTED: + case AcmStackEvent.CONNECTION_STATE_CONNECTING: + // Create a new state machine only when connecting to a device + if (!connectionAllowedCheckMaxDevices(device)) { + Log.e(TAG, "Cannot connect to " + device + + " : too many connected devices"); + return; + } + sm = getOrCreateStateMachine(device); + break; + default: + break; + } + } + } + if (sm == null) { + Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent); + return; + } + sm.sendMessage(AcmStateMachine.STACK_EVENT, stackEvent); + } + } + + private AcmStateMachine getOrCreateStateMachine(BluetoothDevice device) { + if (device == null) { + Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null"); + return null; + } + synchronized (mStateMachines) { + AcmStateMachine sm = mStateMachines.get(device); + if (sm != null) { + return sm; + } + // Limit the maximum number of state machines to avoid DoS attack + if (mStateMachines.size() >= MAX_ACM_STATE_MACHINES) { + Log.e(TAG, "Maximum number of ACM state machines reached: " + + MAX_ACM_STATE_MACHINES); + return null; + } + if (DBG) { + Log.d(TAG, "Creating a new state machine for " + device); + } + sm = AcmStateMachine.make(device, this, mAcmNativeInterface, + mStateMachinesThread.getLooper()); + mStateMachines.put(device, sm); + return sm; + } + } + + private class BondStateChangedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { + return; + } + int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.ERROR); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); + bondStateChanged(device, state); + } + } + + /** + * Process a change in the bonding state for a device. + * + * @param device the device whose bonding state has changed + * @param bondState the new bond state for the device. Possible values are: + * {@link BluetoothDevice#BOND_NONE}, + * {@link BluetoothDevice#BOND_BONDING}, + * {@link BluetoothDevice#BOND_BONDED}. + */ + @VisibleForTesting + void bondStateChanged(BluetoothDevice device, int bondState) { + if (DBG) { + Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState); + } + // Remove state machine if the bonding for a device is removed + if (bondState != BluetoothDevice.BOND_NONE) { + return; + } + synchronized (mStateMachines) { + AcmStateMachine sm = mStateMachines.get(device); + if (sm == null) { + return; + } + if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { + return; + } + } + removeStateMachine(device); + } + + private void removeStateMachine(BluetoothDevice device) { + synchronized (mStateMachines) { + AcmStateMachine sm = mStateMachines.get(device); + if (sm == null) { + Log.w(TAG, "removeStateMachine: device " + device + + " does not have a state machine"); + return; + } + Log.i(TAG, "removeStateMachine: removing state machine for device: " + device); + sm.doQuit(); + sm.cleanup(); + mStateMachines.remove(device); + mAcmDevices.remove(device); + } + } + + void updateLeaChannelMode(int state, BluetoothDevice device) { + BluetoothDevice peerLeaDevice = null; + peerLeaDevice = getLeaPeerDevice(device); + if (peerLeaDevice == null) { + Log.d(TAG, "updateLeaChannelMode: peer device is NULL"); + return; + } + Log.d(TAG, "LeaChannelMode: " + mLeaChannelMode + "state: " + state); + synchronized(mBtLeaLock) { + if ("mono".equals(mLeaChannelMode)) { + if ((state == BluetoothA2dp.STATE_PLAYING) && (peerLeaDevice!= null) + && peerLeaDevice.isConnected() && isAcmPlayingMusic(peerLeaDevice)) { + Log.d(TAG, "updateLeaChannelMode: send delay message to set stereo "); + Message msg = mHandler.obtainMessage(SET_EBSTEREO_CFG); + mHandler.sendMessageDelayed(msg, StereoCfg_Timeout); + } else if (state == BluetoothA2dp.STATE_PLAYING) { + Log.d(TAG, "updateLeaChannelMode: setparameters to Mono"); + synchronized (mAudioManagerLock) { + if (mAudioManager != null) { + Log.d(TAG, "updateLeaChannelMode: Acquired mVariableLock"); + mAudioManager.setParameters("LeaChannelConfig=mono"); + } + } + Log.d(TAG, "updateLeaChannelMode: Released mVariableLock"); + } + if ((state == BluetoothA2dp.STATE_NOT_PLAYING) && + isAcmPlayingMusic(peerLeaDevice)) { + if (mHandler.hasMessages(StereoCfg_Timeout)) { + Log.d(TAG, "updateLeaChannelMode:remove delay message for stereo"); + mHandler.removeMessages(StereoCfg_Timeout); + } + } + } else if ("stereo".equals(mLeaChannelMode)) { + if ((state == BluetoothA2dp.STATE_PLAYING) && + (getConnectionState(peerLeaDevice) != BluetoothProfile.STATE_CONNECTED + || !isAcmPlayingMusic(peerLeaDevice))) { + Log.d(TAG, "updateLeaChannelMode: send delay message to set mono"); + Message msg = mHandler.obtainMessage(SET_EBMONO_CFG); + mHandler.sendMessageDelayed(msg, MonoCfg_Timeout); + } + if ((state == BluetoothA2dp.STATE_PLAYING) && isAcmPlayingMusic(peerLeaDevice)) { + if (mHandler.hasMessages(SET_EBMONO_CFG)) { + Log.d(TAG, "updateLeaChannelMode: remove delay message to set mono"); + mHandler.removeMessages(SET_EBMONO_CFG); + } + } + if ((state == BluetoothA2dp.STATE_NOT_PLAYING) && isAcmPlayingMusic(peerLeaDevice)) { + Log.d(TAG, "setparameters to Mono"); + synchronized (mAudioManagerLock) { + if (mAudioManager != null) + mAudioManager.setParameters("LeaChannelConfig=mono"); + } + mLeaChannelMode = "mono"; + } + } + } + } + + private BluetoothDevice getLeaPeerDevice(BluetoothDevice device) { + synchronized (mStateMachines) { + AcmStateMachine sm = mStateMachines.get(device); + if (sm == null) { + return null; + } + return sm.getPeerDevice(); + } + } + + public boolean isPeerDeviceConnected(BluetoothDevice device, int setid) { + boolean isConnected = false; + if (mAcmDevices.size() != 0) { + for (BluetoothDevice dm : mAcmDevices.keySet()) { + BluetoothAcmDevice d = mAcmDevices.get(dm); + if ((d.msetID == setid) && !Objects.equals(dm, device)) { + if (d.mState == BluetoothProfile.STATE_CONNECTED) { + isConnected = true; + Log.d(TAG, "At least one member is in connected state"); + break; + } + } + } + } + return isConnected; + } + + public boolean isPeerDeviceStreamingMusic(BluetoothDevice device, int setid) { + boolean isStreaming = false; + if (mAcmDevices.size() != 0) { + for (BluetoothDevice dm : mAcmDevices.keySet()) { + BluetoothAcmDevice d = mAcmDevices.get(dm); + if ((d.msetID == setid) && !Objects.equals(dm, device)) { + if (d.mState == BluetoothProfile.STATE_CONNECTED) { + synchronized (mBtAcmLock) { + AcmStateMachine sm = mStateMachines.get(dm); + if (sm == null) { + return false; + } + if (sm.isMusicPlaying()) { + isStreaming = true; + Log.d(TAG, "At least one member is streaming for music"); + break; + } + } + } + } + } + } + return isStreaming; + } + + public boolean isShoPendingStop() { + Log.d(TAG, "isShoPendingStop " + mShoPend); + return mShoPend; + } + + public void resetShoPendingStop() { + mShoPend = false; + } + + public boolean isVoiceShoPendingStop() { + Log.d(TAG, "isVoiceShoPendingStop " + mVoiceShoPend); + return mVoiceShoPend; + } + + public void resetVoiceShoPendingStop() { + mVoiceShoPend = false; + } + + public BluetoothDevice getVoiceActiveDevice() { + return mActiveDeviceVoice; + } + + public void removePeersFromBgWl(BluetoothDevice device, int setid) { + synchronized (mStateMachines) { + BluetoothDevice d = null; + List members = mCsipManager.getSetMembers(setid); + if (members == null) { + Log.d(TAG, "No set member found"); + return; + } + Iterator i = members.iterator(); + if (i != null) { + while (i.hasNext()) { + d = i.next(); + if (!(Objects.equals(d, device))) { + Log.d(TAG, "Device: " + d); + AcmStateMachine sm = mStateMachines.get(d); + if (sm == null) { + return; + } + sm.removeDevicefromBgWL(); + } + } + } + } + } + + public boolean isAcmPlayingMusic(BluetoothDevice device) { + if (DBG) { + Log.d(TAG, "isAcmPlayingMusic(" + device + ")"); + } + synchronized (mBtAcmLock) { + AcmStateMachine sm = mStateMachines.get(device); + if (sm == null) { + return false; + } + return sm.isMusicPlaying(); + } + } + + public boolean isAcmPlayingVoice(BluetoothDevice device) { + if (DBG) { + Log.d(TAG, "isAcmPlayingVoice(" + device + ")"); + } + synchronized (mBtAcmLock) { + AcmStateMachine sm = mStateMachines.get(device); + if (sm == null) { + return false; + } + return sm.isVoicePlaying(); + } + } + + public BluetoothDevice getGroup(BluetoothDevice device) { + Log.d(TAG, "Get group address for (" + device + ")"); + if (device.getAddress().contains("9E:8B:00:00:00")) { + Log.d(TAG, "Called for group address"); + return device; + } + BluetoothDevice dm = null; + + if (mAcmDevices != null) { + Log.d(TAG, "Hash Map is not NULL"); + BluetoothAcmDevice d = mAcmDevices.get(device); + if (d != null) + dm = d.mGrpDevice; + } + if (dm == null) { + Log.d(TAG, "Group address is NULL, make New"); + int Id = mCsipManager.getCsipSetId(device, null /*ACM_UUID*/); //TODO: UUID what to set ? + dm = makeGroupBdAddress(device, BluetoothProfile.STATE_DISCONNECTED, Id); + } + return dm; + } + + public int setActiveDevice(BluetoothDevice device, int contextType, int profileType, boolean playReq) { + + Log.d(TAG, "setActiveDevice: " + device + " contextType: " + contextType + " profileType: " + profileType + + " play req: " + playReq + " mActiveDeviceProfile: " + mActiveDeviceProfile+ " mActiveDeviceVoiceProfile: " + mActiveDeviceVoiceProfile); + + if (Objects.equals(device, mActiveDevice) && contextType == CONTEXT_TYPE_MUSIC && (mActiveDeviceProfile == profileType)) { + Log.e(TAG, "setActiveDevice(" + device + "): already set to active for media and profileType same as active profile"); + return ActiveDeviceManagerService.ALREADY_ACTIVE; + } + if (Objects.equals(device, mActiveDeviceVoice) && contextType == CONTEXT_TYPE_VOICE && (mActiveDeviceVoiceProfile == profileType)) { + Log.e(TAG, "setActiveDevice(" + device + "): already set to active for voice and profileType same as active profile"); + return ActiveDeviceManagerService.ALREADY_ACTIVE; + } + if (contextType == CONTEXT_TYPE_MUSIC) { + mShoPend = false; + if ((device == null) && (mActiveDevice != null)) { + if (mActiveDevice.getAddress().contains("9E:8B:00:00:00")) { + byte[] addrByte = Utils.getByteAddress(mActiveDevice); + int set_id = addrByte[5]; + List members = mCsipManager.getSetMembers(set_id); + if (members == null) { + Log.d(TAG, "No set member found"); + } + Iterator i = members.iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice addr = i.next(); + Log.d(TAG, "isAcmPlayingMusic(addr) " + isAcmPlayingMusic(addr)); + if (isAcmPlayingMusic(addr)) { + mShoPend = true; + break; + } + } + } + } else { + Log.d(TAG, "TWM active device"); + mShoPend = isAcmPlayingMusic(mActiveDevice); + } + } + Log.d(TAG, "mShoPend " + mShoPend); + } else if (contextType == CONTEXT_TYPE_VOICE) { + mVoiceShoPend = false; + if (mActiveDeviceVoice != null) { + if (mActiveDeviceVoice.getAddress().contains("9E:8B:00:00:00")) { + byte[] addrByte = Utils.getByteAddress(mActiveDeviceVoice); + int set_id = addrByte[5]; + List members = mCsipManager.getSetMembers(set_id); + if (members == null) { + Log.d(TAG, "No set member found"); + } + Iterator i = members.iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice addr = i.next(); + Log.d(TAG, "isAcmPlayingVoice(addr) " + isAcmPlayingVoice(addr)); + if (isAcmPlayingVoice(addr)) { + mVoiceShoPend = true; + break; + } + } + } + } else { + Log.d(TAG, "TWM active device"); + mVoiceShoPend = isAcmPlayingVoice(mActiveDeviceVoice); + } + } + Log.d(TAG, "mVoiceShoPend " + mVoiceShoPend); + } + Log.d(TAG, "old mActiveDevice: " + mActiveDevice + " & old mActiveDeviceVoice: " + mActiveDeviceVoice); + + if (setActiveDeviceAcm(device, contextType, profileType)) { + if (contextType == CONTEXT_TYPE_MUSIC) { + mActiveDevice = device; + mActiveDeviceProfile = profileType; + } else if (contextType == CONTEXT_TYPE_VOICE) { + mActiveDeviceVoice = device; + mActiveDeviceVoiceProfile = profileType; + } + Log.d(TAG, "new mActiveDevice: " + mActiveDevice + " & new mActiveDeviceVoice: " + mActiveDeviceVoice); + if(!playReq) { + if (mShoPend || mVoiceShoPend) { + Log.d(TAG, "setActiveDevice(" + device + "): returns with status " + ActiveDeviceManagerService.SHO_PENDING); + return ActiveDeviceManagerService.SHO_PENDING; + } else { + Log.d(TAG, "setActiveDevice(" + device + "): returns with status " + ActiveDeviceManagerService.SHO_SUCCESS); + return ActiveDeviceManagerService.SHO_SUCCESS; + } + } else { + Log.d(TAG, "setActiveDevice(" + device + "): returns with status " + ActiveDeviceManagerService.SHO_PENDING); + return ActiveDeviceManagerService.SHO_PENDING; + } + } + Log.d(TAG, "setActiveDevice(" + device + "): returns with status " + ActiveDeviceManagerService.SHO_FAILED); + mShoPend = false; + mVoiceShoPend = false; + return ActiveDeviceManagerService.SHO_FAILED; + } + + private boolean setActiveDeviceAcm(BluetoothDevice device, int contextType, int profileType) { + Log.d(TAG, "setActiveDeviceAcm: " + device); + try { + mAcmNativeInterfaceLock.readLock().lock(); + if (mAcmNativeInterface != null && !mAcmNativeInterface.setActiveDevice(device, profileType)) { + Log.e(TAG, "setActiveDevice(" + device + "): Cannot set as active in native layer"); + return false; + } + } finally { + mAcmNativeInterfaceLock.readLock().unlock(); + } + Log.d(TAG, "setActiveDeviceAcm(" + device + "): returns true"); + return true; + } + + public void setCodecConfigPreference(BluetoothDevice device, + BluetoothCodecConfig codecConfig, int contextType) { + + if (DBG) { + Log.d(TAG, "setCodecConfigPreference(" + device + "): " + + Objects.toString(codecConfig)); + } + mAcmCodecConfig.setCodecConfigPreference(device, codecConfig, contextType); + } + + public void ChangeCodecConfigPreference(BluetoothDevice device, + String mesg) { + + if (DBG) { + Log.d(TAG, "ChangeCodecConfigPreference " + device + "string: " + mesg); + } + if (device == null) + return; + mAcmName = mesg; + synchronized (mStateMachines) { + if (mAcmDevices.size() != 0) { + for (BluetoothDevice dm : mAcmDevices.keySet()) { + BluetoothAcmDevice d = mAcmDevices.get(dm); + if (Objects.equals(device, d.mGrpDevice)) { + if (d.mState == BluetoothProfile.STATE_CONNECTED) { + AcmStateMachine sm = getOrCreateStateMachine(dm); + if (sm == null) { + //TODO:handle this case + continue; + } + Message msg = sm.obtainMessage(AcmStateMachine.CODEC_CONFIG_CHANGED); + msg.obj = d.msetID; + sm.sendMessage(msg); + } + } + } + } + } + } + + public boolean StartStream(BluetoothDevice device, int contextType) { + if (DBG) { + Log.d(TAG, "startStream for " + device+ " context type: " + contextType); + } + if (device == null) + return false; + synchronized (mStateMachines) { + if (mAcmDevices.size() != 0) { + for (BluetoothDevice dm : mAcmDevices.keySet()) { + BluetoothAcmDevice d = mAcmDevices.get(dm); + if (Objects.equals(device, d.mGrpDevice)) { + if (d.mState == BluetoothProfile.STATE_CONNECTED) { + AcmStateMachine sm = getOrCreateStateMachine(dm); + if (sm == null) { + //TODO:handle this case + continue; + } + Message msg = sm.obtainMessage(AcmStateMachine.START_STREAM); + msg.obj = contextType; + sm.sendMessage(msg); + } + } + } + } + } + return true; + } + + public boolean StopStream(BluetoothDevice device, int contextType) { + if (DBG) { + Log.d(TAG, "stopStream for " + device+ " context type: " + contextType); + } + if (device == null) + return false; + synchronized (mStateMachines) { + if (mAcmDevices.size() != 0) { + for (BluetoothDevice dm : mAcmDevices.keySet()) { + BluetoothAcmDevice d = mAcmDevices.get(dm); + if (Objects.equals(device, d.mGrpDevice)) { + if (d.mState == BluetoothProfile.STATE_CONNECTED) { + AcmStateMachine sm = getOrCreateStateMachine(dm); + if (sm == null) { + //TODO:handle this case + continue; + } + Message msg = sm.obtainMessage(AcmStateMachine.STOP_STREAM); + msg.obj = contextType; + sm.sendMessage(msg); + } + } + } + } + } + return true; + } + + public void setAbsoluteVolume(BluetoothDevice grpAddr, int volumeLevel, int audioType) { + //Convert volume level to VCP level before sending. + int vcpVolume = convertToVcpVolume(volumeLevel); + BluetoothDevice activeDevice = null; + Log.d(TAG, "AudioVolumeLevel " + volumeLevel + " vcpVolume " + vcpVolume); + + if (audioType == ApmConst.AudioFeatures.MEDIA_AUDIO || + audioType == ApmConst.AudioFeatures.CALL_AUDIO) { + ActiveDeviceManagerService activeDeviceManager = + ActiveDeviceManagerService.get(this); + activeDevice = activeDeviceManager.getActiveDevice(audioType); + Log.d(TAG, "activeDevice " + activeDevice + " grpAddr " + grpAddr + + " audioType " + audioType); + if (!grpAddr.equals(activeDevice)) { + Log.e(TAG, "Ignore setAbsoluteVolume for inactive device"); + return; + } + } else if (audioType == ApmConst.AudioFeatures.BROADCAST_AUDIO) { + Log.d(TAG, "No active device, grpAddr " + grpAddr + " is broadcast device or group"); + } else { + Log.e(TAG, "Invalid audio type for set volume, ignore this request"); + return; + } + + if (audioType == ApmConst.AudioFeatures.MEDIA_AUDIO) { + mActiveDeviceLocalMediaVol = volumeLevel; + } else if (audioType == ApmConst.AudioFeatures.CALL_AUDIO) { + mActiveDeviceLocalVoiceVol = volumeLevel; + } + + if (grpAddr.getAddress().contains("9E:8B:00:00:00")) { + Log.d(TAG, "setAbsoluteVolume for group addr, send abs vol to all members"); + byte[] addrByte = Utils.getByteAddress(grpAddr); + int set_id = addrByte[5]; + List members = mCsipManager.getSetMembers(set_id); + if (members == null) { + Log.d(TAG, "No set member found"); + return; + } + Iterator i = members.iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice addr = i.next(); + Log.d(TAG, "send vol to member: " + addr); + mVcpController.setAbsoluteVolume(addr, vcpVolume, audioType); + } + } + } else { + Log.d(TAG, "setAbsoluteVolume for single addr, send abs vol to " + grpAddr); + mVcpController.setAbsoluteVolume(grpAddr, vcpVolume, audioType); + } + } + + public void setMute(BluetoothDevice grpAddr, boolean enableMute) { + if (mActiveDevice == null) { + Log.d(TAG, "No active device set, this grpAddr " + grpAddr + " is broadcast group"); + } else { + Log.d(TAG, "mActiveDevice " + mActiveDevice + " grpAddr " + grpAddr); + } + + if (grpAddr.getAddress().contains("9E:8B:00:00:00")) { + Log.d(TAG, "setMute for group addr, send mute/unmute to all members"); + byte[] addrByte = Utils.getByteAddress(grpAddr); + int set_id = addrByte[5]; + List members = mCsipManager.getSetMembers(set_id); + if (members == null) { + Log.d(TAG, "No set member found"); + return; + } + Iterator i = members.iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice addr = i.next(); + Log.d(TAG, "send setMute to member: " + addr); + mVcpController.setMute(addr, enableMute); + } + } + } else { + Log.d(TAG, "setMute for single addr, send mute/unmute to " + grpAddr); + mVcpController.setMute(grpAddr, enableMute); + } + } + + public void onVolumeStateChanged(BluetoothDevice device, int vol, int audioType) { + VolumeManager service = VolumeManager.get(); + //Get or Make group address + BluetoothDevice grpDev = getGroup(device); + + if (audioType == ApmConst.AudioFeatures.BROADCAST_AUDIO) { + Log.d(TAG, "volume notification for Broadcast audio"); + } else if (audioType == ApmConst.AudioFeatures.MEDIA_AUDIO) { + Log.d(TAG, "volume notification for Media audio"); + } else if (audioType == ApmConst.AudioFeatures.CALL_AUDIO) { + Log.d(TAG, "volume notification for Call audio"); + } else { + // remote volume change case + Log.d(TAG, "Volume change from remote: " + device + " vcp vol " + vol); + audioType = service.getActiveAudioType(device); + } + + //Convert vol + int audioVolume = convertToAudioStreamVolume(vol); + Log.d(TAG, "vcp vol " + vol + " audioVolume " + audioVolume); + + if (audioType == ApmConst.AudioFeatures.BROADCAST_AUDIO) { + Log.d(TAG, "update volume to APM for Broadcast audio"); + service.onVolumeChange(grpDev, audioVolume, audioType); + } else if (audioType == ApmConst.AudioFeatures.MEDIA_AUDIO) { + //Check if new vol is diff than active group vol + Log.d(TAG, "new vol: " + audioVolume + " mActiveDeviceLocalMediaVol: " + mActiveDeviceLocalMediaVol); + if (mActiveDeviceLocalMediaVol != audioVolume) { + Log.d(TAG, "new vol is different than mActiveDeviceLocalMediaVol update APM"); + service.onVolumeChange(grpDev, audioVolume, audioType); + mActiveDeviceLocalMediaVol = audioVolume; + } else { + Log.d(TAG, "local active media vol same as device new vol, ignore sending to APM"); + } + } else if (audioType == ApmConst.AudioFeatures.CALL_AUDIO) { + Log.d(TAG, "new vol: " + audioVolume + " mActiveDeviceLocalVoiceVol: " + mActiveDeviceLocalVoiceVol); + if (mActiveDeviceLocalVoiceVol != audioVolume) { + Log.d(TAG, "new vol is different than mActiveDeviceLocalVoiceVol update APM"); + service.onVolumeChange(grpDev, audioVolume, audioType); + mActiveDeviceLocalVoiceVol = audioVolume; + } else { + Log.d(TAG, "local active call vol same as device new vol, ignore sending to APM"); + } + } else { + Log.d(TAG, "No audio is streaming and is inactive device, ignore sending to APM"); + } + + //Check if this device is group device, then take lock (ignored for now) and update vol to other members as well. + applyVolToOtherMembers(device, mCsipManager.getCsipSetId(device, null /*ACM_UUID*/), vol, audioType); + } + + public void onMuteStatusChanged(BluetoothDevice device, boolean isMuted) { + VolumeManager service = VolumeManager.get(); + //Get or Make group address + BluetoothDevice grpDev = getGroup(device); + //default context type + int c_type = CONTEXT_TYPE_UNKNOWN; + + Log.d(TAG, "isMuted " + isMuted + " mActiveDeviceIsMuted " + mActiveDeviceIsMuted); + + if (!mVcpController.isBroadcastDevice(device) && ((mActiveDevice == null) || (mActiveDevice != grpDev))) { + Log.d(TAG, "unicast-mode, either Active device null or muteStatus changed for inactive device -- return"); + return; + } + + if ((mActiveDevice == null) || (mActiveDevice != grpDev)) { + Log.d(TAG, "No Active device or device is not active, seems in Broadcast mode"); + c_type = CONTEXT_TYPE_BROADCAST_AUDIO; + } else { + Log.d(TAG, "mActiveDevice " + mActiveDevice + " grpDev " + grpDev + " device " + device); + c_type = CONTEXT_TYPE_MUSIC; + } + + //Check if new mute state is diff than active group mute state + if (mActiveDeviceIsMuted != isMuted) { + Log.d(TAG, "new mute state is different than mActiveDeviceIsMuted update APM"); + service.onMuteStatusChange(grpDev, isMuted, CONTEXT_TYPE_MUSIC); + mActiveDeviceIsMuted = isMuted; + } else { + Log.d(TAG, "local active device mute state same as device new mute state, ignore sending to APM, but send to other members"); + } + + //Check if this device is group device, then take lock(ignored for now) and update mute state to other members as well. + applyMuteStateToOtherMembers(device, mCsipManager.getCsipSetId(device, null /*ACM_UUID*/), isMuted); + } + + public void setAbsVolSupport(BluetoothDevice device, boolean isAbsVolSupported, int initialVol) { + VolumeManager service = VolumeManager.get(); + //Get or Make group address + BluetoothDevice grpDev = getGroup(device); + if (grpDev == null) { + Log.d(TAG, "Group not created, return"); + return; + } + if ((mVcpController.getConnectionState(device) == BluetoothProfile.STATE_CONNECTED) && isAbsVolSupported) { + if (!isVcpPeerDeviceConnected(device, mCsipManager.getCsipSetId(device, null /*ACM_UUID*/))) { + Log.d(TAG, "VCP Peer device not connected, this is 1st member, update support to APM "); + //get current vol from VCP and send to APM during connection. + Log.d(TAG, "VCP initialVol " + initialVol + " when connected device " + device); + Log.d(TAG, "Update APM with connection vol " + convertToAudioStreamVolume(initialVol)); + service.setAbsoluteVolumeSupport(grpDev, isAbsVolSupported, convertToAudioStreamVolume(initialVol), ApmConst.AudioProfiles.VCP); + + //get current mute state from VCP and sent to APM during connection. + Log.d(TAG, "VCP mute state " + mVcpController.isMute(device) + " when connected device " + device); + service.onMuteStatusChange(grpDev, mVcpController.isMute(device), CONTEXT_TYPE_MUSIC); + } else { + Log.d(TAG, "VCP Peer device connected, this is not 1st member, skip update to APM"); + } + } else if ((mVcpController.getConnectionState(device) == BluetoothProfile.STATE_DISCONNECTED) && !isAbsVolSupported) { + if (isVcpPeerDeviceConnected(device, mCsipManager.getCsipSetId(device, null /*ACM_UUID*/))) { + Log.d(TAG, "VCP Peer device connected, this is not last member, skip update to APM "); + } else { + Log.d(TAG, "VCP Peer device not connected, this is last member, update to APM"); + service.setAbsoluteVolumeSupport(grpDev, isAbsVolSupported, ApmConst.AudioProfiles.VCP); + } + } + } + + public boolean isVcpPeerDeviceConnected(BluetoothDevice device, int setid) { + boolean isVcpPeerConnected = false; + if (setid == INVALID_SET_ID) + return isVcpPeerConnected; + List members = mCsipManager.getSetMembers(setid); + if (members == null) { + Log.d(TAG, "No set member found"); + return isVcpPeerConnected; + } + Iterator i = members.iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice addr = i.next(); + if (!Objects.equals(addr, device) && (mVcpController.getConnectionState(addr) == BluetoothProfile.STATE_CONNECTED)) { + isVcpPeerConnected = true; + Log.d(TAG, "At least one other vcp member is in connected state"); + break; + } + } + } + return isVcpPeerConnected; + } + + private int convertToAudioStreamVolume(int volume) { + // Rescale volume to match AudioSystem's volume + return (int) Math.round((double) volume * mAudioStreamMax / VCP_MAX_VOL); + } + + private int convertToVcpVolume(int volume) { + return (int) Math.ceil((double) volume * VCP_MAX_VOL / mAudioStreamMax); + } + + private void applyVolToOtherMembers(BluetoothDevice device, int setid, int volume, int audioType) { + if (setid == INVALID_SET_ID) + return; + List members = mCsipManager.getSetMembers(setid); + if (members == null) { + Log.d(TAG, "No set member found"); + return; + } + Iterator i = members.iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice addr = i.next(); + if (!Objects.equals(addr, device)) { + Log.d(TAG, "send vol changed to addr " + addr); + mVcpController.setAbsoluteVolume(addr, volume, audioType); + } + } + } + } + + private void applyMuteStateToOtherMembers(BluetoothDevice device, int setid, boolean muteState) { + if (setid == INVALID_SET_ID) + return; + List members = mCsipManager.getSetMembers(setid); + if (members == null) { + Log.d(TAG, "No set member found"); + return; + } + Iterator i = members.iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice addr = i.next(); + if (!Objects.equals(addr, device)) { + Log.d(TAG, "send mute state to addr " + addr); + mVcpController.setMute(addr, muteState); + } + } + } + } + + public int getVcpConnState(BluetoothDevice device) { + return mVcpController.getConnectionState(device); + } + + public int getVcpConnMode(BluetoothDevice device) { + return mVcpController.getConnectionMode(device); + } + + public boolean isVcpMute(BluetoothDevice device) { + return mVcpController.isMute(device); + } + + public String getAcmName() { + return mAcmName; + } + + private static class AcmBinder extends Binder implements IProfileServiceBinder { + AcmBinder(AcmService service) { + } + @Override + public void cleanup() { + } + } + + private BluetoothGroupCallback mBluetoothGroupCallback = new BluetoothGroupCallback() { + public void onGroupClientAppRegistered(int status, int appId) { + Log.d(TAG, "onGroupClientAppRegistered, status: " + status + "appId: " + appId); + if (status == 0) { + mCsipManager.updateAppId(appId); + mIsCsipRegistered = true; + } else { + Log.e(TAG, "DeviceGroup registeration failed, status:" + status); + } + } + + public void onConnectionStateChanged (int state, BluetoothDevice device) { + Log.d(TAG, "onConnectionStateChanged: Device: " + device + "state: " + state); + //notify statemachine about device connection state + synchronized (mStateMachines) { + AcmStateMachine sm = getOrCreateStateMachine(device); + Message msg = sm.obtainMessage(AcmStateMachine.CSIP_CONNECTION_STATE_CHANGED); + msg.obj = state; + sm.sendMessage(msg); + } + } + + public void onExclusiveAccessChanged (int setId, int value, int status, + List devices) { + Log.d(TAG, "onExclusiveAccessChanged: setId" + setId + devices + "status:" + status); + + //notify the statemachine about CSIP Lock status + switch (status) { + case ALL_LOCKS_ACQUIRED: { + synchronized (mStateMachines) { + for (BluetoothDevice device : devices) { + AcmStateMachine sm = getOrCreateStateMachine(device); + if (sm == null) { + //TODO:handle this case + continue; + } + Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_LOCKED); + msg.obj = setId; + msg.arg1 = value; + sm.sendMessage(msg); + } + } + break; + } + + case SOME_LOCKS_ACQUIRED_REASON_TIMEOUT: //check if need to handle separately + case SOME_LOCKS_ACQUIRED_REASON_DISC: { + BluetoothDevice mDevice = getCsipLockRequestedDevice(); + if (devices.contains(mDevice)) { + synchronized (mStateMachines) { + for (BluetoothDevice device : devices) { + AcmStateMachine sm = getOrCreateStateMachine(device); + if (sm == null) { + //TODO:handle this case + continue; + } + Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_PARTIAL_LOCK); + msg.obj = setId; + msg.arg1 = value; + sm.sendMessage(msg); + } + } + } else { + Log.d(TAG, "Exclusive access requested device is not present in list, release access for all devices"); + mCsipManager.setLock(setId, devices, mCsipManager.UNLOCK); + } + } break; + + case LOCK_DENIED: { + synchronized (mStateMachines) { + for (BluetoothDevice device : devices) { + AcmStateMachine sm = getOrCreateStateMachine(device); + if (sm == null) { + //TODO:handle this case + continue; + } + Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_DENIED); + msg.obj = setId; + msg.arg1 = value; + sm.sendMessage(msg); + } + } + } break; + + case LOCK_RELEASED: { + synchronized (mStateMachines) { + for (BluetoothDevice device : devices) { + AcmStateMachine sm = getOrCreateStateMachine(device); + if (sm == null) { + //TODO:handle this case + continue; + } + Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_RELEASED); + msg.obj = setId; + msg.arg1 = value; + sm.sendMessage(msg); + } + } + } break; + + case LOCK_RELEASED_TIMEOUT: { + synchronized (mStateMachines) { + for (BluetoothDevice device : devices) { + AcmStateMachine sm = getOrCreateStateMachine(device); + if (sm == null) { + //TODO:handle this case + continue; + } + Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_RELEASED); + msg.obj = setId; + msg.arg1 = value; + sm.sendMessage(msg); + } + } + } break; + + /*case INVALID_REQUEST_PARAMS: { + synchronized (mStateMachines) { + for (BluetoothDevice device : devices) { + AcmStateMachine sm = getOrCreateStateMachine(device); + if (sm == null) { + //TODO:handle this case + continue; + } + Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_INVALID_PARAM); + msg.obj = setId; + msg.arg1 = value; + sm.sendMessage(msg); + } + } + } break;*/ + + /*case LOCK_RELEASE_NOT_ALLOWED: { + synchronized (mStateMachines) { + for (BluetoothDevice device : devices) { + AcmStateMachine sm = getOrCreateStateMachine(device); + if (sm == null) { + //TODO:handle this case + continue; + } + Message msg = sm.obtainMessage(AcmStateMachine.CSIP_LOCK_STATUS_RELEASE); + msg.obj = setId; + msg.arg1 = value; + sm.sendMessage(msg); + } + } + } break;*/ + + case INVALID_VALUE: + break; + } + } + + //public void onSetMemberFound (int setId, BluetoothDevice device) { } + + //public void onSetDiscoveryStatusChanged (int setId, int status, int reason) { } + + //public void onLockAvailable (int setId, BluetoothDevice device) { } + + //public void onNewSetFound (int setId, BluetoothDevice device, UUID uuid) { } + + //public void onLockStatusFetched (int setId, int lockStatus) { } + }; + + public CsipManager getCsipManager(){ + return mCsipManager; + } + + class CsipManager { + public int LOCK = 0; + public int UNLOCK = 0; + + int mCsipAppid; + + CsipManager() { + LOCK = BluetoothDeviceGroup.ACCESS_GRANTED; + UNLOCK = BluetoothDeviceGroup.ACCESS_RELEASED; + + registerCsip(); + } + + CsipManager(BluetoothGroupCallback callbacks) { + LOCK = BluetoothDeviceGroup.ACCESS_GRANTED; + UNLOCK = BluetoothDeviceGroup.ACCESS_RELEASED; + + registerCsip(callbacks); + } + + void updateAppId(int id) { + mCsipAppid = id; + } + + int getAppId() { + return mCsipAppid; + } + + void registerCsip() { + GroupService mGroupService = GroupService.getGroupService(); + if(mGroupService != null) { + Log.i(TAG, "mGroupService is not null, register"); + mGroupService.registerGroupClientModule(mBluetoothGroupCallback); + } + } + + void registerCsip(BluetoothGroupCallback callbacks) { + GroupService mGroupService = GroupService.getGroupService(); + if(mGroupService != null) { + Log.i(TAG, "mGroupService is not null, register " + callbacks); + mGroupService.registerGroupClientModule(callbacks); + } + } + + void unregisterCsip() { + GroupService mGroupService = GroupService.getGroupService(); + if (mGroupService != null) + mGroupService.unregisterGroupClientModule(mCsipAppid); + } + + void connectCsip(BluetoothDevice device) { + GroupService mGroupService = GroupService.getGroupService(); + mGroupService.connect(mCsipAppid, device); + } + + void disconnectCsip(BluetoothDevice device) { + GroupService mGroupService = GroupService.getGroupService(); + mGroupService.disconnect(mCsipAppid, device); + } + + void setLock(int setId, List devices, int lockVal) { + GroupService mGroupService = GroupService.getGroupService(); + mGroupService.setLockValue(mCsipAppid, setId, devices, lockVal); + } + + DeviceGroup getCsipSet(int setId) { + GroupService mGroupService = GroupService.getGroupService(); + return mGroupService.getCoordinatedSet(setId); + } + + List getSetMembers(int setId) { + DeviceGroup set = getCsipSet(setId); + if(set != null) + return set.getDeviceGroupMembers(); + return null; + } + + int getCsipSetId(BluetoothDevice device, ParcelUuid uuid) { + GroupService mGroupService = GroupService.getGroupService(); + return mGroupService.getRemoteDeviceGroupId(device, uuid); + //return -1; + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmStackEvent.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmStackEvent.java new file mode 100644 index 00000000000..a0c4c44b720 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmStackEvent.java @@ -0,0 +1,130 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +package com.android.bluetooth.acm; + +import android.bluetooth.BluetoothCodecStatus; +import android.bluetooth.BluetoothDevice; + +/** + * Stack event sent via a callback from JNI to Java, or generated + * internally by the ACM State Machine. + */ +public class AcmStackEvent { + // Event types for STACK_EVENT message (coming from native) + private static final int EVENT_TYPE_NONE = 0; + public static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1; + public static final int EVENT_TYPE_AUDIO_STATE_CHANGED = 2; + public static final int EVENT_TYPE_CODEC_CONFIG_CHANGED = 3; + + // Do not modify without updating the HAL bt_acm.h files. + // Match up with btacm_connection_state_t enum of bt_acm.h + static final int CONNECTION_STATE_DISCONNECTED = 0; + static final int CONNECTION_STATE_CONNECTING = 1; + static final int CONNECTION_STATE_CONNECTED = 2; + static final int CONNECTION_STATE_DISCONNECTING = 3; + // Match up with btacm_audio_state_t enum of bt_acm.h + static final int AUDIO_STATE_REMOTE_SUSPEND = 0; + static final int AUDIO_STATE_STOPPED = 1; + static final int AUDIO_STATE_STARTED = 2; + + // Match up with btacm_audio_state_t enum of bt_acm.h + static final int CONTEXT_TYPE_UNKNOWN = 0; + static final int CONTEXT_TYPE_MUSIC = 1; + static final int CONTEXT_TYPE_VOICE = 2; + static final int CONTEXT_TYPE_MUSIC_VOICE = 3; + + // Match up with btacm_audio_state_t enum of bt_acm.h + static final int PROFILE_TYPE_NONE = 0; + + public int type = EVENT_TYPE_NONE; + public BluetoothDevice device; + public int valueInt1 = 0; + public int valueInt2 = 0; + public BluetoothCodecStatus codecStatus; + + AcmStackEvent(int type) { + this.type = type; + } + + @Override + public String toString() { + // event dump + StringBuilder result = new StringBuilder(); + result.append("AcmStackEvent {type:" + eventTypeToString(type)); + result.append(", device:" + device); + result.append(", state:" + eventTypeValueIntToString(type, valueInt1)); + result.append(", context type:" + contextTypeValueIntToString(valueInt2)); + if (codecStatus != null) { + result.append(", codecStatus:" + codecStatus); + } + result.append("}"); + return result.toString(); + } + + private static String eventTypeToString(int type) { + switch (type) { + case EVENT_TYPE_NONE: + return "EVENT_TYPE_NONE"; + case EVENT_TYPE_CONNECTION_STATE_CHANGED: + return "EVENT_TYPE_CONNECTION_STATE_CHANGED"; + case EVENT_TYPE_AUDIO_STATE_CHANGED: + return "EVENT_TYPE_AUDIO_STATE_CHANGED"; + case EVENT_TYPE_CODEC_CONFIG_CHANGED: + return "EVENT_TYPE_CODEC_CONFIG_CHANGED"; + default: + return "EVENT_TYPE_UNKNOWN:" + type; + } + } + + private static String eventTypeValueIntToString(int type, int value) { + switch (type) { + case EVENT_TYPE_CONNECTION_STATE_CHANGED: + switch (value) { + case CONNECTION_STATE_DISCONNECTED: + return "DISCONNECTED"; + case CONNECTION_STATE_CONNECTING: + return "CONNECTING"; + case CONNECTION_STATE_CONNECTED: + return "CONNECTED"; + case CONNECTION_STATE_DISCONNECTING: + return "DISCONNECTING"; + default: + break; + } + break; + case EVENT_TYPE_AUDIO_STATE_CHANGED: + switch (value) { + case AUDIO_STATE_REMOTE_SUSPEND: + return "REMOTE_SUSPEND"; + case AUDIO_STATE_STOPPED: + return "STOPPED"; + case AUDIO_STATE_STARTED: + return "STARTED"; + default: + break; + } + break; + default: + break; + } + return Integer.toString(value); + } + + private static String contextTypeValueIntToString(int value) { + switch (value) { + case CONTEXT_TYPE_UNKNOWN: + return "UNKNOWN"; + case CONTEXT_TYPE_MUSIC: + return "MEDIA"; + case CONTEXT_TYPE_VOICE: + return "CONVERSATIONAL"; + case CONTEXT_TYPE_MUSIC_VOICE: + return "MEDIA+CONVERSATIONAL"; + default: + return "UNKNOWN:" + value; + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmStateMachine.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmStateMachine.java new file mode 100644 index 00000000000..eeedee6a0e3 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmStateMachine.java @@ -0,0 +1,2340 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +package com.android.bluetooth.acm; +//package com.android.bluetooth.apm; + +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothCodecStatus; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Intent; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import com.android.internal.util.IState; +import com.android.bluetooth.BluetoothStatsLog; +import com.android.bluetooth.btservice.ProfileService; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import java.util.UUID; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattService; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Scanner; +import android.os.SystemProperties; +import com.android.bluetooth.btservice.AdapterService; +import java.util.Objects; +import java.util.ArrayList; +import java.util.List; +import java.util.Iterator; +import com.android.bluetooth.apm.ApmConst; +import com.android.bluetooth.apm.StreamAudioService; +import com.android.bluetooth.vcp.VcpController; +import android.bluetooth.BluetoothVcp; + +final class AcmStateMachine extends StateMachine { + private static final boolean DBG = true; + private static final String TAG = "AcmStateMachine"; + private String mReconfig = ""; + public UUID uuid; + static final int CONNECT = 1; + static final int DISCONNECT = 2; + static final int CSIP_CONNECTION_STATE_CHANGED = 3; + static final int CSIP_LOCK_STATUS_LOCKED = 4; + static final int CSIP_LOCK_STATUS_PARTIAL_LOCK = 5; + static final int CSIP_LOCK_STATUS_DENIED = 6; + static final int CSIP_LOCK_STATUS_RELEASED = 7; + static final int CODEC_CONFIG_CHANGED = 8; + static final int GATT_CONNECTION_STATE_CHANGED = 9; + static final int GATT_CONNECTION_TIMEOUT = 10; + static final int START_STREAM = 11; + static final int STOP_STREAM = 12; + static final int START_STREAM_REQ = 13; + @VisibleForTesting + static final int STACK_EVENT = 101; + private static final int CONNECT_TIMEOUT = 201; + private static final int CSIP_LOCK_RELEASE_TIMEOUT = 301; + private static final int CSIP_LOCK_TIMEOUT = 401; + private static final int LE_AUDIO_AVAILABLE_LICENSED = 0x00000300; + static final int GATT_CONNECTION_TIMEOUT_MS = 30000; + static final int GATT_PEER_CONN_TIMEOUT = 8; + static final int GATT_CONN_FAILED_ESTABLISHED = 0x3E; + + @VisibleForTesting + static int sConnectTimeoutMs = 30000; // 30s + static int sCsipLockReleaseTimeoutMs = 5000; //TODO + static int sCsipLockTimeoutMs = 5000; + + private final int ACM_MAX_BYTES = 100; + + private final int CS_PARAM_NUM_BITS = 8; + private final int CS_PARAM_IND_MASK = 0xff; + private final int CS_PARAM_1ST_INDEX = 0x00; + private final int CS_PARAM_2ND_INDEX = 0x01; + private final int CS_PARAM_3RD_INDEX = 0x02; + private final int CS_PARAM_4TH_INDEX = 0x03; + private final int CS_PARAM_5TH_INDEX = 0x04; + private final int CS_PARAM_6TH_INDEX = 0x05; + private final int CS_PARAM_7TH_INDEX = 0x06; + private final int CS_PARAM_8TH_INDEX = 0x07; + + private final int CODEC_TYPE_LC3Q = 0x10; + + private static final String CODEC_NAME = "Codec"; + private static final String STREAM_MAP = "StreamMap"; + private static final String FRAME_DURATION = "FrameDuration"; + private static final String SDU_BLOCK = "Blocks_forSDU"; + private static final String RXCONFIG_INDX = "rxconfig_index"; + private static final String TXCONFIG_INDX = "txconfig_index"; + private static final String VERSION = "version"; + private static final String VENDOR_META_DATA = "vendor"; + private Disconnected mDisconnected; + private Connecting mConnecting; + private Disconnecting mDisconnecting; + private Connected mConnected; + private Streaming mStreaming; + private int mConnectionState = BluetoothProfile.STATE_DISCONNECTED; + private int mLastConnectionState = -1; + private int mMusicConnectionState = BluetoothProfile.STATE_DISCONNECTED; + private int mLastMusicConnectionState = -1; + private int mVoiceConnectionState = BluetoothProfile.STATE_DISCONNECTED; + private int mLastVoiceConnectionState = -1; + + private AcmService mAcmService; + + private AcmNativeInterface mAcmNativeInterface; + private BluetoothGatt mBluetoothGatt; + private final BluetoothDevice mDevice; + private BluetoothDevice mGroupAddress; + private boolean mIsMusicPlaying = false; + private boolean mIsVoicePlaying = false; + private VcpController mVcpController; + private BluetoothCodecStatus mMusicCodecStatus; + private BluetoothCodecStatus mVoiceCodecStatus; + + static final int CONTEXT_TYPE_UNKNOWN = 0; + static final int CONTEXT_TYPE_MUSIC = 1; + static final int CONTEXT_TYPE_VOICE = 2; + static final int CONTEXT_TYPE_MUSIC_VOICE = 3; + + private int mContextTypeToDisconnect = CONTEXT_TYPE_UNKNOWN; + private int mCurrentContextType = CONTEXT_TYPE_UNKNOWN; + private int mProfileType = 0; + private int mPreferredContext = CONTEXT_TYPE_UNKNOWN; + + private boolean IsDisconnectRequested = false; + private boolean IsReconfigRequested = false; + private int mCsipConnectionState = BluetoothProfile.STATE_DISCONNECTED; + private boolean mDeviceLocked = false; + private boolean mCsipLockRequested = false; + private boolean mIsDeviceWhitelisted = false; + private int mSetId; + private boolean mAcmMTUChangeRequested = false; + private int cached_state; + private boolean mIsUpdateProfDisConnection = false; + + AcmStateMachine(BluetoothDevice device, AcmService acmService, + AcmNativeInterface acmNativeInterface, Looper looper) { + super(TAG, looper); + setDbg(DBG); + mDevice = device; + mAcmService = acmService; + mAcmNativeInterface = acmNativeInterface; + + mDisconnected = new Disconnected(); + mConnecting = new Connecting(); + mDisconnecting = new Disconnecting(); + mConnected = new Connected(); + mStreaming = new Streaming(); + + addState(mDisconnected); + addState(mConnecting); + addState(mDisconnecting); + addState(mConnected); + addState(mStreaming); + + mCurrentContextType = CONTEXT_TYPE_UNKNOWN; + mDeviceLocked = false; + IsDisconnectRequested = false; + IsReconfigRequested = false; + mIsDeviceWhitelisted = false; + setInitialState(mDisconnected); + mSetId = -1; + cached_state = -1; + mAcmMTUChangeRequested = false; + mIsUpdateProfDisConnection = false; + + if (mBluetoothGatt != null) { + Log.e(TAG, "disconnect gatt"); + mBluetoothGatt.disconnect(); + mBluetoothGatt.close(); + mBluetoothGatt = null; + } + } + + static AcmStateMachine make(BluetoothDevice device, AcmService acmService, + AcmNativeInterface acmNativeInterface, Looper looper) { + Log.i(TAG, "make acm for device " + device); + AcmStateMachine acmSm = new AcmStateMachine(device, acmService, acmNativeInterface, + looper); + acmSm.start(); + return acmSm; + } + + public void doQuit() { + log("doQuit for device " + mDevice); + if (mIsMusicPlaying) { + // Stop if audio is still playing + log("doQuit: stopped Music playing " + mDevice); + mIsMusicPlaying = false; + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (service != null) + service.onStreamStateChange(mDevice, BluetoothA2dp.STATE_NOT_PLAYING, CONTEXT_TYPE_MUSIC); + } + if (mIsVoicePlaying) { + log("doQuit: stopped voice streaming " + mDevice); + mIsVoicePlaying = false; + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (service != null) + service.onStreamStateChange(mDevice, BluetoothA2dp.STATE_NOT_PLAYING, ApmConst.AudioFeatures.CALL_AUDIO); + } + quitNow(); + } + + public void cleanup() { + log("cleanup for device " + mDevice); + if (mBluetoothGatt != null) { + Log.e(TAG, "disconnect gatt"); + mBluetoothGatt.disconnect(); + mBluetoothGatt.close(); + mBluetoothGatt = null; + } + } + + private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { + @Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + Log.i(TAG, "onConnectionStateChange: Status: " + status + " newState: " + newState); + if (status == BluetoothGatt.GATT_SUCCESS /* || status == 0x16 */) { + if (newState == BluetoothProfile.STATE_CONNECTED) { + mBluetoothGatt.requestMtu(ACM_MAX_BYTES); + mAcmMTUChangeRequested = true; + cached_state = newState; + } else { + if (mAcmMTUChangeRequested == true) { + mAcmMTUChangeRequested = false; + } + + if (cached_state != -1) { + cached_state = -1; + } + + Message m = obtainMessage(GATT_CONNECTION_STATE_CHANGED); + m.obj = newState; + sendMessage(m); + } + } else if (status == GATT_PEER_CONN_TIMEOUT || + status == GATT_CONN_FAILED_ESTABLISHED) { + BluetoothDevice target = gatt.getDevice(); + Log.i(TAG, "[ACM] remote side disconnection, for device add in BG WL: " +target); + mBluetoothGatt.close(); + mBluetoothGatt = target.connectGatt(mAcmService, true, mGattCallback, + BluetoothDevice.TRANSPORT_LE, false, (BluetoothDevice.PHY_LE_1M_MASK | + BluetoothDevice.PHY_LE_2M_MASK | BluetoothDevice.PHY_LE_CODED_MASK), + null, true); + mIsDeviceWhitelisted = true; + } else { + mBluetoothGatt.close(); + mBluetoothGatt = null; + Log.i(TAG, "[ACM] Failed to connect GATT server."); + } + } + + @Override + public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { + log("onMtuChanged: mtu: " + mtu + + " mAcmMTUChangeRequested: " + mAcmMTUChangeRequested + + " cached_state: " + cached_state); + if (mAcmMTUChangeRequested == true) { + if (cached_state != -1) { + Message m = obtainMessage(GATT_CONNECTION_STATE_CHANGED); + m.obj = cached_state; + sendMessage(m); + mAcmMTUChangeRequested = false; + cached_state = -1; + } + } else { + log("onMtuChanged: Remote initiated trigger"); + //Do nothing + } + } + }; + + @VisibleForTesting + class Disconnected extends State { + @Override + public void enter() { + //Disconnect unicast VCP 1st + mVcpController = VcpController.getVcpController(); + if (mVcpController != null) { + Log.d(TAG, "Disconnect VCP for " + mDevice); + mVcpController.disconnect(mDevice, BluetoothVcp.MODE_UNICAST); + } else { + Log.d(TAG, "mVcpController is null"); + } + + Message currentMessage = getCurrentMessage(); + Log.i(TAG, "Enter Disconnected(" + mDevice + "): " + (currentMessage == null ? "null" + : messageWhatToString(currentMessage.what))); + synchronized (this) { + mConnectionState = BluetoothProfile.STATE_DISCONNECTED; + mMusicConnectionState = BluetoothProfile.STATE_DISCONNECTED; + mVoiceConnectionState = BluetoothProfile.STATE_DISCONNECTED; + } + removeDeferredMessages(DISCONNECT); + removeMessages(CSIP_LOCK_RELEASE_TIMEOUT); + removeMessages(GATT_CONNECTION_TIMEOUT); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (mLastMusicConnectionState != -1) { + // Don't broadcast during startup + if (mIsMusicPlaying) { + Log.i(TAG, "Disconnected: stop music streaming: " + mDevice); + mIsMusicPlaying = false; + service.onStreamStateChange(mDevice, BluetoothA2dp.STATE_NOT_PLAYING, CONTEXT_TYPE_MUSIC); + } + if (mLastVoiceConnectionState != -1) { + if (mIsVoicePlaying) { + Log.i(TAG, "Disconnected: stop voice streaming: " + mDevice); + mIsVoicePlaying = false; + service.onStreamStateChange(mDevice, BluetoothA2dp.STATE_NOT_PLAYING, ApmConst.AudioFeatures.CALL_AUDIO); + } + } + // ensure when one device is disconnected, need to update the channel mode. + mAcmService.updateLeaChannelMode(BluetoothA2dp.STATE_NOT_PLAYING, mDevice); + //Unlock, device state changed from connected to disconnected + + Log.i(TAG, " mIsUpdateProfDisConnection: " + mIsUpdateProfDisConnection); + if (mDevice.getBondState() == BluetoothDevice.BOND_NONE || + mIsUpdateProfDisConnection) { + Log.i(TAG, "Device is unpaired/mIsUpdateProfDisConnection has been set," + + " update disconnected to APM for " + mDevice); + mIsUpdateProfDisConnection = false; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, not last member "); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + if (mLastVoiceConnectionState != -1) + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + if (mLastVoiceConnectionState != -1) + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + mDeviceLocked = false; + //assuming disconnected state we are moving hence update AcmDevice hash map + mAcmService.handleAcmDeviceStateChange(mDevice, mConnectionState, mSetId); + } else if (mDeviceLocked) { + Log.i(TAG, "Access RELEASED for device " + mDevice); + List members = new ArrayList(); + members.add(mDevice); + mAcmService.getCsipManager().setLock(mSetId, members, mAcmService.getCsipManager().UNLOCK); + mDeviceLocked = false; + } else if (mCsipConnectionState == BluetoothProfile.STATE_CONNECTED) { + Log.i(TAG, "Device access is already RELEASED, Go for DeviceGroup Disconnect " + mDevice); + mAcmService.getCsipManager().disconnectCsip(mDevice); + } else { + if (mBluetoothGatt != null && !mIsDeviceWhitelisted) { + Log.d(TAG, "remove other devices from BG WL"); + mAcmService.removePeersFromBgWl(mDevice, mSetId); + Log.i(TAG, "Go for GATT Disconnect " + mDevice); + mBluetoothGatt.disconnect(); + } else { + Log.d(TAG, "mBluetoothGatt is NULL or device is in BG WL "); + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, not last member "); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + if (mLastVoiceConnectionState != -1) + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect"); + if (!mIsDeviceWhitelisted) { + mAcmService.removePeersFromBgWl(mDevice, mSetId); + Log.d(TAG, "remove other devices from BG WL"); + } + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + if (mLastVoiceConnectionState != -1) + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + //assuming disconnected state we are moving hence update AcmDevice hash map + mAcmService.handleAcmDeviceStateChange(mDevice, mConnectionState, mSetId); + } + } + } + } + + @Override + public void exit() { + Message currentMessage = getCurrentMessage(); + log("Exit Disconnected(" + mDevice + "): " + (currentMessage == null ? "null" + : messageWhatToString(currentMessage.what))); + mConnectionState = BluetoothProfile.STATE_DISCONNECTED; + mLastMusicConnectionState = BluetoothProfile.STATE_DISCONNECTED; + mLastVoiceConnectionState = BluetoothProfile.STATE_DISCONNECTED; + } + + @Override + public boolean processMessage(Message message) { + log("Disconnected process message(" + mDevice + "): " + + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: { + mCurrentContextType = message.arg1; + mProfileType = message.arg2; + mPreferredContext = (int)message.obj; + Log.i(TAG, "Connecting " + contextTypeToString(mCurrentContextType) + " to " + mDevice); + if (mAcmService.IsLockSupportAvailable(mDevice)) { + Log.d(TAG, "Exclusive Access support available, go for GATT connect"); + //if lock support available then go for CSIP connect + if (mBluetoothGatt != null) { + mBluetoothGatt.close(); + } + mBluetoothGatt = mDevice.connectGatt(mAcmService, false, mGattCallback, + BluetoothDevice.TRANSPORT_LE, false, (BluetoothDevice.PHY_LE_1M_MASK | + BluetoothDevice.PHY_LE_2M_MASK | BluetoothDevice.PHY_LE_CODED_MASK), + null, true); + + sendMessageDelayed(GATT_CONNECTION_TIMEOUT, GATT_CONNECTION_TIMEOUT_MS); + transitionTo(mConnecting); + break; + } else { + Log.d(TAG, "Exclusive Access support not available, go for GATT connect"); + if (mBluetoothGatt != null) { + mBluetoothGatt.close(); + } + mBluetoothGatt = mDevice.connectGatt(mAcmService, false, mGattCallback, + BluetoothDevice.TRANSPORT_LE, false, (BluetoothDevice.PHY_LE_1M_MASK | + BluetoothDevice.PHY_LE_2M_MASK | BluetoothDevice.PHY_LE_CODED_MASK), + null, true); + + sendMessageDelayed(GATT_CONNECTION_TIMEOUT, GATT_CONNECTION_TIMEOUT_MS); + transitionTo(mConnecting); + /*if (!mAcmNativeInterface.connectAcm(mDevice, message.arg1, message.arg2, (int)message.obj)) { + Log.e(TAG, "Disconnected: error connecting to " + mDevice); + break; + } + transitionTo(mConnecting);*/ + } + } break; + + case GATT_CONNECTION_STATE_CHANGED: { + removeMessages(GATT_CONNECTION_TIMEOUT); + int st = (int)message.obj; + if (st == BluetoothProfile.STATE_DISCONNECTED) { + Log.d(TAG, "GATT Disconnected"); + mIsDeviceWhitelisted = false; + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (mCurrentContextType == CONTEXT_TYPE_MUSIC) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, not last member "); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "Last member to disconnect MEDIA"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + } else if (mCurrentContextType == CONTEXT_TYPE_VOICE) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, not last member "); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } else if (mCurrentContextType == CONTEXT_TYPE_MUSIC_VOICE) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, not last member "); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect MEDIA+VOICE"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + //assuming disconneted state we are moving hence update AcmDevice hash map + mAcmService.handleAcmDeviceStateChange(mDevice, BluetoothProfile.STATE_DISCONNECTED, mSetId); + mBluetoothGatt.close(); + } else if (st == BluetoothProfile.STATE_CONNECTED) { + mIsDeviceWhitelisted = false; + Log.d(TAG, "GATT connected from background, go for profile connection"); + AdapterService mAdapterService = AdapterService.getAdapterService(); + if (mAdapterService != null && !mAdapterService.connectAllEnabledProfiles(mDevice)) { + Log.e(TAG, "Disconnected: error connecting to " + mDevice); + break; + } + break; + } + } break; + + case DISCONNECT: + Log.w(TAG, "Disconnected: DISCONNECT ignored: " + mDevice); + break; + + case GATT_CONNECTION_TIMEOUT: { + Log.d(TAG, "GATT connection Timeout"); + break; + } + + case CSIP_CONNECTION_STATE_CHANGED: + int state = (int)message.obj; + if (state == BluetoothProfile.STATE_DISCONNECTED) + mCsipConnectionState = BluetoothProfile.STATE_DISCONNECTED; + if (mBluetoothGatt != null) + mBluetoothGatt.disconnect(); + break; + + case CSIP_LOCK_STATUS_RELEASED: { + removeMessages(CSIP_LOCK_RELEASE_TIMEOUT); + //disconnect CSIP + int value = (int)message.arg1; + Log.d(TAG, "Exclusive Access state changed:" + value); + if (value == mAcmService.getCsipManager().UNLOCK) { + if (mCsipConnectionState == BluetoothProfile.STATE_CONNECTED) { + mAcmService.getCsipManager().disconnectCsip(mDevice); + } + } + mCsipLockRequested = false; + mDeviceLocked = false; + break; + } + + case STACK_EVENT: + AcmStackEvent event = (AcmStackEvent) message.obj; + log("Disconnected: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case AcmStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1, event.valueInt2); + break; + case AcmStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED: + processCodecConfigEvent(event.codecStatus, event.valueInt2); + break; + default: + Log.e(TAG, "Disconnected: ignoring stack event: " + event); + break; + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Disconnected state + private void processConnectionEvent(int state, int contextType) { + switch (state) { + case AcmStackEvent.CONNECTION_STATE_DISCONNECTED: + Log.w(TAG, "Ignore ACM DISCONNECTED event: " + mDevice); + break; + case AcmStackEvent.CONNECTION_STATE_CONNECTING:{ + // Reject the connection and stay in Disconnected state itself + Log.w(TAG, "Incoming A2DP Connecting request rejected: " + mDevice); + mAcmNativeInterface.disconnectAcm(mDevice, contextType); + } + break; + case AcmStackEvent.CONNECTION_STATE_CONNECTED: {//Shouldn't come + Log.w(TAG, "ACM Connected from Disconnected state: " + mDevice); + // Reject the connection and stay in Disconnected state itself + Log.w(TAG, "Incoming ACM Connected request rejected: " + mDevice); + mAcmNativeInterface.disconnectAcm(mDevice, contextType); + } + break; + case AcmStackEvent.CONNECTION_STATE_DISCONNECTING: + Log.w(TAG, "Ignore ACM DISCONNECTING event: " + mDevice); + break; + default: + Log.e(TAG, "Incorrect state: " + state + " device: " + mDevice); + break; + } + } + } + + @VisibleForTesting + class Connecting extends State { + @Override + public void enter() { + //Connect unicast VCP 1st + mVcpController = VcpController.getVcpController(); + if (mVcpController != null) { + Log.d(TAG, "Connect VCP for " + mDevice); + mVcpController.connect(mDevice, BluetoothVcp.MODE_UNICAST); + } else { + Log.d(TAG, "mVcpController is null"); + } + + Message currentMessage = getCurrentMessage(); + Log.i(TAG, "Enter Connecting(" + mDevice + "): " + (currentMessage == null ? "null" + : messageWhatToString(currentMessage.what))); + sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + //this context type here is global context type, if not based on current + //and prev states per context, form this contextType here + synchronized (this) { + mConnectionState = BluetoothProfile.STATE_CONNECTING; + } + mSetId = mAcmService.getCsipManager().getCsipSetId(mDevice, null /*ACM_UUID*/); //TODO: UUID what to set ? + mGroupAddress = mAcmService.getGroup(mDevice); + Log.d(TAG, "Group bd address " + mGroupAddress); + mAcmService.handleAcmDeviceStateChange(mDevice, mConnectionState, mSetId); + if (mCurrentContextType == CONTEXT_TYPE_MUSIC) { + mMusicConnectionState = BluetoothProfile.STATE_CONNECTING; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, not first member "); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "First member of group to connect MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + } else if (mCurrentContextType == CONTEXT_TYPE_VOICE) { + mVoiceConnectionState = BluetoothProfile.STATE_CONNECTING; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, not first member "); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "First member of group to connect VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } else if (mCurrentContextType == CONTEXT_TYPE_MUSIC_VOICE) { + mMusicConnectionState = BluetoothProfile.STATE_CONNECTING; + mVoiceConnectionState = BluetoothProfile.STATE_CONNECTING; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, not first member "); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "First member of group to connect MUSIC & VOICE"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + } + + @Override + public void exit() { + Message currentMessage = getCurrentMessage(); + log("Exit Connecting(" + mDevice + "): " + (currentMessage == null ? "null" + : messageWhatToString(currentMessage.what))); + if (mCurrentContextType == CONTEXT_TYPE_MUSIC) { + mLastMusicConnectionState = BluetoothProfile.STATE_CONNECTING; + } else if (mCurrentContextType == CONTEXT_TYPE_VOICE) { + mLastVoiceConnectionState = BluetoothProfile.STATE_CONNECTING; + } else if (mCurrentContextType == CONTEXT_TYPE_MUSIC_VOICE) { + mLastMusicConnectionState = BluetoothProfile.STATE_CONNECTING; + mLastVoiceConnectionState = BluetoothProfile.STATE_CONNECTING; + } + removeMessages(CONNECT_TIMEOUT); + } + + @Override + public boolean processMessage(Message message) { + log("Connecting process message(" + mDevice + "): " + + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: + deferMessage(message); // Do we need to defer this ? It shouldn't have come. + break; + case CONNECT_TIMEOUT: { + Log.w(TAG, "Connecting connection timeout: " + mDevice); + //check if CSIP is connected + if (mCsipConnectionState == BluetoothProfile.STATE_CONNECTED) + mAcmService.getCsipManager().disconnectCsip(mDevice); + mAcmNativeInterface.disconnectAcm(mDevice, mCurrentContextType); + AcmStackEvent event = + new AcmStackEvent(AcmStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); + event.device = mDevice; + event.valueInt1 = AcmStackEvent.CONNECTION_STATE_DISCONNECTED; + event.valueInt2 = mCurrentContextType; + sendMessage(STACK_EVENT, event); + break; + } + case DISCONNECT: { + // Cancel connection, shouldn't come + mContextTypeToDisconnect = (int)message.obj; + //check if disconnect is for individual context type + IState state = mDisconnected; + Log.i(TAG, "Connecting: connection canceled to " + mDevice); + mAcmNativeInterface.disconnectAcm(mDevice, mContextTypeToDisconnect); + if ((mMusicConnectionState == BluetoothProfile.STATE_CONNECTING) && + (mVoiceConnectionState == BluetoothProfile.STATE_CONNECTING)) { + if (mContextTypeToDisconnect != CONTEXT_TYPE_MUSIC_VOICE) { + /*only 1/2 contexts are being disconnected, + remain in connecting state but broadcast the connection state*/ + state = mConnecting; + } else { + //disconnect is for both context type then disconnect CSIP + if (mCsipConnectionState == BluetoothProfile.STATE_CONNECTED) + mAcmService.getCsipManager().disconnectCsip(mDevice); + } + } + processTransitionContextState(mConnecting, mDisconnected, mContextTypeToDisconnect); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (state == mConnecting) { + if (mContextTypeToDisconnect == CONTEXT_TYPE_MUSIC) + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + else if (mContextTypeToDisconnect == CONTEXT_TYPE_VOICE) + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + transitionTo(state); + } + break; + } + + case GATT_CONNECTION_STATE_CHANGED: { + removeMessages(GATT_CONNECTION_TIMEOUT); + int st = (int)message.obj; + if (st == BluetoothProfile.STATE_CONNECTED) { + mIsDeviceWhitelisted = false; + Log.d(TAG, "GATT connected, go for connect"); + if (!mAcmNativeInterface.connectAcm(mDevice, mCurrentContextType, mProfileType, mPreferredContext)) { + Log.e(TAG, "Disconnected: error connecting to " + mDevice); + break; + } + } else if (st == BluetoothProfile.STATE_DISCONNECTED) { + Log.d(TAG, "GATT Disconnected"); + mIsDeviceWhitelisted = false; + StreamAudioService service = StreamAudioService.getStreamAudioService(); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + transitionTo(mDisconnected); + } + /*if (st == BluetoothProfile.STATE_CONNECTED) { + Log.d(TAG, "GATT connected, go for DeviceGroup connect"); + mAcmService.getCsipManager().connectCsip(mDevice); + } else if (st == BluetoothProfile.STATE_DISCONNECTED) { + Log.d(TAG, "GATT Disconnected"); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + }*/ + } break; + + case GATT_CONNECTION_TIMEOUT: { + Log.d(TAG, "GATT connection Timeout"); + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Peer device is connected, add to BG WL"); + mIsDeviceWhitelisted = true; + mBluetoothGatt.close(); + + mBluetoothGatt = mDevice.connectGatt(mAcmService, true, mGattCallback, + BluetoothDevice.TRANSPORT_LE, false, (BluetoothDevice.PHY_LE_1M_MASK | + BluetoothDevice.PHY_LE_2M_MASK | BluetoothDevice.PHY_LE_CODED_MASK), + null, true); + + } else { + Log.d(TAG, "No member is in connected state, do not add in BG WL"); + mIsDeviceWhitelisted = false; + if (mBluetoothGatt != null) { + Log.e(TAG, "Disconnect gatt and make gatt instance null."); + mBluetoothGatt.disconnect(); + mBluetoothGatt.close(); + mBluetoothGatt = null; + } + } + transitionTo(mDisconnected); + break; + } + + case CSIP_CONNECTION_STATE_CHANGED: { + int state = (int)message.obj; + if (state == BluetoothProfile.STATE_CONNECTED) { + Log.e(TAG, "DeviceGroup Connected to " + mDevice); + mCsipConnectionState = BluetoothProfile.STATE_CONNECTED; + mSetId = mAcmService.getCsipManager().getCsipSetId(mDevice, null /*ACM_UUID*/); //TODO: UUID what to set ? + Iterator i = mAcmService.getCsipManager().getSetMembers(mSetId).iterator(); + List members = new ArrayList(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice device = i.next(); + if (mAcmService.getCsipConnectionState(device) == BluetoothProfile.STATE_CONNECTED) { + members.add(device); + } + } + } + //setlockvalue takes device list + mCsipLockRequested = true; + mAcmService.getCsipManager().setLock(mSetId, members, mAcmService.getCsipManager().LOCK); + } else { + Log.e(TAG, "DeviceGroup Connection failed to " + mDevice); + mCsipConnectionState = BluetoothProfile.STATE_DISCONNECTED; + mCsipLockRequested = false; + mDeviceLocked = false; + transitionTo(mDisconnected); + } + break; + } + + case CSIP_LOCK_STATUS_LOCKED: { + mCsipLockRequested = false; + int value = (int)message.arg1; + Log.d(TAG, "Exclusive Access state changed:" + value); + int setId = (int)message.obj; + int st = mAcmService.getCsipConnectionState(mDevice); + if ((mDeviceLocked && st == BluetoothProfile.STATE_CONNECTED)) { + Log.w(TAG, "Device access is already granted and DeviceGroup is in connected state"); + break; + } + if (value == mAcmService.getCsipManager().LOCK) { + mDeviceLocked = true; + if (!mAcmNativeInterface.connectAcm(mDevice, mCurrentContextType, mProfileType, mPreferredContext)) { + Log.e(TAG, "Disconnected: error connecting to " + mDevice); + mAcmService.getCsipManager().disconnectCsip(mDevice); + transitionTo(mDisconnected); + break; + } + } else { + mDeviceLocked = false; + Log.w(TAG, "Exclusive Access failed to " + setId); + transitionTo(mDisconnected); + } + break; + } + + case CSIP_LOCK_STATUS_PARTIAL_LOCK: { + //check if requested lock is from this device + if (mCsipLockRequested) { + int value = (int)message.arg1; + Log.d(TAG, "Exclusive Access state changed:" + value); + int setId = (int)message.obj; + int st = mAcmService.getCsipConnectionState(mDevice); + if (mDeviceLocked && st == BluetoothProfile.STATE_CONNECTED) { + Log.w(TAG, "Device access is already granted and DeviceGroup is in connected state"); + break; + } + if (value == mAcmService.getCsipManager().LOCK) { + mDeviceLocked = true; + if (!mAcmNativeInterface.connectAcm(mDevice, mCurrentContextType, mProfileType, mPreferredContext)) { + Log.e(TAG, "Disconnected: error connecting to " + mDevice); + mAcmService.getCsipManager().disconnectCsip(mDevice); + transitionTo(mDisconnected); + break; + } + } else { + mDeviceLocked = false; + Log.w(TAG, "Exclusive Access failed to " + setId); + transitionTo(mDisconnected); + } + mCsipLockRequested = false; + } else { + //lock is not requested from this device TODO: check if + Log.d(TAG, "Exclusive Access is not requested from this device: " + mDevice); + mDeviceLocked = true; + } + } + break; + + case CSIP_LOCK_RELEASE_TIMEOUT: + //TODO: lock release individual ? + Log.d(TAG, "Exclusive Access timeout to " + mDevice); + List set = new ArrayList(); + set.add(mDevice); + mAcmService.getCsipManager().setLock(mSetId, set, mAcmService.getCsipManager().UNLOCK); + mDeviceLocked = false; + break; + + case CSIP_LOCK_STATUS_DENIED: + //lock denied fail connection + Log.e(TAG, "DeviceGroup Connection failed to " + mDevice); + mCsipLockRequested = false; + mDeviceLocked = false; + break; + + case CSIP_LOCK_STATUS_RELEASED: + removeMessages(CSIP_LOCK_RELEASE_TIMEOUT); + mCsipLockRequested = false; + mDeviceLocked = false; + break; + + case STACK_EVENT: + AcmStackEvent event = (AcmStackEvent) message.obj; + log("Connecting: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case AcmStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1, event.valueInt2); + break; + case AcmStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED: + processCodecConfigEvent(event.codecStatus, event.valueInt2); + break; + case AcmStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED: + break; + default: + Log.e(TAG, "Connecting: ignoring stack event: " + event); + break; + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Connecting state + private void processConnectionEvent(int state, int contextType) { + IState smState; + StreamAudioService service = StreamAudioService.getStreamAudioService(); + switch (state) { + case AcmStackEvent.CONNECTION_STATE_DISCONNECTED: { + smState = mDisconnected; + Log.w(TAG, "Connecting device disconnected: " + mDevice); + if ((mMusicConnectionState == BluetoothProfile.STATE_CONNECTING) && + (mVoiceConnectionState == BluetoothProfile.STATE_CONNECTING)) { + if (contextType != CONTEXT_TYPE_MUSIC_VOICE) { + /*only 1/2 contexts are being disconnected, remain in connecting state but broadcast the connection state*/ + smState = mConnecting; + } + } + processTransitionContextState(mConnecting, mDisconnected, contextType); + if (smState == mConnecting) { + if (contextType == CONTEXT_TYPE_MUSIC) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "First member of group to connect MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + } else if (contextType == CONTEXT_TYPE_VOICE) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "First member of group to connect VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + } else { + transitionTo(smState); + } + } break; + + case AcmStackEvent.CONNECTION_STATE_CONNECTED: { + // start lock release timer TODO:when CSIP support is available + //sendMessageDelayed(CSIP_LOCK_RELEASE_TIMEOUT, sCsipLockReleaseTimeoutMs); + if (contextType == CONTEXT_TYPE_MUSIC) { + mMusicConnectionState = BluetoothProfile.STATE_CONNECTED; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "First member of group to connect MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + } else if (contextType == CONTEXT_TYPE_VOICE) { + mVoiceConnectionState = BluetoothProfile.STATE_CONNECTED; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "First member of group to connect VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + transitionTo(mConnected); + } break; + + case AcmStackEvent.CONNECTION_STATE_CONNECTING: + // Ignored - probably an event that the outgoing connection was initiated + break; + case AcmStackEvent.CONNECTION_STATE_DISCONNECTING: { + Log.w(TAG, "Connecting device disconnecting: " + mDevice); + transitionTo(mDisconnecting); + } break; + + default: + Log.e(TAG, "Incorrect event: " + state); + break; + } + } + } + + @VisibleForTesting + class Disconnecting extends State { + @Override + public void enter() { + //Disconnect unicast VCP 1st + mVcpController = VcpController.getVcpController(); + if (mVcpController != null) { + Log.d(TAG, "Disconnect VCP for " + mDevice); + mVcpController.disconnect(mDevice, BluetoothVcp.MODE_UNICAST); + } else { + Log.d(TAG, "mVcpController is null"); + } + + Message currentMessage = getCurrentMessage(); + Log.i(TAG, "Enter Disconnecting(" + mDevice + "): " + (currentMessage == null ? "null" + : messageWhatToString(currentMessage.what))); + sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs); + synchronized (this) { + mConnectionState = BluetoothProfile.STATE_DISCONNECTING; + } + mAcmService.handleAcmDeviceStateChange(mDevice, mConnectionState, mSetId); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (mContextTypeToDisconnect == CONTEXT_TYPE_MUSIC && mMusicConnectionState != BluetoothProfile.STATE_DISCONNECTED) { + mMusicConnectionState = BluetoothProfile.STATE_DISCONNECTING; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MEDIA"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "Last member to disconnect, update MEDIA"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + } else if (mContextTypeToDisconnect == CONTEXT_TYPE_VOICE && mVoiceConnectionState != BluetoothProfile.STATE_DISCONNECTED) { + mVoiceConnectionState = BluetoothProfile.STATE_DISCONNECTING; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update VOICE "); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } else if (mContextTypeToDisconnect == CONTEXT_TYPE_MUSIC_VOICE && mMusicConnectionState != BluetoothProfile.STATE_DISCONNECTED + && mVoiceConnectionState != BluetoothProfile.STATE_DISCONNECTED) { + mMusicConnectionState = BluetoothProfile.STATE_DISCONNECTING; + mVoiceConnectionState = BluetoothProfile.STATE_DISCONNECTING; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MEDIA+VOICE"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect, update MEDIA+VOICE"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + } + + @Override + public void exit() { + Message currentMessage = getCurrentMessage(); + log("Exit Disconnecting(" + mDevice + "): " + (currentMessage == null ? "null" + : messageWhatToString(currentMessage.what))); + mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING; + removeMessages(CONNECT_TIMEOUT); + } + + @Override + public boolean processMessage(Message message) { + log("Disconnecting process message(" + mDevice + "): " + + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: + deferMessage(message); + break; + case CONNECT_TIMEOUT: { + Log.w(TAG, "Disconnecting connection timeout: " + mDevice); + mAcmNativeInterface.disconnectAcm(mDevice, mCurrentContextType); + AcmStackEvent event = + new AcmStackEvent(AcmStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); + event.device = mDevice; + event.valueInt1 = AcmStackEvent.CONNECTION_STATE_DISCONNECTED; + event.valueInt2 = mCurrentContextType; + sendMessage(STACK_EVENT, event); + break; + } + case DISCONNECT: + deferMessage(message); + break; + case GATT_CONNECTION_STATE_CHANGED: + deferMessage(message); + break; + case STACK_EVENT: + AcmStackEvent event = (AcmStackEvent) message.obj; + log("Disconnecting: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case AcmStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1, event.valueInt2); + break; + case AcmStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED: + processCodecConfigEvent(event.codecStatus, event.valueInt2); + break; + case AcmStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED: + default: + Log.e(TAG, "Disconnecting: ignoring stack event: " + event); + break; + } + break; + + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Disconnecting state + private void processConnectionEvent(int event, int contextType) { + IState smState; + StreamAudioService service = StreamAudioService.getStreamAudioService(); + switch (event) { + case AcmStackEvent.CONNECTION_STATE_DISCONNECTED: { + Log.w(TAG, "Disconnecting device disconnected " + mDevice); + processTransitionContextState(mDisconnecting, mDisconnected, contextType); + if (contextType == CONTEXT_TYPE_MUSIC) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "Last member to disconnect, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + } else if (contextType == CONTEXT_TYPE_VOICE) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + if (mMusicConnectionState == BluetoothProfile.STATE_DISCONNECTED && + mVoiceConnectionState == BluetoothProfile.STATE_DISCONNECTED) { + transitionTo(mDisconnected); + } + } break; + + case AcmStackEvent.CONNECTION_STATE_CONNECTED: { + //TODO:sendMessageDelayed(CSIP_LOCK_RELEASE_TIMEOUT, sCsipLockReleaseTimeoutMs); + // Reject the connection and stay in Disconnecting state + Log.w(TAG, "Incoming ACM Connected request rejected: " + mDevice); + mAcmNativeInterface.disconnectAcm(mDevice, contextType); + } break; + + case AcmStackEvent.CONNECTION_STATE_DISCONNECTING: + if (contextType == CONTEXT_TYPE_MUSIC) + service.onConnectionStateChange(mDevice, BluetoothProfile.STATE_DISCONNECTING, CONTEXT_TYPE_MUSIC, false); + else if (contextType == CONTEXT_TYPE_VOICE) + service.onConnectionStateChange(mDevice, BluetoothProfile.STATE_DISCONNECTING, CONTEXT_TYPE_VOICE, false); + else { + service.onConnectionStateChange(mDevice, BluetoothProfile.STATE_DISCONNECTING, CONTEXT_TYPE_MUSIC, false); + service.onConnectionStateChange(mDevice, BluetoothProfile.STATE_DISCONNECTING, CONTEXT_TYPE_VOICE, false); + } + Log.d(TAG, "Updating disconnecting state to APM " + mDevice + "contextType " + contextType); + IsDisconnectRequested = false; + // We are already disconnecting, do nothing + break; + default: + Log.e(TAG, "Incorrect event: " + event); + break; + } + } + } + + @VisibleForTesting + class Connected extends State { + @Override + public void enter() { + Message currentMessage = getCurrentMessage(); + Log.i(TAG, "Enter Connected(" + mDevice + "): " + (currentMessage == null ? "null" + : messageWhatToString(currentMessage.what))); + synchronized (this) { + mConnectionState = BluetoothProfile.STATE_CONNECTED; + } + mAcmService.handleAcmDeviceStateChange(mDevice, mConnectionState, mSetId); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + service.onStreamStateChange(mDevice, BluetoothA2dp.STATE_NOT_PLAYING, CONTEXT_TYPE_MUSIC); + } + + @Override + public void exit() { + Message currentMessage = getCurrentMessage(); + log("Exit Connected(" + mDevice + "): " + (currentMessage == null ? "null" + : messageWhatToString(currentMessage.what))); + mLastConnectionState = BluetoothProfile.STATE_CONNECTED; + } + + @Override + public boolean processMessage(Message message) { + log("Connected process message(" + mDevice + "): " + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: { + if (message.arg1 == CONTEXT_TYPE_MUSIC && mMusicConnectionState != BluetoothProfile.STATE_DISCONNECTED) + break; + if (message.arg1 == CONTEXT_TYPE_VOICE && mVoiceConnectionState != BluetoothProfile.STATE_DISCONNECTED) + break; + if (message.arg1 == CONTEXT_TYPE_MUSIC_VOICE && mVoiceConnectionState != BluetoothProfile.STATE_DISCONNECTED + && mMusicConnectionState != BluetoothProfile.STATE_DISCONNECTED) + break; + mCurrentContextType += message.arg1; + Log.i(TAG, "mCurrentContextType now is " + contextTypeToString(mCurrentContextType)); + mProfileType = message.arg2; + mPreferredContext = (int)message.obj; + Log.i(TAG, "Connecting " + contextTypeToString(message.arg1) + " to " + mDevice); + if (mAcmService.IsLockSupportAvailable(mDevice)) { + Log.d(TAG, "Exclusive Access support available, gatt should already be connected"); + //if lock support available then go for CSIP connect + //mBluetoothGatt = mDevice.connectGatt(mAcmService, false, mGattCallback, BluetoothDevice.TRANSPORT_LE, 7); + //sendMessageDelayed(GATT_CONNECTION_TIMEOUT, GATT_CONNECTION_TIMEOUT_MS); + //transitionTo(mConnecting); + //break; + } else { + Log.d(TAG, "Exclusive Access support not available, gatt should already be connected"); + //mBluetoothGatt = mDevice.connectGatt(mAcmService, false, mGattCallback, BluetoothDevice.TRANSPORT_LE, 7); + //sendMessageDelayed(GATT_CONNECTION_TIMEOUT, GATT_CONNECTION_TIMEOUT_MS); + //transitionTo(mConnecting); + } + if (!mAcmNativeInterface.connectAcm(mDevice, message.arg1, message.arg2, (int)message.obj)) { + Log.e(TAG, "Disconnected: error connecting to " + mDevice + " remain in connected"); + } + } break; + + case DISCONNECT: {//disconnect request goes individual + IsDisconnectRequested = true; + mIsDeviceWhitelisted = false; + mContextTypeToDisconnect = (int)message.obj; + IState state = mDisconnecting; + boolean disconnected_flag = false; + //check if disconnect is for individual context type + Log.i(TAG, "Disconnecting " + contextTypeToString(mContextTypeToDisconnect) + " from " + mDevice); + if (!mAcmNativeInterface.disconnectAcm(mDevice, mContextTypeToDisconnect)) { + Log.e(TAG, "error disconnecting " + contextTypeToString(mContextTypeToDisconnect) + " from " + mDevice); + transitionTo(mDisconnected); + disconnected_flag = true; + } + if ((mMusicConnectionState == BluetoothProfile.STATE_CONNECTED) && + (mVoiceConnectionState == BluetoothProfile.STATE_CONNECTED)) { + if (mContextTypeToDisconnect != CONTEXT_TYPE_MUSIC_VOICE) { + /*only 1/2 contexts are being disconnected, + remain in connected state but broadcast the connection state*/ + state = mConnected; + } + } + StreamAudioService service = StreamAudioService.getStreamAudioService(); + processTransitionContextState(mConnected, (disconnected_flag ? mDisconnected : mDisconnecting), mContextTypeToDisconnect); + if (state == mConnected) { + if (mContextTypeToDisconnect == CONTEXT_TYPE_MUSIC) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "Last member to disconnect, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + } else if (mContextTypeToDisconnect == CONTEXT_TYPE_VOICE) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + } else { + transitionTo(state); + } + /*mSetId = mSetCoordinator.getRemoteDeviceSetId(mDevice, null); // TODO: UUID ? + List members = new ArrayList(); + members.add(mDevice); + //setlockvalue takes device list + mCsipLockRequested = true; + mDeviceLocked = false; + mSetCoordinator.setLockValue(mAcmService.mCsipAppId, mSetId, members, BluetoothCsip.LOCK);*/ + } break; + + case CSIP_LOCK_STATUS_LOCKED: { + mCsipLockRequested = false; + int value = (int)message.arg1; + int setId = (int)message.obj; + int st = mAcmService.getCsipConnectionState(mDevice); + Log.d(TAG, "Exclusive Access state changed:" + value); + if (value == mAcmService.getCsipManager().LOCK) { + mDeviceLocked = true; + if (IsDisconnectRequested) { + Log.i(TAG, "Disconnecting " + contextTypeToString(mContextTypeToDisconnect) + " from " + mDevice); + if (!mAcmNativeInterface.disconnectAcm(mDevice, mContextTypeToDisconnect)) { // this context Type is passed in disconnect api from APM + Log.e(TAG, "error disconnecting " + contextTypeToString(mContextTypeToDisconnect) + " from " + mDevice); + transitionTo(mDisconnected); + } + transitionTo(mDisconnecting); + } else if (IsReconfigRequested) { + Log.w(TAG, "Reconfig requested Exclusive Access"); + if (!mAcmNativeInterface.ChangeCodecConfigPreference(mDevice, mReconfig)) { // this context Type is passed in disconnect api from APM + Log.e(TAG, "reconfig error " + mDevice); + break; + } + } + } + } break; + + case CSIP_CONNECTION_STATE_CHANGED: + int state = (int)message.obj; + if (state == BluetoothProfile.STATE_DISCONNECTED) + mCsipConnectionState = BluetoothProfile.STATE_DISCONNECTED; + break; + + case CSIP_LOCK_RELEASE_TIMEOUT: + //lock release individual ? + Log.d(TAG, "Exclusive Access timeout to " + mDevice); + List devices = new ArrayList(); + devices.add(mDevice); + mAcmService.getCsipManager().setLock(mSetId, devices, mAcmService.getCsipManager().UNLOCK); + mDeviceLocked = false; + break; + + case CSIP_LOCK_STATUS_RELEASED: + // ignore disconnect CSIP + removeMessages(CSIP_LOCK_RELEASE_TIMEOUT); + mDeviceLocked = false; + break; + + case CODEC_CONFIG_CHANGED: { + IsReconfigRequested = true; + mReconfig = mAcmService.getAcmName(); + if (!mAcmNativeInterface.ChangeCodecConfigPreference(mDevice, mReconfig)) { + Log.e(TAG, "reconfig error " + mDevice); + break; + } + /*int setId = (int)message.obj; + int st = mAcmService.getCsipConnectionState(mDevice); + if ((mDeviceLocked && st == BluetoothProfile.STATE_CONNECTED)) { + Log.w(TAG, "Device access is already granted and DeviceGroup is in connected state"); + if (!mAcmNativeInterface.ChangeCodecConfigPreference(mDevice, mReconfig)) { + Log.e(TAG, "reconfig error " + mDevice); + break; + } + break; + } + //mSetId = mSetCoordinator.getRemoteDeviceSetId(mDevice, null ); //TODO: UUID what to set ? + List members = new ArrayList(); + Iterator i = mAcmService.getCsipManager().getSetMembers(mSetId).iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice device = i.next(); + if (mAcmService.getCsipConnectionState(device) == BluetoothProfile.STATE_CONNECTED) { + members.add(device); + } + } + } + mAcmService.getCsipManager().setLock(mSetId, members, mAcmService.getCsipManager().LOCK);*/ + } break; + + case START_STREAM: { + int value = (int)message.obj; + if (!mAcmNativeInterface.startStream(mDevice, value)) { + Log.e(TAG, "start stream error " + mDevice); + break; + } + /*int setId = (int)message.obj; + int st = mAcmService.getCsipConnectionState(mDevice); + if ((mDeviceLocked && st == BluetoothProfile.STATE_CONNECTED)) { + Log.w(TAG, "Device access is already granted and DeviceGroup is in connected state"); + if (!mAcmNativeInterface.ChangeCodecConfigPreference(mDevice, mReconfig)) { + Log.e(TAG, "reconfig error " + mDevice); + break; + } + break; + } + //mSetId = mSetCoordinator.getRemoteDeviceSetId(mDevice, null ); //TODO: UUID what to set ? + List members = new ArrayList(); + Iterator i = mAcmService.getCsipManager().getSetMembers(mSetId).iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice device = i.next(); + if (mAcmService.getCsipConnectionState(device) == BluetoothProfile.STATE_CONNECTED) { + members.add(device); + } + } + } + mAcmService.getCsipManager().setLock(mSetId, members, mAcmService.getCsipManager().LOCK);*/ + } break; + + case START_STREAM_REQ: { + if (!mAcmService.isPeerDeviceStreamingMusic(mDevice, mSetId)) { + mAcmService.StartStream(mGroupAddress, CONTEXT_TYPE_VOICE); + } + } break; + + case STACK_EVENT: + AcmStackEvent event = (AcmStackEvent) message.obj; + log("Connected: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case AcmStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1, event.valueInt2); + break; + case AcmStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED: + processAudioStateEvent(event.valueInt1, event.valueInt2); + break; + case AcmStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED: + processCodecConfigEvent(event.codecStatus, event.valueInt2); + break; + default: + Log.e(TAG, "Connected: ignoring stack event: " + event); + break; + } + break; + + case GATT_CONNECTION_STATE_CHANGED: { + Log.e(TAG, "Connection state as disconnected " + mDevice); + removeMessages(GATT_CONNECTION_TIMEOUT); + int st = (int)message.obj; + if (st == BluetoothProfile.STATE_DISCONNECTED) { + Log.d(TAG, " GATT Disconnected"); + mIsDeviceWhitelisted = false; + mIsUpdateProfDisConnection = true; + transitionTo(mDisconnected); + } break; + } + + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Connected state + private void processConnectionEvent(int event, int contextType) { + IState smState; + switch (event) { + case AcmStackEvent.CONNECTION_STATE_DISCONNECTED: { + StreamAudioService service = StreamAudioService.getStreamAudioService(); + mCurrentContextType -= contextType; + Log.i(TAG, "mCurrentContextType now is " + contextTypeToString(mCurrentContextType)); + processTransitionContextState(mDisconnecting, mDisconnected, contextType); + if (contextType == CONTEXT_TYPE_MUSIC) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "Last member to disconnect, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + } else if (mContextTypeToDisconnect == CONTEXT_TYPE_VOICE) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + } break; + + case AcmStackEvent.CONNECTION_STATE_CONNECTED: { + StreamAudioService service = StreamAudioService.getStreamAudioService(); + //TODO:sendMessageDelayed(CSIP_LOCK_RELEASE_TIMEOUT, sCsipLockReleaseTimeoutMs); + Log.w(TAG, "ACM CONNECTED event for device: " + mDevice + " context type: " + contextTypeToString(contextType)); + if (contextType == CONTEXT_TYPE_MUSIC) { + mMusicConnectionState = BluetoothProfile.STATE_CONNECTED; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "First member of group to connect MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + } else if (contextType == CONTEXT_TYPE_VOICE) { + mVoiceConnectionState = BluetoothProfile.STATE_CONNECTED; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "First member of group to connect VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + + } break; + + case AcmStackEvent.CONNECTION_STATE_CONNECTING: { + Log.w(TAG, "Ignore ACM CONNECTED event: " + mDevice); + } break; + + case AcmStackEvent.CONNECTION_STATE_DISCONNECTING: { + if ((contextType == CONTEXT_TYPE_MUSIC) && + (mMusicConnectionState == BluetoothProfile.STATE_DISCONNECTING)) { + Log.w(TAG, "Ignore Disconnecting for media - already disconnecting"); + } else if ((contextType == CONTEXT_TYPE_VOICE) && + (mVoiceConnectionState == BluetoothProfile.STATE_DISCONNECTING)) { + Log.w(TAG, "Ignore Disconnecting for voice - already disconnecting"); + } else { + smState = mDisconnecting; + Log.w(TAG, "Connected device disconnecting: " + mDevice); + if ((mMusicConnectionState == BluetoothProfile.STATE_CONNECTED) && + (mVoiceConnectionState == BluetoothProfile.STATE_CONNECTED)) { + if (contextType != CONTEXT_TYPE_MUSIC_VOICE) { + /*only 1/2 contexts are being disconnected, + remain in connecting state but broadcast the connection state*/ + smState = mConnected; + } + } + processTransitionContextState(mConnected, mDisconnecting, contextType); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (smState == mConnected) { + if (contextType == CONTEXT_TYPE_MUSIC) { + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else if (contextType == CONTEXT_TYPE_VOICE) { + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } + } else { + transitionTo(smState); + } + } + } break; + + default: + Log.e(TAG, "Connection State Device: " + mDevice + " bad event: " + event); + break; + } + } + + + // in Connected state + private void processAudioStateEvent(int state, int contextType) { + Log.i(TAG, "Connected: processAudioStateEvent: state: " + state + " mIsMusicPlaying: " + mIsMusicPlaying); + switch (state) { + case AcmStackEvent.AUDIO_STATE_STARTED: { + if (contextType == CONTEXT_TYPE_MUSIC) + mIsMusicPlaying = true; + else if (contextType == CONTEXT_TYPE_VOICE) + mIsVoicePlaying = true; + transitionTo(mStreaming); + } break; + case AcmStackEvent.AUDIO_STATE_REMOTE_SUSPEND: + case AcmStackEvent.AUDIO_STATE_STOPPED: { + StreamAudioService service = StreamAudioService.getStreamAudioService(); + synchronized (this) { + if (contextType == CONTEXT_TYPE_MUSIC) { + if (mIsMusicPlaying) { + Log.i(TAG, "Connected: stopped media playing: " + mDevice); + mIsMusicPlaying = false; + service.onStreamStateChange(mDevice, BluetoothA2dp.STATE_NOT_PLAYING, ApmConst.AudioFeatures.MEDIA_AUDIO); + } + } else if (contextType == CONTEXT_TYPE_VOICE) { + if (mIsVoicePlaying) { + Log.i(TAG, "Connected: stopped voice playing: " + mDevice); + mIsVoicePlaying = false; + service.onStreamStateChange(mDevice, BluetoothA2dp.STATE_NOT_PLAYING, ApmConst.AudioFeatures.CALL_AUDIO); + } + } + } + } break; + default: + Log.e(TAG, "Audio State Device: " + mDevice + " bad state: " + state); + break; + } + } + } + + + @VisibleForTesting + class Streaming extends State { + @Override + public void enter() { + Message currentMessage = getCurrentMessage(); + Log.i(TAG, "Enter Streaming(" + mDevice + "): " + (currentMessage == null ? "null" + : messageWhatToString(currentMessage.what))); + + if ((mMusicConnectionState == BluetoothProfile.STATE_CONNECTED)) { + removeDeferredMessages(CONNECT); + } + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (mIsMusicPlaying) { + Log.i(TAG, "start playing media: " + mDevice); + service.onStreamStateChange(mDevice, BluetoothA2dp.STATE_PLAYING, ApmConst.AudioFeatures.MEDIA_AUDIO); + mAcmService.updateLeaChannelMode(BluetoothA2dp.STATE_PLAYING, mDevice); + } else if (mIsVoicePlaying) { + Log.i(TAG, "start playing voice: " + mDevice); + service.onStreamStateChange(mDevice, BluetoothHeadset.STATE_AUDIO_CONNECTED, ApmConst.AudioFeatures.CALL_AUDIO); + setVoiceParameters(); + setCallAudioOn(true); + } + } + + @Override + public void exit() { + Message currentMessage = getCurrentMessage(); + log("Exit Streaming(" + mDevice + "): " + (currentMessage == null ? "null" + : messageWhatToString(currentMessage.what))); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (mIsMusicPlaying) + service.onStreamStateChange(mDevice, BluetoothA2dp.STATE_NOT_PLAYING, ApmConst.AudioFeatures.MEDIA_AUDIO); + else if (mIsVoicePlaying) { + service.onStreamStateChange(mDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED, ApmConst.AudioFeatures.CALL_AUDIO); + setCallAudioOn(false); + } + mIsMusicPlaying = false; + mIsVoicePlaying = false; + } + + @Override + public boolean processMessage(Message message) { + log("Streaming process message(" + mDevice + "): " + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: { + if (message.arg1 == CONTEXT_TYPE_MUSIC && mMusicConnectionState != BluetoothProfile.STATE_DISCONNECTED) + break; + if (message.arg1 == CONTEXT_TYPE_VOICE && mVoiceConnectionState != BluetoothProfile.STATE_DISCONNECTED) + break; + if (message.arg1 == CONTEXT_TYPE_MUSIC_VOICE && mVoiceConnectionState != BluetoothProfile.STATE_DISCONNECTED + && mMusicConnectionState != BluetoothProfile.STATE_DISCONNECTED) + break; + mCurrentContextType += message.arg1; + Log.i(TAG, "mCurrentContextType now is " + contextTypeToString(mCurrentContextType)); + mProfileType = message.arg2; + mPreferredContext = (int)message.obj; + Log.i(TAG, "Connecting " + contextTypeToString(message.arg1) + " to " + mDevice); + if (mAcmService.IsLockSupportAvailable(mDevice)) { + Log.d(TAG, "Exclusive Access support available, gatt should already be connected"); + //if lock support available then go for CSIP connect + //mBluetoothGatt = mDevice.connectGatt(mAcmService, false, mGattCallback, BluetoothDevice.TRANSPORT_LE, 7); + //sendMessageDelayed(GATT_CONNECTION_TIMEOUT, GATT_CONNECTION_TIMEOUT_MS); + //transitionTo(mConnecting); + //break; + } else { + Log.d(TAG, "Exclusive Access support not available, gatt should already be connected"); + //mBluetoothGatt = mDevice.connectGatt(mAcmService, false, mGattCallback, BluetoothDevice.TRANSPORT_LE, 7); + //sendMessageDelayed(GATT_CONNECTION_TIMEOUT, GATT_CONNECTION_TIMEOUT_MS); + //transitionTo(mConnecting); + } + if (!mAcmNativeInterface.connectAcm(mDevice, message.arg1, message.arg2, (int)message.obj)) { + Log.e(TAG, "Disconnected: error connecting to " + mDevice + " remain in streaming"); + } + } break; + + case DISCONNECT: {//disconnect request goes individual + IsDisconnectRequested = true; + mIsDeviceWhitelisted = false; + mContextTypeToDisconnect = (int)message.obj; + IState state = mDisconnecting; + boolean disconnected_flag = false; + //check if disconnect is for individual context type + Log.i(TAG, "Disconnecting " + contextTypeToString(mContextTypeToDisconnect) + " from " + mDevice); + if (!mAcmNativeInterface.disconnectAcm(mDevice, mContextTypeToDisconnect)) { + Log.e(TAG, "error disconnecting " + contextTypeToString(mContextTypeToDisconnect) + " from " + mDevice); + transitionTo(mDisconnected); + disconnected_flag = true; + } + if ((mMusicConnectionState == BluetoothProfile.STATE_CONNECTED) + && (mVoiceConnectionState == BluetoothProfile.STATE_CONNECTED)) { + if (mContextTypeToDisconnect != CONTEXT_TYPE_MUSIC_VOICE) { + /*only 1/2 contexts are being disconnected, + remain in connected state but broadcast the connection state*/ + state = mConnected; + } + } + StreamAudioService service = StreamAudioService.getStreamAudioService(); + processTransitionContextState(mConnected, (disconnected_flag ? mDisconnected : mDisconnecting), mContextTypeToDisconnect); + if (state == mConnected) { + if (mContextTypeToDisconnect == CONTEXT_TYPE_MUSIC) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "Last member to disconnect, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + transitionTo(state); + } else if (mContextTypeToDisconnect == CONTEXT_TYPE_VOICE) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + } else { + transitionTo(state); + } + /*mSetId = mSetCoordinator.getRemoteDeviceSetId(mDevice, null); // TODO: UUID ? + List members = new ArrayList(); + members.add(mDevice); + //setlockvalue takes device list + mCsipLockRequested = true; + mDeviceLocked = false; + mSetCoordinator.setLockValue(mAcmService.mCsipAppId, mSetId, members, BluetoothCsip.LOCK);*/ + } break; + + case CSIP_LOCK_STATUS_LOCKED: { + mCsipLockRequested = false; + int value = (int)message.arg1; + int setId = (int)message.obj; + int st = mAcmService.getCsipConnectionState(mDevice); + Log.d(TAG, "Exclusive Access state changed:" + value); + if (value == mAcmService.getCsipManager().LOCK) { + mDeviceLocked = true; + if (IsDisconnectRequested) { + Log.i(TAG, "Disconnecting " + contextTypeToString(mContextTypeToDisconnect) + " from " + mDevice); + if (!mAcmNativeInterface.disconnectAcm(mDevice, mContextTypeToDisconnect)) { // this context Type is passed in disconnect api from APM + Log.e(TAG, "error disconnecting " + contextTypeToString(mContextTypeToDisconnect) + " from " + mDevice); + transitionTo(mDisconnected); + } + transitionTo(mDisconnecting); + } else if (IsReconfigRequested) { + Log.w(TAG, "Reconfig requested Exclusive Access"); + if (!mAcmNativeInterface.ChangeCodecConfigPreference(mDevice, mReconfig)) { // this context Type is passed in disconnect api from APM + Log.e(TAG, "reconfig error " + mDevice); + break; + } + } + } + } break; + + case CSIP_CONNECTION_STATE_CHANGED: + int state = (int)message.obj; + if (state == BluetoothProfile.STATE_DISCONNECTED) + mCsipConnectionState = BluetoothProfile.STATE_DISCONNECTED; + break; + + case CSIP_LOCK_RELEASE_TIMEOUT: + //lock release individual ? + Log.d(TAG, "Exclusive Access timeout to " + mDevice); + List devices = new ArrayList(); + devices.add(mDevice); + mAcmService.getCsipManager().setLock(mSetId, devices, mAcmService.getCsipManager().UNLOCK); + mDeviceLocked = false; + break; + + case CSIP_LOCK_STATUS_RELEASED: + // ignore disconnect CSIP + removeMessages(CSIP_LOCK_RELEASE_TIMEOUT); + mDeviceLocked = false; + break; + + case CODEC_CONFIG_CHANGED: { + IsReconfigRequested = true; + mReconfig = mAcmService.getAcmName(); + if (!mAcmNativeInterface.ChangeCodecConfigPreference(mDevice, mReconfig)) { + Log.e(TAG, "reconfig error " + mDevice); + break; + } + /*int setId = (int)message.obj; + int st = mAcmService.getCsipConnectionState(mDevice); + if ((mDeviceLocked && st == BluetoothProfile.STATE_CONNECTED)) { + Log.w(TAG, "Device is already acquired and DeviceGroup is in connected state"); + if (!mAcmNativeInterface.ChangeCodecConfigPreference(mDevice, mReconfig)) { + Log.e(TAG, "reconfig error " + mDevice); + break; + } + break; + } + //mSetId = mSetCoordinator.getRemoteDeviceSetId(mDevice, null); //TODO: UUID what to set ? + List members = new ArrayList(); + Iterator i = mAcmService.getCsipManager().getSetMembers(setId).iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice device = i.next(); + if (mAcmService.getCsipConnectionState(device) == BluetoothProfile.STATE_CONNECTED) { + members.add(device); + } + } + } + mAcmService.getCsipManager().setLock(setId, + members, mAcmService.getCsipManager().LOCK);*/ + } break; + + case STOP_STREAM: { + int value = (int)message.obj; + if (!mAcmNativeInterface.stopStream(mDevice, value)) { + Log.e(TAG, "start stream error " + mDevice); + break; + } + /*int setId = (int)message.obj; + int st = mAcmService.getCsipConnectionState(mDevice); + if ((mDeviceLocked && st == BluetoothProfile.STATE_CONNECTED)) { + Log.w(TAG, "Device access is already granted and DeviceGroup is in connected state"); + if (!mAcmNativeInterface.ChangeCodecConfigPreference(mDevice, mReconfig)) { + Log.e(TAG, "reconfig error " + mDevice); + break; + } + break; + } + //mSetId = mSetCoordinator.getRemoteDeviceSetId(mDevice, null ); //TODO: UUID what to set ? + List members = new ArrayList(); + Iterator i = mAcmService.getCsipManager().getSetMembers(mSetId).iterator(); + if (i != null) { + while (i.hasNext()) { + BluetoothDevice device = i.next(); + if (mAcmService.getCsipConnectionState(device) == BluetoothProfile.STATE_CONNECTED) { + members.add(device); + } + } + } + mAcmService.getCsipManager().setLock(mSetId, members, mAcmService.getCsipManager().LOCK);*/ + } break; + + case START_STREAM: + int value = (int)message.obj; + if (value == CONTEXT_TYPE_VOICE && mIsMusicPlaying) { + deferMessage(obtainMessage(START_STREAM_REQ, value)); + Log.wtf(TAG, "Defer START request for voice context"); + } + break; + + case STACK_EVENT: + AcmStackEvent event = (AcmStackEvent) message.obj; + log("Streaming: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case AcmStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1, event.valueInt2); + break; + case AcmStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED: + processAudioStateEvent(event.valueInt1, event.valueInt2); + break; + case AcmStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED: + processCodecConfigEvent(event.codecStatus, event.valueInt2); + break; + default: + Log.e(TAG, "Streaming: ignoring stack event: " + event); + break; + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Streaming state + private void processConnectionEvent(int event, int contextType) { + IState smState; + switch (event) { + case AcmStackEvent.CONNECTION_STATE_DISCONNECTED: { + //TODO: sendMessageDelayed(CSIP_LOCK_RELEASE_TIMEOUT, sCsipLockReleaseTimeoutMs); + Log.w(TAG, "Streaming device disconnected: " + mDevice); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + mCurrentContextType -= contextType; + Log.i(TAG, "mCurrentContextType now is " + contextTypeToString(mCurrentContextType)); + processTransitionContextState(mDisconnecting, mDisconnected, contextType); + if (contextType == CONTEXT_TYPE_MUSIC) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "Last member to disconnect, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + transitionTo(mConnected); + } else if (mContextTypeToDisconnect == CONTEXT_TYPE_VOICE) { + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "Last member to disconnect, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + } break; + + case AcmStackEvent.CONNECTION_STATE_CONNECTED: { + StreamAudioService service = StreamAudioService.getStreamAudioService(); + //TODO:sendMessageDelayed(CSIP_LOCK_RELEASE_TIMEOUT, sCsipLockReleaseTimeoutMs); + Log.w(TAG, "ACM CONNECTED event for device: " + mDevice + " context type: " + contextTypeToString(contextType)); + if (contextType == CONTEXT_TYPE_MUSIC) { + mMusicConnectionState = BluetoothProfile.STATE_CONNECTED; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else { + Log.d(TAG, "First member of group to connect MUSIC"); + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, true); + } + } else if (contextType == CONTEXT_TYPE_VOICE) { + mVoiceConnectionState = BluetoothProfile.STATE_CONNECTED; + if (mAcmService.isPeerDeviceConnected(mDevice, mSetId)) { + Log.d(TAG, "Fellow device is already connected, update VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } else { + Log.d(TAG, "First member of group to connect VOICE"); + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, true); + } + } + } break; + + case AcmStackEvent.CONNECTION_STATE_CONNECTING: { + Log.w(TAG, "ACM CONNECTING event: " + mDevice); + } break; + + case AcmStackEvent.CONNECTION_STATE_DISCONNECTING: { + if ((contextType == CONTEXT_TYPE_MUSIC) && + (mMusicConnectionState == BluetoothProfile.STATE_DISCONNECTING)) { + Log.w(TAG, "Ignore Disconnecting for media - already disconnecting"); + } else if ((contextType == CONTEXT_TYPE_VOICE) && + (mVoiceConnectionState == BluetoothProfile.STATE_DISCONNECTING)) { + Log.w(TAG, "Ignore Disconnecting for voice - already disconnecting"); + } else { + smState = mDisconnecting; + Log.w(TAG, "Connected device disconnecting: " + mDevice); + if ((mMusicConnectionState == BluetoothProfile.STATE_CONNECTED) && + (mVoiceConnectionState == BluetoothProfile.STATE_CONNECTED)) { + if (contextType != CONTEXT_TYPE_MUSIC_VOICE) { + /*only 1/2 contexts are being disconnected, + remain in connecting state but broadcast the connection state*/ + smState = mConnected; + } + } + processTransitionContextState(mConnected, mDisconnecting, contextType); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (smState == mConnected) { + if (contextType == CONTEXT_TYPE_MUSIC) { + service.onConnectionStateChange(mDevice, mMusicConnectionState, CONTEXT_TYPE_MUSIC, false); + } else if (contextType == CONTEXT_TYPE_VOICE) { + service.onConnectionStateChange(mDevice, mVoiceConnectionState, ApmConst.AudioFeatures.CALL_AUDIO, false); + } + } else { + transitionTo(smState); + } + } + } break; + + default: + Log.e(TAG, "Connection State Device: " + mDevice + " bad event: " + event); + break; + } + } + + // in Streaming state + private void processAudioStateEvent(int state, int contextType) { + Log.i(TAG, "Streaming: processAudioStateEvent: state: " + state + "mIsMusicPlaying: " + mIsMusicPlaying); + switch (state) { + case AcmStackEvent.AUDIO_STATE_STARTED: + Log.i(TAG, "Streaming: already started: " + mDevice); + break; + case AcmStackEvent.AUDIO_STATE_REMOTE_SUSPEND: + case AcmStackEvent.AUDIO_STATE_STOPPED: + synchronized (this) { + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if (contextType == CONTEXT_TYPE_MUSIC) { + if (mIsMusicPlaying) { + Log.i(TAG, "Streaming: stopped media playing: " + mDevice); + mIsMusicPlaying = false; + service.onStreamStateChange(mDevice, BluetoothA2dp.STATE_NOT_PLAYING, CONTEXT_TYPE_MUSIC); + if (mAcmService.isShoPendingStop()) { + Log.i(TAG, "Streaming: SHO was pending earlier, complete now"); + mAcmService.resetShoPendingStop(); + service.onActiveDeviceChange(null, ApmConst.AudioFeatures.MEDIA_AUDIO); + } + transitionTo(mConnected); + } + } + if (contextType == CONTEXT_TYPE_VOICE) { + if (mIsVoicePlaying) { + Log.i(TAG, "Streaming: stopped voice playing: " + mDevice); + mIsVoicePlaying = false; + service.onStreamStateChange(mDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED, ApmConst.AudioFeatures.CALL_AUDIO); + setCallAudioOn(false); + if (mAcmService.isVoiceShoPendingStop()) { + Log.i(TAG, "Voice SHO was pending earlier, complete now"); + mAcmService.resetVoiceShoPendingStop(); + service.onActiveDeviceChange(mAcmService.getVoiceActiveDevice(), ApmConst.AudioFeatures.CALL_AUDIO); + } + transitionTo(mConnected); + } + } + } + break; + default: + Log.e(TAG, "Audio State Device: " + mDevice + " bad state: " + state); + break; + } + } + } + + private String getFrameDuration() { + BluetoothCodecConfig mCodecConfig = mVoiceCodecStatus.getCodecConfig(); + long cs1 = mCodecConfig.getCodecSpecific1() >> 32 & 0xff; + if (cs1 == 0x00) { + return "7.5"; + } else { + return "10"; + } + } + + private String getLc3BlocksPerSdu() { + BluetoothCodecConfig mCodecConfig = mVoiceCodecStatus.getCodecConfig(); + long cs1 = mCodecConfig.getCodecSpecific1() >> 40 & 0xff; + return Integer.toString((int)cs1); + } + + private String getCodectype() { + BluetoothCodecConfig mCodecConfig = mVoiceCodecStatus.getCodecConfig(); + long cs3 = (mCodecConfig.getCodecSpecific3() >> + (CS_PARAM_NUM_BITS * CS_PARAM_1ST_INDEX)) & CS_PARAM_IND_MASK; + if (cs3 == CODEC_TYPE_LC3Q) { + return "LC3Q"; + } else { + return "LC3"; + } + } + + private String getLc3qValues() { + BluetoothCodecConfig mCodecConfig = mVoiceCodecStatus.getCodecConfig(); + long cs3 = mCodecConfig.getCodecSpecific3(); + long cs4 = mCodecConfig.getCodecSpecific4(); + + String vsMetaDataLc3qVal = String.join(",", new String[]{ + String.format("%02X", ((cs4 >> + (CS_PARAM_NUM_BITS * CS_PARAM_8TH_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs4 >> + (CS_PARAM_NUM_BITS * CS_PARAM_7TH_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs4 >> + (CS_PARAM_NUM_BITS * CS_PARAM_6TH_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs4 >> + (CS_PARAM_NUM_BITS * CS_PARAM_5TH_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs4 >> + (CS_PARAM_NUM_BITS * CS_PARAM_4TH_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs4 >> + (CS_PARAM_NUM_BITS * CS_PARAM_3RD_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs4 >> + (CS_PARAM_NUM_BITS * CS_PARAM_2ND_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs4 >> + (CS_PARAM_NUM_BITS * CS_PARAM_1ST_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs3 >> + (CS_PARAM_NUM_BITS * CS_PARAM_8TH_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", (((cs3 >> + (CS_PARAM_NUM_BITS * CS_PARAM_2ND_INDEX)) & CS_PARAM_IND_MASK) & 0x01)), + String.format("%02X", ((cs3 >> + (CS_PARAM_NUM_BITS * CS_PARAM_1ST_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs3 >> + (CS_PARAM_NUM_BITS * CS_PARAM_7TH_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs3 >> + (CS_PARAM_NUM_BITS * CS_PARAM_6TH_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs3 >> + (CS_PARAM_NUM_BITS * CS_PARAM_5TH_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs3 >> + (CS_PARAM_NUM_BITS * CS_PARAM_4TH_INDEX)) & CS_PARAM_IND_MASK)), + String.format("%02X", ((cs3 >> + (CS_PARAM_NUM_BITS * CS_PARAM_3RD_INDEX)) & CS_PARAM_IND_MASK)) + }); + Log.i(TAG, "getLc3qValues() for " + mDevice + ": " + vsMetaDataLc3qVal); + return vsMetaDataLc3qVal; + } + + private String getRxTxConfigIndex() { + BluetoothCodecConfig mCodecConfig = mVoiceCodecStatus.getCodecConfig(); + int sampleRate = mCodecConfig.getSampleRate(); + long cs1 = mCodecConfig.getCodecSpecific1() >> 0 & 0xff; + if (sampleRate == BluetoothCodecConfig.SAMPLE_RATE_8000) { + if (cs1 == 0x01) { + return "0"; + } else { + return "1"; + } + } else if (sampleRate == BluetoothCodecConfig.SAMPLE_RATE_16000) { + if (cs1 == 0x01) { + return "2"; + } else { + return "3"; + } + } else if (sampleRate == BluetoothCodecConfig.SAMPLE_RATE_32000) { + if (cs1 == 0x01) { + return "4"; + } else { + return "5"; + } + } + return "0"; + } + + private void setVoiceParameters() { + String keyValuePairs = String.join(";", new String[]{ + CODEC_NAME + "=" + "LC3", + STREAM_MAP + "=" + "(0, 0, M, 0, 1, L),(1, 0, M, 1, 1, R)", + FRAME_DURATION + "=" + getFrameDuration(), + SDU_BLOCK + "=" + getLc3BlocksPerSdu(), + RXCONFIG_INDX + "=" + getRxTxConfigIndex(), + TXCONFIG_INDX + "=" + getRxTxConfigIndex(), + VERSION + "=" + "21", + VENDOR_META_DATA + "=" + getLc3qValues() + }); + Log.i(TAG, "setVoiceParameters for " + mDevice + ": " + keyValuePairs); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + service.setCallAudioParam(keyValuePairs); + } + + private void setCallAudioOn(boolean on) { + Log.i(TAG, "set Call Audio On: " + on); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + service.setCallAudioOn(on); + } + + int getConnectionState() { + return mConnectionState; + } + + int getCsipConnectionState() { + return mCsipConnectionState; + } + + BluetoothDevice getDevice() { + return mDevice; + } + + BluetoothDevice getPeerDevice() { + BluetoothDevice d = null; + List members = mAcmService.getCsipManager().getSetMembers(mSetId); + if (members == null) { + Log.d(TAG, "No set member found"); + return d; + } + Iterator i = members.iterator(); + if (i != null) { + while (i.hasNext()) { + d = i.next(); + if (!(Objects.equals(d, mDevice))) { + Log.d(TAG, "Device: " + d); + break; + } + } + } + return d; + } + + void removeDevicefromBgWL() { + Log.d(TAG, "remove device from BG WL"); + if (mBluetoothGatt != null && mIsDeviceWhitelisted) { + mIsDeviceWhitelisted = false; + mBluetoothGatt.disconnect(); + } + } + + boolean isConnected() { + synchronized (this) { + return (getConnectionState() == BluetoothProfile.STATE_CONNECTED); + } + } + + boolean isCsipLockRequested() { + synchronized (this) { + return mCsipLockRequested; + } + } + + boolean isMusicPlaying() { + synchronized (this) { + return mIsMusicPlaying; + } + } + + boolean isVoicePlaying() { + synchronized (this) { + return mIsVoicePlaying; + } + } + + private void processTransitionContextState(IState prevState, IState nextState, int contextType) { + int pState = AcmStateToBluetoothProfileState(prevState); + int nState = AcmStateToBluetoothProfileState(nextState); + if (contextType == CONTEXT_TYPE_MUSIC) { + mLastMusicConnectionState = pState; + mMusicConnectionState = nState; + } else if (contextType == CONTEXT_TYPE_VOICE) { + mLastVoiceConnectionState = pState; + mVoiceConnectionState = nState; + } else if (contextType == CONTEXT_TYPE_MUSIC_VOICE) { + mLastMusicConnectionState = pState; + mLastVoiceConnectionState = pState; + mMusicConnectionState = nState; + mVoiceConnectionState = nState; + } + } + + // NOTE: This event is processed in any state + @VisibleForTesting + void processCodecConfigEvent(BluetoothCodecStatus newCodecStatus, int contextType) { + Log.d(TAG,"ProcessCodecConfigEvent: context type :" + contextType); + if (contextType == CONTEXT_TYPE_MUSIC) { + BluetoothCodecConfig mCodecConfig = newCodecStatus.getCodecConfig(); + long cs3 = mCodecConfig.getCodecSpecific3(); + cs3 |= LE_AUDIO_AVAILABLE_LICENSED; + BluetoothCodecConfig mCodecConfigLc3 = new BluetoothCodecConfig( + mCodecConfig.getCodecType(), + mCodecConfig.getCodecPriority(), + mCodecConfig.getSampleRate(), + mCodecConfig.getBitsPerSample(), + mCodecConfig.getChannelMode(), + mCodecConfig.getCodecSpecific1(), mCodecConfig.getCodecSpecific2(), + cs3, mCodecConfig.getCodecSpecific4()); + mMusicCodecStatus = new BluetoothCodecStatus(mCodecConfigLc3, + newCodecStatus.getCodecsLocalCapabilities(), + newCodecStatus.getCodecsSelectableCapabilities()); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + service.onMediaCodecConfigChange(mGroupAddress, mMusicCodecStatus, contextType); + } else if (contextType == CONTEXT_TYPE_VOICE) { + mVoiceCodecStatus = newCodecStatus; + } + } + + @Override + protected String getLogRecString(Message msg) { + StringBuilder builder = new StringBuilder(); + builder.append(messageWhatToString(msg.what)); + builder.append(": "); + builder.append("arg1=") + .append(msg.arg1) + .append(", arg2=") + .append(msg.arg2) + .append(", obj=") + .append(msg.obj); + return builder.toString(); + } + + private static boolean sameSelectableCodec(BluetoothCodecStatus prevCodecStatus, + BluetoothCodecStatus newCodecStatus) { + if (prevCodecStatus == null) { + return false; + } + return BluetoothCodecStatus.sameCapabilities( + prevCodecStatus.getCodecsSelectableCapabilities(), + newCodecStatus.getCodecsSelectableCapabilities()); + } + + private static String messageWhatToString(int what) { + switch (what) { + case CONNECT: + return "CONNECT"; + case DISCONNECT: + return "DISCONNECT"; + case STACK_EVENT: + return "STACK_EVENT"; + case CONNECT_TIMEOUT: + return "CONNECT_TIMEOUT"; + default: + break; + } + return Integer.toString(what); + } + + private static String contextTypeToString(int contextType) { + switch (contextType) { + case CONTEXT_TYPE_UNKNOWN: + return "UNKNOWN"; + case CONTEXT_TYPE_MUSIC: + return "MEDIA"; + case CONTEXT_TYPE_VOICE: + return "CONVERSATIONAL"; + case CONTEXT_TYPE_MUSIC_VOICE: + return "MEDIA+CONVERSATIONAL"; + default: + break; + } + return Integer.toString(contextType); + } + + private static String profileStateToString(int state) { + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return "DISCONNECTED"; + case BluetoothProfile.STATE_CONNECTING: + return "CONNECTING"; + case BluetoothProfile.STATE_CONNECTED: + return "CONNECTED"; + case BluetoothProfile.STATE_DISCONNECTING: + return "DISCONNECTING"; + default: + break; + } + return Integer.toString(state); + } + + /*private static String musicAudioStateToString(int state) { + switch (state) { + case BluetoothA2dp.STATE_PLAYING: + return "PLAYING"; + case BluetoothA2dp.STATE_NOT_PLAYING: + return "NOT_PLAYING"; + default: + break; + } + return Integer.toString(state); + }*/ + + private static String voiceAudioStateToString(int state) { + switch (state) { + case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: + return "AUDIO_DISCONNECTED"; + case BluetoothHeadset.STATE_AUDIO_CONNECTING: + return "AUDIO_CONNECTING"; + case BluetoothHeadset.STATE_AUDIO_CONNECTED: + return "AUDIO_CONNECTED"; + case BluetoothHeadset.STATE_AUDIO_DISCONNECTING: + return "AUDIO_DISCONNECTING"; + default: + break; + } + return Integer.toString(state); + } + + private static int AcmStateToBluetoothProfileState(IState state) { + if (state instanceof Disconnected) { + return BluetoothProfile.STATE_DISCONNECTED; + } else if (state instanceof Connecting) { + return BluetoothProfile.STATE_CONNECTING; + } else if (state instanceof Connected) { + return BluetoothProfile.STATE_CONNECTED; + } else if (state instanceof Disconnecting) { + return BluetoothProfile.STATE_DISCONNECTING; + } + Log.w(TAG, "Unknown State"); + return BluetoothProfile.STATE_DISCONNECTED; + } + + public void dump(StringBuilder sb) { + ProfileService.println(sb, "mDevice: " + mDevice); + ProfileService.println(sb, " StateMachine: " + this.toString()); + ProfileService.println(sb, " mIsMusicPlaying: " + mIsMusicPlaying); + synchronized (this) { + if (mVoiceCodecStatus != null) { + ProfileService.println(sb, " Voice mCodecConfig: " + mVoiceCodecStatus.getCodecConfig()); + } + if (mMusicCodecStatus != null) { + ProfileService.println(sb, " Music mCodecConfig: " + mMusicCodecStatus.getCodecConfig()); + } + } + // Dump the state machine logs + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + super.dump(new FileDescriptor(), printWriter, new String[]{}); + printWriter.flush(); + stringWriter.flush(); + ProfileService.println(sb, " StateMachineLog:"); + Scanner scanner = new Scanner(stringWriter.toString()); + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + ProfileService.println(sb, " " + line); + } + scanner.close(); + } + + @Override + protected void log(String msg) { + if (DBG) { + super.log(msg); + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ActiveDeviceManagerService.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ActiveDeviceManagerService.java new file mode 100644 index 00000000000..e6c4e8643e0 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ActiveDeviceManagerService.java @@ -0,0 +1,1430 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + ************************************************************************** + +/** + * Bluetooth ActiveDeviceManagerService. There is one instance each for + * voice and media profile management.. + * - "Idle" and "Active" are steady states. + * - "Activating" and "Deactivating" are transient states until the + * SHO / Deactivation is completed. + * + * + * (Idle) + * | ^ + * SetActive | | Removed + * V | + * (Activating) (Deactivating) + * | ^ + * Activated | | setActive(NULL) / removeDevice + * V | + * (Active / Broadcasting) + * + * + */ + +package com.android.bluetooth.apm; + +import static android.Manifest.permission.BLUETOOTH_CONNECT; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothHeadset; +import com.android.bluetooth.Utils; +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.ActiveDeviceManager; +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.a2dp.A2dpService; +import com.android.bluetooth.hearingaid.HearingAidService; +import com.android.bluetooth.hfp.HeadsetService; +import com.android.bluetooth.mcp.McpService; +import com.android.bluetooth.broadcast.BroadcastService; +import com.android.bluetooth.cc.CCService; +import android.content.Intent; +import android.content.Context; +import android.os.Looper; +import android.os.Message; +import android.os.HandlerThread; +import android.os.UserHandle; +import android.os.SystemProperties; +import android.util.Log; +import android.media.AudioManager; + +import com.android.bluetooth.BluetoothStatsLog; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.Boolean; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.lang.Integer; +import java.util.Scanner; +import java.util.Objects; + +public class ActiveDeviceManagerService { + private static final boolean DBG = true; + private static final String TAG = "APM: ActiveDeviceManagerService"; + private static ActiveDeviceManagerService sActiveDeviceManager = null; + private AudioManager mAudioManager; + private HandlerThread[] thread = new HandlerThread[AudioType.SIZE]; + private ShoStateMachine[] sm = new ShoStateMachine[AudioType.SIZE]; + private ApmNativeInterface apmNative; + private Context mContext; + private boolean txStreamSuspended = false; + private final Lock lock = new ReentrantLock(); + private final Condition mediaHandoffComplete = lock.newCondition(); + private final Condition voiceHandoffComplete = lock.newCondition(); + static class Event { + static final int SET_ACTIVE = 1; + static final int ACTIVE_DEVICE_CHANGE = 2; + static final int REMOVE_DEVICE = 3; + static final int DEVICE_REMOVED = 4; + static final int ACTIVATE_TIMEOUT = 5; + static final int DEACTIVATE_TIMEOUT = 6; + static final int SUSPEND_RECORDING = 7; + static final int RESUME_RECORDING = 8; + static final int RETRY_DEACTIVATE = 9; + static final int STOP_SM = 0; + } + + public static final int SHO_SUCCESS = 0; + public static final int SHO_PENDING = 1; + public static final int SHO_FAILED = 2; + public static final int ALREADY_ACTIVE = 3; + + static final int RETRY_LIMIT = 4; + static final int ACTIVATE_TIMEOUT_DELAY = 3000; + static final int DEACTIVATE_TIMEOUT_DELAY = 2000; + static final int DEACTIVATE_TRY_DELAY = 500; + + private ActiveDeviceManagerService (Context context) { + thread[AudioType.MEDIA] = new HandlerThread("ActiveDeviceManager.MediaThread"); + thread[AudioType.MEDIA].start(); + Looper mediaLooper = thread[AudioType.MEDIA].getLooper(); + sm[AudioType.MEDIA] = new ShoStateMachine(AudioType.MEDIA, mediaLooper); + + thread[AudioType.VOICE] = new HandlerThread("ActiveDeviceManager.VoiceThread"); + thread[AudioType.VOICE].start(); + Looper voiceLooper = thread[AudioType.VOICE].getLooper(); + sm[AudioType.VOICE] = new ShoStateMachine(AudioType.VOICE, voiceLooper); + + mContext = context; + apmNative = ApmNativeInterface.getInstance(); + apmNative.init(); + + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + Objects.requireNonNull(mAudioManager, + "AudioManager cannot be null when ActiveDeviceManagerService starts"); + } + + public static ActiveDeviceManagerService get(Context context) { + if(sActiveDeviceManager == null) { + sActiveDeviceManager = new ActiveDeviceManagerService(context); + ActiveDeviceManagerServiceIntf.init(sActiveDeviceManager); + } + return sActiveDeviceManager; + } + + public static ActiveDeviceManagerService get() { + return sActiveDeviceManager; + } + + public boolean setActiveDevice(BluetoothDevice device, Integer mAudioType, Boolean isUIReq, Boolean playReq) { + Log.d(TAG, "setActiveDevice(" + device + ") audioType: " + mAudioType); + boolean isCallActive = false; + if(ApmConst.AudioFeatures.CALL_AUDIO == mAudioType) { + CallAudio mCallAudio = CallAudio.get(); + isCallActive = mCallAudio.isAudioOn(); + } + + synchronized(sm[mAudioType]) { + sm[mAudioType].mSHOQueue.device = device; + sm[mAudioType].mSHOQueue.isUIReq = isUIReq; + sm[mAudioType].mSHOQueue.PlayReq = (playReq || isCallActive); + sm[mAudioType].mSHOQueue.isBroadcast = false; + sm[mAudioType].mSHOQueue.isRecordingMode = false; + sm[mAudioType].mSHOQueue.isGamingMode = false; + } + + if(device != null) { + sm[mAudioType].sendMessage(Event.SET_ACTIVE); + } + else { + if (mAudioType == AudioType.MEDIA && sm[mAudioType].mState == sm[mAudioType].mBroadcasting) { + Log.d(TAG, "LE Broadcast is active, ignore REMOVE_DEVICE"); + } else { + sm[mAudioType].sendMessage(Event.REMOVE_DEVICE); + } + } + return isUIReq; + } + + public boolean setActiveDevice(BluetoothDevice device, Integer mAudioType, Boolean isUIReq) { + return setActiveDevice(device, mAudioType, isUIReq, false); + } + + public boolean setActiveDevice(BluetoothDevice device, Integer mAudioType) { + return setActiveDevice(device, mAudioType, false, false); + } + + public boolean setActiveDeviceBlocking(BluetoothDevice device, Integer mAudioType) { + Log.d(TAG, "setActiveDeviceBlocking: Enter"); + if(ApmConst.AudioFeatures.CALL_AUDIO == mAudioType) { + setActiveDevice(device, mAudioType, false, false); + try { + lock.lock(); + voiceHandoffComplete.await(); + } catch (InterruptedException e) { + Log.d(TAG, "setActiveDeviceBlocking: Unblocked because of exception: " + e); + } finally { + Log.d(TAG, "setActiveDeviceBlocking: unlock"); + lock.unlock(); + } + } + Log.d(TAG, "setActiveDeviceBlocking: Exit"); + return true; + } + + public BluetoothDevice getQueuedDevice(Integer mAudioType) { + return sm[mAudioType].mSHOQueue.device; + } + + public boolean removeActiveDevice(Integer mAudioType, Boolean forceStopAudio) { + sm[mAudioType].mSHOQueue.forceStopAudio = forceStopAudio; + setActiveDevice(null, mAudioType, false); + return true; + } + + public BluetoothDevice getActiveDevice(Integer mAudioType) { + return sm[mAudioType].Current.Device; + } + + public int getActiveProfile(Integer mAudioType) { + if(sm[mAudioType].mState == sm[mAudioType].mBroadcasting) { + // Use Current.Profile here + return ApmConst.AudioProfiles.BROADCAST_LE; + } else if(sm[mAudioType].mState == sm[mAudioType].mActive) { + return sm[mAudioType].Current.Profile; + } + return ApmConst.AudioProfiles.NONE; + } + + public boolean onActiveDeviceChange(BluetoothDevice device, Integer mAudioType) { + return onActiveDeviceChange(device, mAudioType, ApmConst.AudioProfiles.NONE); + } + + public boolean onActiveDeviceChange(BluetoothDevice device, Integer mAudioType, Integer mProfile) { + if (device != null || mProfile == ApmConst.AudioProfiles.BROADCAST_LE) { + DeviceProfileCombo mDeviceProfileCombo = new DeviceProfileCombo(device, mProfile); + sm[mAudioType].sendMessage(Event.ACTIVE_DEVICE_CHANGE, mDeviceProfileCombo); + } + else + sm[mAudioType].sendMessage(Event.DEVICE_REMOVED); + return true; + } + + public boolean enableBroadcast(BluetoothDevice device) { + synchronized(sm[AudioType.MEDIA]) { + sm[AudioType.MEDIA].mSHOQueue.device = device; + sm[AudioType.MEDIA].mSHOQueue.isBroadcast = true; + sm[AudioType.MEDIA].mSHOQueue.PlayReq = false; + } + sm[AudioType.MEDIA].sendMessage(Event.SET_ACTIVE); + return true; + } + + public boolean disableBroadcast() { + Log.d(TAG, "disableBroadcast"); + synchronized(sm[AudioType.MEDIA]) { + sm[AudioType.MEDIA].mSHOQueue.isBroadcast = false; + sm[AudioType.MEDIA].mSHOQueue.PlayReq = false; + } + if (sm[AudioType.MEDIA].mState == sm[AudioType.MEDIA].mBroadcasting) { + sm[AudioType.MEDIA].sendMessage(Event.REMOVE_DEVICE); + } + return true; + } + + public boolean enableGaming(BluetoothDevice device) { + if (sm[AudioType.MEDIA].mState == sm[AudioType.MEDIA].mGamingMode && + device.equals(getActiveDevice(AudioType.MEDIA))) { + Log.d(TAG, "Device already in Gaming Mode"); + return true; + } + + Log.d(TAG, "enableGaming"); + synchronized(sm[AudioType.MEDIA]) { + sm[AudioType.MEDIA].mSHOQueue.device = device; + sm[AudioType.MEDIA].mSHOQueue.isBroadcast = false; + sm[AudioType.MEDIA].mSHOQueue.isGamingMode = true; + sm[AudioType.MEDIA].mSHOQueue.PlayReq = false; + } + sm[AudioType.MEDIA].sendMessage(Event.SET_ACTIVE); + return true; + } + + public boolean disableGaming(BluetoothDevice device) { + if (sm[AudioType.MEDIA].mState != sm[AudioType.MEDIA].mGamingMode) { + Log.e(TAG, "Gaming Mode not active"); + return true; + } + + /*MediaAudio mMediaAudio = MediaAudio.get(); + if(mMediaAudio != null && mMediaAudio.isA2dpPlaying(device)) { + Log.w(TAG, "Gaming Stream is Active"); + return false; + }*/ + + Log.d(TAG, "disableGaming"); + synchronized(sm[AudioType.MEDIA]) { + sm[AudioType.MEDIA].mSHOQueue.device = device; + sm[AudioType.MEDIA].mSHOQueue.isGamingMode = false; + sm[AudioType.MEDIA].mSHOQueue.PlayReq = false; + sm[AudioType.MEDIA].mSHOQueue.isUIReq = true; + } + + //sm[AudioType.MEDIA].sendMessageDelayed(Event.SET_ACTIVE, DEACTIVATE_TRY_DELAY); + sm[AudioType.MEDIA].sendMessage(Event.SET_ACTIVE); + return true; + } + + public boolean enableRecording(BluetoothDevice device) { + Log.d(TAG, "enableRecording: " + device); + + MediaAudio mMediaAudio = MediaAudio.get(); + if(txStreamSuspended == false) { + Log.d(TAG, "Set A2dpSuspended=true"); + mAudioManager.setParameters("A2dpSuspended=true"); + txStreamSuspended = true; + } + + synchronized(sm[AudioType.MEDIA]) { + sm[AudioType.MEDIA].mSHOQueue.device = device; + sm[AudioType.MEDIA].mSHOQueue.isBroadcast = false; + sm[AudioType.MEDIA].mSHOQueue.isGamingMode = false; + sm[AudioType.MEDIA].mSHOQueue.isRecordingMode = true; + sm[AudioType.MEDIA].mSHOQueue.PlayReq = false; + sm[AudioType.MEDIA].mSHOQueue.isUIReq = true; + } + sm[AudioType.MEDIA].sendMessage(Event.SET_ACTIVE); + return true; + } + + public boolean disableRecording(BluetoothDevice device) { + Log.d(TAG, "disableRecording: " + device); + + synchronized(sm[AudioType.MEDIA]) { + sm[AudioType.MEDIA].mSHOQueue.device = device; + sm[AudioType.MEDIA].mSHOQueue.isRecordingMode = false; + sm[AudioType.MEDIA].mSHOQueue.PlayReq = false; + } + + if (sm[AudioType.MEDIA].mState == sm[AudioType.MEDIA].mRecordingMode) { + sm[AudioType.MEDIA].sendMessage(Event.SET_ACTIVE); + } + return true; + } + + public boolean suspendRecording(Boolean suspend) { + Log.d(TAG, "suspendRecording: " + suspend); + + if(sm[AudioType.MEDIA].mState == sm[AudioType.MEDIA].mRecordingMode) { + if(suspend) { + sm[AudioType.MEDIA].sendMessage(Event.SUSPEND_RECORDING); + } else { + sm[AudioType.MEDIA].sendMessage(Event.RESUME_RECORDING); + } + } + return true; + } + + public boolean isRecordingActive(BluetoothDevice device) { + Log.d(TAG, "isRecordingActive"); + return sm[AudioType.MEDIA].mState == sm[AudioType.MEDIA].mRecordingMode; + } + + public boolean isStableState(int mAudioType) { + State state = sm[mAudioType].mState; + return !(sm[mAudioType].mActivating == state || sm[mAudioType].mDeactivating == state); + } + + private void broadcastActiveDeviceChange(BluetoothDevice device, int mAudioType) { + if (DBG) { + Log.d(TAG, "broadcastActiveDeviceChange(" + device + ")"); + } + Intent intent; + + /*if (mAdapterService != null) + BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_ACTIVE_DEVICE_CHANGED, BluetoothProfile.A2DP, + mAdapterService.obfuscateAddress(device), 0);*/ + + if(mAudioType == AudioType.MEDIA) + intent = new Intent(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED); + else + intent = new Intent(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + if(mAudioType == AudioType.MEDIA) { + A2dpService mA2dpService = A2dpService.getA2dpService(); + if(mA2dpService == null) { + Log.e(TAG, "A2dp Service not ready"); + return; + } + mA2dpService.sendBroadcast(intent, BLUETOOTH_CONNECT); + } else { + HeadsetService mHeadsetService = HeadsetService.getHeadsetService(); + if(mHeadsetService == null) { + Log.e(TAG, "Headset Service not ready"); + return; + } + mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_CONNECT, + Utils.getTempAllowlistBroadcastOptions()); + } + } + + public void disable() { + Log.d(TAG, "disable() called"); + sm[AudioType.MEDIA].sendMessage(Event.STOP_SM); + sm[AudioType.VOICE].sendMessage(Event.STOP_SM); + } + + public void cleanup() { + sm[AudioType.VOICE].doQuit(); + sm[AudioType.MEDIA].doQuit(); + thread[AudioType.VOICE].quitSafely(); + thread[AudioType.MEDIA].quitSafely(); + thread[AudioType.VOICE] = null; + thread[AudioType.MEDIA] = null; + sActiveDeviceManager = null; + } + + private class DeviceProfileCombo { + BluetoothDevice Device; + BluetoothDevice absoluteDevice; + int Profile; + + DeviceProfileCombo(BluetoothDevice mDevice, int mProfile) { + Device = mDevice; + Profile = mProfile; + } + + DeviceProfileCombo() { + Device = null; + Profile = ApmConst.AudioProfiles.NONE; + } + } + + private final class ShoStateMachine extends StateMachine { + private static final boolean DBG = true; + private static final String TAG = "APM: ActiveDeviceManagerService"; + + static final int IDLE = 0; + static final int ACTIVATING = 1; + static final int ACTIVE = 2; + static final int DEACTIVATING = 3; + static final int BROADCAST_ACTIVE = 4; + static final int GAMING_ACTIVE = 5; + static final int RECORDING_ACTIVE = 6; + + private Idle mIdle; + private Activating mActivating; + private Active mActive; + private Deactivating mDeactivating; + private Broadcasting mBroadcasting; + private Gaming mGamingMode; + private Recording mRecordingMode; + + private DeviceProfileCombo Current; + private DeviceProfileCombo Target; + private SHOReq mTargetSHO; + private SHOReq mSHOQueue; + + private DeviceProfileMap dpm; + + private int mAudioType; + private State mState; + private State mPrevState = null; + private BluetoothDevice mPrevActiveDevice; + private int mPrevActiveProfile = ApmConst.AudioProfiles.NONE; + boolean enabled; + boolean updatePending = false; + boolean mRecordingSuspended = false; + private String sAudioType; + + ShoStateMachine (int audioType, Looper looper) { + super(TAG, looper); + setDbg(DBG); + + mIdle = new Idle(); + mActivating = new Activating(); + mActive = new Active(); + mDeactivating = new Deactivating(); + mBroadcasting = new Broadcasting(); + mGamingMode = new Gaming(); + mRecordingMode = new Recording(); + + Current = new DeviceProfileCombo(); + Target = new DeviceProfileCombo(); + mSHOQueue = new SHOReq(); + mTargetSHO = new SHOReq(); + + addState(mIdle); + addState(mActivating); + addState(mActive); + addState(mDeactivating); + addState(mBroadcasting); + addState(mGamingMode); + addState(mRecordingMode); + + mAudioType = audioType; + if(mAudioType == AudioType.MEDIA) + sAudioType = new String("MEDIA"); + else if(mAudioType == AudioType.VOICE) + sAudioType = new String("VOICE"); + + enabled = true; + setInitialState(mIdle); + start(); + } + + public void doQuit () { + Log.i(TAG, "Stopping SHO StateMachine for " + mAudioType); + sAudioType = null; + quitNow(); + } + + /* public void cleanUp { + + }*/ + + private String messageWhatToString(int msg) { + switch (msg) { + case Event.SET_ACTIVE: + return "SET ACTIVE"; + case Event.ACTIVE_DEVICE_CHANGE: + return "ACTIVE DEVICE CHANGED"; + case Event.REMOVE_DEVICE: + return "REMOVE DEVICE"; + case Event.DEVICE_REMOVED: + return "REMOVED"; + case Event.ACTIVATE_TIMEOUT: + return "SET ACTIVE TIMEOUT"; + case Event.DEACTIVATE_TIMEOUT: + return "REMOVE DEVICE TIMEOUT"; + case Event.STOP_SM: + return "STOP STATE MACHINE"; + default: + break; + } + return Integer.toString(msg); + } + + int startSho(BluetoothDevice device, int profile) { + MediaAudio mMediaAudio = MediaAudio.get(); + int ret = SHO_FAILED; + StreamAudioService streamAudioService; + Log.e(TAG, ": startSho() for device: " + device + ", for profile: " + profile); + switch (profile) { + case ApmConst.AudioProfiles.A2DP: + A2dpService a2dpService = A2dpService.getA2dpService(); + if(a2dpService != null) + // pass play status here + ret = a2dpService.setActiveDevice(device, false); + break; + case ApmConst.AudioProfiles.HFP: + HeadsetService headsetService = HeadsetService.getHeadsetService(); + if(headsetService != null) + ret = headsetService.setActiveDeviceHF(device); + break; + case ApmConst.AudioProfiles.TMAP_MEDIA: + case ApmConst.AudioProfiles.BAP_MEDIA: + streamAudioService = StreamAudioService.getStreamAudioService(); + ret = streamAudioService.setActiveDevice(device, ApmConst.AudioProfiles.BAP_MEDIA, false); + if (ret == ActiveDeviceManagerService.ALREADY_ACTIVE) { + ret = SHO_SUCCESS; + } + break; + case ApmConst.AudioProfiles.BAP_RECORDING: + streamAudioService = StreamAudioService.getStreamAudioService(); + ret = streamAudioService.setActiveDevice(device, ApmConst.AudioProfiles.BAP_RECORDING, false); + if (ret == ActiveDeviceManagerService.ALREADY_ACTIVE) { + ret = SHO_SUCCESS; + } + break; + case ApmConst.AudioProfiles.TMAP_CALL: + case ApmConst.AudioProfiles.BAP_CALL: + streamAudioService = StreamAudioService.getStreamAudioService(); + ret = streamAudioService.setActiveDevice(device, profile, false); + break; + case ApmConst.AudioProfiles.BAP_GCP: + streamAudioService = StreamAudioService.getStreamAudioService(); + ret = streamAudioService.setActiveDevice(device, ApmConst.AudioProfiles.BAP_GCP, false); + if (ret == ActiveDeviceManagerService.ALREADY_ACTIVE) { + ret = SHO_SUCCESS; + } + break; + case ApmConst.AudioProfiles.BROADCAST_LE: + //ret = SHO_SUCCESS;//broadcastService.setActiveDevice(); + BroadcastService mBroadcastService = BroadcastService.getBroadcastService(); + if (mBroadcastService != null) + ret = mBroadcastService.setActiveDevice(device); + break; + case ApmConst.AudioProfiles.HAP_BREDR: + HearingAidService hearingAidService = HearingAidService.getHearingAidService(); + ret = hearingAidService.setActiveDevice(device) ? SHO_SUCCESS : SHO_FAILED; + break; + } + return ret; + } + + class Idle extends State { + @Override + public void enter() { + synchronized (this) { + mState = mIdle; + } + Current.Device = null; + //2 Update dependent profiles + if(mPrevState != null && mPrevActiveDevice != null) { + broadcastActiveDeviceChange (null, mAudioType); + + if (mAudioType == AudioType.MEDIA && + mPrevActiveProfile != ApmConst.AudioProfiles.HAP_BREDR) { + mPrevActiveProfile = ApmConst.AudioProfiles.NONE; + MediaAudio mMediaAudio = MediaAudio.get(); + boolean suppressNoisyIntent = !mTargetSHO.forceStopAudio + && (mMediaAudio.getConnectionState(mPrevActiveDevice) + == BluetoothProfile.STATE_CONNECTED); + /*TODO: Add profile check here*/ + if(mAudioManager != null) { + log("De-Activate Device " + mPrevActiveDevice + " Noisy Intent: " + suppressNoisyIntent); + mAudioManager.handleBluetoothA2dpActiveDeviceChange( + mPrevActiveDevice, BluetoothProfile.STATE_DISCONNECTED, + BluetoothProfile.A2DP, suppressNoisyIntent, -1); + } + } + + VolumeManager mVolumeManager = VolumeManager.get(); + if(mVolumeManager != null) { + mVolumeManager.onActiveDeviceChange(Current.Device, mAudioType); + } + } + + if(txStreamSuspended && mAudioType == AudioType.MEDIA) { + mAudioManager.setParameters("A2dpSuspended=false"); + txStreamSuspended = false; + } + + if(!enabled) + log("state machine stopped"); + } + + @Override + public void exit() { + mPrevState = mIdle; + mPrevActiveDevice = null; + } + + @Override + public boolean processMessage(Message message) { + log("Idle: Process Message (" + mAudioType + "): " + + messageWhatToString(message.what)); + if(!enabled) { + log("State Machine not running. Returning"); + return NOT_HANDLED; + } + + switch(message.what) { + case Event.SET_ACTIVE: + transitionTo(mActivating); + break; + + case Event.ACTIVE_DEVICE_CHANGE: + /* Might move to active here*/ + case Event.REMOVE_DEVICE: + log("Idle: Process Message Ignored"); + break; + case Event.STOP_SM: + enabled = false; + log("state machine stopped"); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class Activating extends State { + int ret; + @Override + public void enter() { + synchronized (this) { + mState = mActivating; + updatePending = true; + + Target.Device = mSHOQueue.device; + Target.absoluteDevice = Target.Device; + mTargetSHO.copy(mSHOQueue); + mSHOQueue.reset(); + Log.w(TAG, "Activating " + sAudioType + " Device: " + Target.Device); + } + + dpm = DeviceProfileMap.getDeviceProfileMapInstance(); + if (mTargetSHO.isBroadcast) { + Target.Profile = dpm.getProfile(Target.Device, ApmConst.AudioFeatures.BROADCAST_AUDIO); + mSHOQueue.device = Current.Device; + mSHOQueue.isUIReq = false; + } else if (mTargetSHO.isRecordingMode) { + mSHOQueue.device = Current.Device; + Target.Profile = ApmConst.AudioProfiles.BAP_RECORDING; + mSHOQueue.isUIReq = false; + } else if (mTargetSHO.isGamingMode) { + /*Only single profile supports gaming Mode*/ + Target.Profile = ApmConst.AudioProfiles.BAP_GCP; + } else { + Target.Profile = dpm.getProfile(Target.Device, mAudioType); + } + + if(Target.Profile == ApmConst.AudioProfiles.BAP_CALL || + Target.Profile == ApmConst.AudioProfiles.BAP_MEDIA || + Target.Profile == ApmConst.AudioProfiles.BAP_RECORDING || + Target.Profile == ApmConst.AudioProfiles.BAP_GCP) { + StreamAudioService streamAudioService = StreamAudioService.getStreamAudioService(); + Target.Device = streamAudioService.getDeviceGroup(Target.Device); + } + + if(Target.Device == null) { + Log.e(TAG, "Target Device is null, Returning"); + transitionTo(mPrevState); + updatePending = false; + return; + } + + if(Target.Device.equals(Current.Device) && Target.Profile == Current.Profile){ + Log.d(TAG,"Target Device: " + Target.Device + " and Profile: " + Target.Profile + + " already active"); + transitionTo(mPrevState); + updatePending = false; + return; + } + + if(Current.Device == null || isSameProfile(Current.Profile, Target.Profile, mAudioType)) { + /* Single Step SHO*/ + ActivateDevice(Target, mTargetSHO); + } else { + /*Multi Step SHO*/ + ret = startSho(null, Current.Profile); + if(SHO_PENDING == ret) { + sendMessageDelayed(Event.DEACTIVATE_TIMEOUT, DEACTIVATE_TIMEOUT_DELAY); + } else if(ret == SHO_FAILED) { + mTargetSHO.retryCount = 1; + sendMessageDelayed(Event.RETRY_DEACTIVATE, DEACTIVATE_TRY_DELAY); + } else if(SHO_SUCCESS == ret) { + mPrevState = mIdle; + Current.Device = null; + ActivateDevice(Target, mTargetSHO); + } + } + } + + @Override + public void exit() { + removeMessages(Event.ACTIVATE_TIMEOUT); + mPrevState = mActivating; + } + + @Override + public boolean processMessage(Message message) { + log("Activating: Process Message (" + mAudioType + "): " + + messageWhatToString(message.what)); + + switch(message.what) { + case Event.SET_ACTIVE: + log("New SHO request while handling previous. Add to queue"); + removeDeferredMessages(Event.REMOVE_DEVICE); + removeDeferredMessages(Event.SET_ACTIVE); + deferMessage(message); + break; + + case Event.ACTIVE_DEVICE_CHANGE: + DeviceProfileCombo mDeviceProfileCombo = (DeviceProfileCombo)message.obj; + removeMessages(Event.ACTIVATE_TIMEOUT); + if (Target.Profile == ApmConst.AudioProfiles.BAP_GCP + && Target.Profile == mDeviceProfileCombo.Profile) { + Current.Device = Target.Device; + Current.Profile = Target.Profile; + transitionTo(mGamingMode); + } else if (Target.Profile == ApmConst.AudioProfiles.BROADCAST_LE + && Target.Profile == mDeviceProfileCombo.Profile) { + Current.Device = Target.Device; + Current.Profile = Target.Profile; + transitionTo(mBroadcasting); + } else if(Target.Device != null && Target.Device.equals(mDeviceProfileCombo.Device)) { + Current.Device = mDeviceProfileCombo.Device; + Current.Profile = Target.Profile; + Current.absoluteDevice = Target.absoluteDevice; + transitionTo(mActive); + } + break; + + case Event.REMOVE_DEVICE: + removeDeferredMessages(Event.REMOVE_DEVICE); + deferMessage(message); + break; + + case Event.DEVICE_REMOVED: + mPrevState = mIdle; + Current.Device = null; + removeMessages(Event.DEACTIVATE_TIMEOUT); + ActivateDevice(Target, mTargetSHO); + break; + + case Event.RETRY_DEACTIVATE: + ret = startSho(null, Current.Profile); + if(SHO_PENDING == ret) { + mTargetSHO.retryCount = 0; + sendMessageDelayed(Event.DEACTIVATE_TIMEOUT, DEACTIVATE_TIMEOUT_DELAY); + } else if(ret == SHO_FAILED) { + if(mTargetSHO.retryCount >= RETRY_LIMIT) { + updatePending = false; + transitionTo(mPrevState); + } else { + mTargetSHO.retryCount++; + sendMessageDelayed(Event.RETRY_DEACTIVATE, DEACTIVATE_TRY_DELAY); + } + } else if(SHO_SUCCESS == ret) { + mTargetSHO.retryCount = 0; + mPrevState = mIdle; + Current.Device = null; + ActivateDevice(Target, mTargetSHO); + } + break; + + case Event.ACTIVATE_TIMEOUT: + case Event.DEACTIVATE_TIMEOUT: + transitionTo(mPrevState); + break; + + case Event.STOP_SM: + deferMessage(message); + break; + + default: + return NOT_HANDLED; + } + return HANDLED; + } + + void ActivateDevice(DeviceProfileCombo mTarget, SHOReq mTargetSHO) { + ret = startSho(mTarget.Device, mTarget.Profile); + if(SHO_PENDING == ret) { + Current.Device = mTarget.Device; + Current.absoluteDevice = mTarget.absoluteDevice; + Current.Profile = mTarget.Profile; + if(mAudioType == AudioType.MEDIA) { + sendActiveDeviceMediaUpdate(Current); + } + sendMessageDelayed(Event.ACTIVATE_TIMEOUT, ACTIVATE_TIMEOUT_DELAY); + } else if(ret == SHO_FAILED) { + if (mState == mBroadcasting) { + mTargetSHO.forceStopAudio = true; + Log.d(TAG,"Previous state was broadcasting, moving to idle"); + } + updatePending = false; + transitionTo(mPrevState); + } else if(SHO_SUCCESS == ret) { + Current.Device = mTarget.Device; + Current.absoluteDevice = mTarget.absoluteDevice; + Current.Profile = mTarget.Profile; + if(mTargetSHO.isBroadcast) { + transitionTo(mBroadcasting); + } else if (mTargetSHO.isGamingMode) { + transitionTo(mGamingMode); + } else if (mTargetSHO.isRecordingMode) { + transitionTo(mRecordingMode); + } else { + transitionTo(mActive); + } + } else if(ALREADY_ACTIVE == ret) { + transitionTo(mActive); + } + } + } + + class Deactivating extends State { + int ret; + @Override + public void enter() { + synchronized (this) { + mState = mDeactivating; + } + if (mPrevState == mBroadcasting) { + mPrevState = mIdle; + } + Target.Device = null; + Target.Profile = Current.Profile; + mTargetSHO.copy(mSHOQueue); + mSHOQueue.reset(); + + ret = startSho(Target.Device, Target.Profile); + Log.d(TAG, "ret: " + ret); + if (SHO_SUCCESS == ret) { + transitionTo(mIdle); + } else if (SHO_PENDING == ret) { + sendMessageDelayed(Event.DEACTIVATE_TIMEOUT, DEACTIVATE_TIMEOUT_DELAY); + } else { + transitionTo(mPrevState); + } + } + + @Override + public void exit() { + removeMessages(Event.DEACTIVATE_TIMEOUT); + mPrevState = mDeactivating; + mPrevActiveDevice = Current.Device; + Current.Device = null; + mPrevActiveProfile = Current.Profile; + Current.Profile = ApmConst.AudioProfiles.NONE; // Add profile value here + } + + @Override + public boolean processMessage(Message message) { + log("Deactivating: Process Message (" + mAudioType + "): " + + messageWhatToString(message.what)); + + switch(message.what) { + case Event.SET_ACTIVE: + log("New SHO request while handling previous. Add to queue"); + removeDeferredMessages(Event.SET_ACTIVE); + deferMessage(message); + break; + + case Event.ACTIVE_DEVICE_CHANGE: + break; + + case Event.REMOVE_DEVICE: + break; + + case Event.DEVICE_REMOVED: + removeMessages(Event.DEACTIVATE_TIMEOUT); + transitionTo(mIdle); + break; + + case Event.DEACTIVATE_TIMEOUT: + transitionTo(mPrevState); + + case Event.STOP_SM: + deferMessage(message); + break; + + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class Active extends State { + int ret; + @Override + public void enter() { + synchronized (this) { + mState = mActive; + } + if(updatePending) { + if(mAudioType == AudioType.MEDIA) + sendActiveDeviceMediaUpdate(Current); + else if(mAudioType == AudioType.VOICE) + sendActiveDeviceVoiceUpdate(Current); + } + if(txStreamSuspended && mAudioType == AudioType.MEDIA) { + mAudioManager.setParameters("A2dpSuspended=false"); + txStreamSuspended = false; + } else if (mAudioType == AudioType.VOICE) { + lock.lock(); + voiceHandoffComplete.signal(); + lock.unlock(); + Log.d(TAG, "Voice Active: unlock by signal"); + } + } + + @Override + public void exit() { + //2 update dependent profiles + mPrevState = mActive; + mPrevActiveDevice = Current.Device; + VolumeManager mVolumeManager = VolumeManager.get(); + if(mVolumeManager != null) { + mVolumeManager.saveVolume(mAudioType); + } + } + + @Override + public boolean processMessage(Message message) { + log("Active: Process Message (" + mAudioType + "): " + + messageWhatToString(message.what)); + + switch(message.what) { + case Event.SET_ACTIVE: + if(mSHOQueue.device == null) { + Log.w(TAG, "Invalid request"); + break; + } + transitionTo(mActivating); + break; + + case Event.ACTIVE_DEVICE_CHANGE: + // might have to handle + break; + + case Event.REMOVE_DEVICE: + transitionTo(mDeactivating); + break; + + case Event.DEVICE_REMOVED: + //might have to handle + break; + + case Event.STOP_SM: + transitionTo(mDeactivating); + enabled = false; + break; + + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class Gaming extends State { + int ret; + @Override + public void enter() { + synchronized (this) { + mState = mGamingMode; + } + if(updatePending) { + sendActiveDeviceGamingUpdate(Current); + } + if(txStreamSuspended) { + mAudioManager.setParameters("A2dpSuspended=false"); + txStreamSuspended = false; + } + } + + @Override + public void exit() { + //2 update dependent profiles + mPrevState = mGamingMode; + mPrevActiveDevice = Current.Device; + VolumeManager mVolumeManager = VolumeManager.get(); + if(mVolumeManager != null) { + mVolumeManager.saveVolume(mAudioType); + } + } + + @Override + public boolean processMessage(Message message) { + log("Gaming: Process Message (" + mAudioType + "): " + + messageWhatToString(message.what)); + + switch(message.what) { + case Event.SET_ACTIVE: + if(mSHOQueue.device == null) { + Log.w(TAG, "Invalid request"); + break; + } + if(!mSHOQueue.isUIReq && Current.Device.equals(mSHOQueue.device)) { + Log.w(TAG, "Spurious request for same device. Ignore"); + mSHOQueue.reset(); + break; + } + /*MediaAudio mMediaAudio = MediaAudio.get(); + if(mMediaAudio != null && mMediaAudio.isA2dpPlaying(mSHOQueue.device)) { + if(!(mSHOQueue.isBroadcast || mSHOQueue.isRecordingMode)) { + Log.w(TAG, "Gaming streaming is on"); + break; + } + }*/ + transitionTo(mActivating); + break; + + case Event.ACTIVE_DEVICE_CHANGE: + // might have to handle + break; + + case Event.REMOVE_DEVICE: + transitionTo(mDeactivating); + break; + + case Event.DEVICE_REMOVED: + //might have to handle + break; + + case Event.STOP_SM: + transitionTo(mDeactivating); + enabled = false; + break; + + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class Broadcasting extends State { + int ret; + @Override + public void enter() { + synchronized (this) { + mState = mBroadcasting; + } + if (updatePending) { + apmNative.activeDeviceUpdate(Current.Device, Current.Profile, mAudioType); + broadcastActiveDeviceChange(Current.Device, AudioType.MEDIA); + mAudioManager.avrcpSupportsAbsoluteVolume(Current.Device.getAddress(), false); + // Update active device to null in VolumeManager while enter broadcasting state + VolumeManager mVolumeManager = VolumeManager.get(); + if(mVolumeManager != null) { + mVolumeManager.onActiveDeviceChange(null, + ApmConst.AudioFeatures.MEDIA_AUDIO); + } + int rememberedVolume = 15; + mAudioManager.handleBluetoothA2dpActiveDeviceChange( + Current.Device, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, + true, rememberedVolume); + updatePending = false; + } + if(txStreamSuspended) { + mAudioManager.setParameters("A2dpSuspended=false"); + txStreamSuspended = false; + } + } + + @Override + public void exit() { + mPrevState = mBroadcasting; + mPrevActiveDevice = Current.Device; + } + + @Override + public boolean processMessage(Message message) { + log("Broadcasting: Process Message (" + mAudioType + "): " + + messageWhatToString(message.what)); + + switch(message.what) { + case Event.SET_ACTIVE: + if(mSHOQueue.isUIReq) + transitionTo(mActivating); + break; + + case Event.ACTIVE_DEVICE_CHANGE: + break; + + case Event.REMOVE_DEVICE: + if(mSHOQueue.device == null) { + transitionTo(mDeactivating); + } else { + transitionTo(mActivating); + } + break; + + case Event.DEVICE_REMOVED: + break; + + case Event.STOP_SM: + transitionTo(mDeactivating); + enabled = false; + break; + + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class Recording extends State { + int ret; + @Override + public void enter() { + synchronized (this) { + mState = mRecordingMode; + } + if(updatePending) { + sendActiveDeviceRecordingUpdate(Current); + } + mRecordingSuspended = false; + } + + @Override + public void exit() { + mPrevState = mRecordingMode; + mPrevActiveDevice = Current.Device; + HeadsetService hfpService = HeadsetService.getHeadsetService(); + + mAudioManager.handleBluetoothA2dpActiveDeviceChange( + mPrevActiveDevice, + BluetoothProfile.STATE_DISCONNECTED, + BluetoothProfile.A2DP_SINK, + true, -1); + if(mRecordingSuspended) { + mAudioManager.setParameters("A2dpCaptureSuspend=false"); + mRecordingSuspended = false; + } + CallAudio mCallAudio = CallAudio.get(); + boolean isInCall = mCallAudio != null && + mCallAudio.isVoiceOrCallActive(); + if(isInCall) { + Log.d(TAG, " reset txStreamSuspended as call is active" ); + txStreamSuspended = false; + } + VolumeManager mVolumeManager = VolumeManager.get(); + if(mVolumeManager != null) { + mVolumeManager.saveVolume(mAudioType); + } + } + + @Override + public boolean processMessage(Message message) { + log("Recording: Process Message (" + mAudioType + "): " + + messageWhatToString(message.what)); + + switch(message.what) { + case Event.SET_ACTIVE: + if (mSHOQueue.device == null) { + transitionTo(mDeactivating); + } else { + transitionTo(mActivating); + } + break; + + case Event.ACTIVE_DEVICE_CHANGE: + break; + + case Event.REMOVE_DEVICE: + transitionTo(mDeactivating); + break; + + case Event.DEVICE_REMOVED: + //might have to handle + break; + case Event.SUSPEND_RECORDING: { + if(mRecordingSuspended) break; + mAudioManager.setParameters("A2dpCaptureSuspend=true"); + mRecordingSuspended = true; + } break; + + case Event.RESUME_RECORDING: { + if(!mRecordingSuspended) break; + mAudioManager.setParameters("A2dpCaptureSuspend=false"); + mRecordingSuspended = false; + } break; + case Event.STOP_SM: + transitionTo(mDeactivating); + enabled = false; + break; + + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + void sendActiveDeviceMediaUpdate(DeviceProfileCombo Current) { + if(Current.Profile == ApmConst.AudioProfiles.HAP_BREDR) { + if(mPrevActiveDevice != null) { + broadcastActiveDeviceChange (null, AudioType.MEDIA ); + ActiveDeviceManager mDeviceManager = AdapterService.getAdapterService().getActiveDeviceManager(); + mDeviceManager.onActiveDeviceChange(null, ApmConst.AudioFeatures.MEDIA_AUDIO); + mAudioManager.handleBluetoothA2dpActiveDeviceChange( + mPrevActiveDevice, BluetoothProfile.STATE_DISCONNECTED, + BluetoothProfile.A2DP, true, -1); + } + return; + } + apmNative.activeDeviceUpdate(Current.Device, Current.Profile, AudioType.MEDIA); + Log.d(TAG, "sendActiveDeviceMediaUpdate: mPrevActiveDevice: " + + mPrevActiveDevice + ", Current.Device: " + Current.Device); + + MediaAudio mMediaAudio = MediaAudio.get(); + mMediaAudio.refreshCurrentCodec(Current.Device); + + broadcastActiveDeviceChange (Current.absoluteDevice, AudioType.MEDIA); + ActiveDeviceManager mDeviceManager = AdapterService.getAdapterService().getActiveDeviceManager(); + mDeviceManager.onActiveDeviceChange(Current.Device, ApmConst.AudioFeatures.MEDIA_AUDIO); + if(Current.Profile == ApmConst.AudioProfiles.A2DP) { + A2dpService mA2dpService = A2dpService.getA2dpService(); + mA2dpService.broadcastActiveCodecConfig(); + } + //2 Update dependent profiles + VolumeManager mVolumeManager = VolumeManager.get(); + if(mVolumeManager != null) { + mVolumeManager.onActiveDeviceChange(Current.Device, + ApmConst.AudioFeatures.MEDIA_AUDIO); + } + + McpService mMcpService = McpService.getMcpService(); + if (mMcpService != null) { + mMcpService.SetActiveDevices(Current.absoluteDevice, Current.Profile); + } + int deviceVolume = 7; + if(mVolumeManager != null) { + deviceVolume = mVolumeManager.getActiveVolume(ApmConst.AudioFeatures.MEDIA_AUDIO); + } + if(mAudioManager != null) { + mAudioManager.handleBluetoothA2dpActiveDeviceChange( + Current.Device, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, + true, deviceVolume); + } + updatePending = false; + } + + void sendActiveDeviceGamingUpdate(DeviceProfileCombo Current) { + apmNative.activeDeviceUpdate(Current.Device, Current.Profile, AudioType.MEDIA); + + MediaAudio mMediaAudio = MediaAudio.get(); + mMediaAudio.refreshCurrentCodec(Current.Device); + + broadcastActiveDeviceChange (Current.absoluteDevice, AudioType.MEDIA); + ActiveDeviceManager mDeviceManager = AdapterService.getAdapterService().getActiveDeviceManager(); + mDeviceManager.onActiveDeviceChange(Current.Device, ApmConst.AudioFeatures.MEDIA_AUDIO); + + //2 Update dependent profiles + VolumeManager mVolumeManager = VolumeManager.get(); + int deviceVolume = 7; + if(mVolumeManager != null) { + mVolumeManager.onActiveDeviceChange(Current.Device, + ApmConst.AudioFeatures.MEDIA_AUDIO); + deviceVolume = mVolumeManager.getActiveVolume(ApmConst.AudioFeatures.MEDIA_AUDIO); + } + if(mAudioManager != null) { + mAudioManager.handleBluetoothA2dpActiveDeviceChange( + Current.Device, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, + true, deviceVolume); + + /*Add back channel call here*/ + } + updatePending = false; + } + + void sendActiveDeviceVoiceUpdate(DeviceProfileCombo Current) { + Log.d(TAG, "sendActiveDeviceVoiceUpdate"); + if(Current.Profile == ApmConst.AudioProfiles.HAP_BREDR) { + if(mPrevActiveDevice != null) { + broadcastActiveDeviceChange (null, AudioType.VOICE); + ActiveDeviceManager mDeviceManager = AdapterService.getAdapterService().getActiveDeviceManager(); + mDeviceManager.onActiveDeviceChange(Current.Device, ApmConst.AudioFeatures.CALL_AUDIO); + } + return; + } + broadcastActiveDeviceChange (Current.absoluteDevice, AudioType.VOICE); + ActiveDeviceManager mDeviceManager = AdapterService.getAdapterService().getActiveDeviceManager(); + mDeviceManager.onActiveDeviceChange(Current.Device, ApmConst.AudioFeatures.CALL_AUDIO); + VolumeManager mVolumeManager = VolumeManager.get(); + if(mVolumeManager != null) { + mVolumeManager.onActiveDeviceChange(Current.Device, + ApmConst.AudioFeatures.CALL_AUDIO); + } + CCService ccService = CCService.getCCService(); + if (ccService != null) { + ccService.setActiveDevice(Current.absoluteDevice); + } + + if(mTargetSHO.PlayReq) { + CallAudio mCallAudio = CallAudio.get(); + mCallAudio.connectAudio(); + } + + updatePending = false; + } + + void sendActiveDeviceRecordingUpdate(DeviceProfileCombo Current) { + apmNative.activeDeviceUpdate(Current.Device, Current.Profile, AudioType.MEDIA); + + Log.d(TAG, "sendActiveDeviceRecordingUpdate: mPrevActiveDevice: " + + mPrevActiveDevice + ", Current.Device: " + Current.Device); + MediaAudio mMediaAudio = MediaAudio.get(); + mMediaAudio.refreshCurrentCodec(Current.Device); + if (mAudioManager != null) { + mAudioManager.handleBluetoothA2dpActiveDeviceChange(Current.Device, + BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP_SINK, false, -1); // TO-Check + } + updatePending = false; + } + + boolean isSameProfile (int p1, int p2, int audioType) { + if(p1 == p2) { + return true; + } + + if(audioType == AudioType.MEDIA) { + int leMediaMask = ApmConst.AudioProfiles.TMAP_MEDIA | + ApmConst.AudioProfiles.BAP_MEDIA | + ApmConst.AudioProfiles.BAP_RECORDING | + ApmConst.AudioProfiles.BAP_GCP; + if((leMediaMask & p1) > 0 && (leMediaMask & p2) > 0) { + return true; + } + } else if(audioType == AudioType.VOICE) { + int leVoiceMask = ApmConst.AudioProfiles.TMAP_CALL | ApmConst.AudioProfiles.BAP_CALL; + if((leVoiceMask & p1) > 0 && (leVoiceMask & p2) > 0) { + return true; + } + } + + return false; + } + } + + static class AudioType { + public static int VOICE = ApmConst.AudioFeatures.CALL_AUDIO; + public static int MEDIA = ApmConst.AudioFeatures.MEDIA_AUDIO; + + public static int SIZE = 2; + } + + private class SHOReq { + BluetoothDevice device; + boolean PlayReq; + int retryCount; + boolean isBroadcast; + boolean isGamingMode; + boolean isRecordingMode; + boolean isUIReq; + boolean forceStopAudio; + + void copy(SHOReq src) { + device = src.device; + PlayReq = src.PlayReq; + retryCount = src.retryCount; + isBroadcast = src.isBroadcast; + isGamingMode = src.isGamingMode; + isRecordingMode = src.isRecordingMode; + isUIReq = src.isUIReq; + forceStopAudio = src.forceStopAudio; + } + + void reset() { + device = null; + PlayReq = false; + retryCount = 0; + isBroadcast = false; + isGamingMode = false; + isRecordingMode = false; + isUIReq = false; + forceStopAudio = false; + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ApmConst.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ApmConst.java new file mode 100644 index 00000000000..786c4d19597 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ApmConst.java @@ -0,0 +1,56 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * +*****************************************************************************/ + +package com.android.bluetooth.apm; + +public class ApmConst { + + private static boolean leAudioEnabled = false; + public static final String groupAddress = "9E:8B:00:00:00"; + + public static boolean getLeAudioEnabled() { + return leAudioEnabled; + } + + protected static void setLeAudioEnabled(boolean leAudioSupport) { + leAudioEnabled = leAudioSupport; + } + + public static class AudioFeatures { + + public static final int CALL_AUDIO = 0; + public static final int MEDIA_AUDIO = 1; + public static final int CALL_CONTROL = 2; + public static final int MEDIA_CONTROL = 3; + public static final int MEDIA_VOLUME_CONTROL = 4; + public static final int CALL_VOLUME_CONTROL = 5; + public static final int BROADCAST_AUDIO = 6; + public static final int HEARING_AID = 7; + public static final int MAX_AUDIO_FEATURES = 8; + + } + + public static class AudioProfiles { + + public static final int NONE = 0x0000; + public static final int A2DP = 0x0001; + public static final int HFP = 0x0002; + public static final int AVRCP = 0x0004; + public static final int TMAP_MEDIA = 0x0008; + public static final int BAP_MEDIA = 0x0010; + public static final int MCP = 0x0020; + public static final int CCP = 0x0040; + public static final int VCP = 0x0080; + public static final int HAP_BREDR = 0x0100; + public static final int HAP_LE = 0x0200; + public static final int BROADCAST_BREDR = 0x0400; + public static final int BROADCAST_LE = 0x0800; + public static final int TMAP_CALL = 0x1000; + public static final int BAP_CALL = 0x2000; + public static final int BAP_GCP = 0x4000; + public static final int BAP_RECORDING = 0x8000; + + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ApmNativeInterface.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ApmNativeInterface.java new file mode 100644 index 00000000000..44a84a7e3e2 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ApmNativeInterface.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + **************************************************************************/ + +package com.android.bluetooth.apm; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.util.Log; +import java.util.List; + +import com.android.bluetooth.Utils; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +/** + * A2DP Native Interface to/from JNI. + */ +public class ApmNativeInterface { + private static final String TAG = "ApmNativeInterface"; + private static final boolean DBG = true; + + @GuardedBy("INSTANCE_LOCK") + private static ApmNativeInterface sInstance; + private BluetoothAdapter mAdapter; + private static final Object INSTANCE_LOCK = new Object(); + + static { + classInitNative(); + } + + @VisibleForTesting + private ApmNativeInterface() { + mAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mAdapter == null) { + Log.w(TAG, "No Bluetooth Adapter Available"); + } + } + + /** + * Get singleton instance. + */ + public static ApmNativeInterface getInstance() { + synchronized (INSTANCE_LOCK) { + if (sInstance == null) { + sInstance = new ApmNativeInterface(); + } + return sInstance; + } + } + + /** + * Initializes the native interface. + */ + public void init() { + initNative(); + } + + /** + * Cleanup the native interface. + */ + public void cleanup() { + cleanupNative(); + } + + /** + * Report new active device to stack. + * + * @param device: new active device + * @param profile: new active profile + * @return true on success, otherwise false. + */ + public boolean activeDeviceUpdate(BluetoothDevice device, int profile, int audioType) { + return activeDeviceUpdateNative(getByteAddress(device), profile, audioType); + } + + /** + * Report Content Control ID to stack. + * + * @param id: Content Control ID + * @param profile: content control profile + * @return true on success, otherwise false. + */ + public boolean setContentControl(int id, int profile) { + return setContentControlNative(id, profile); + } + + private BluetoothDevice getDevice(byte[] address) { + return mAdapter.getRemoteDevice(address); + } + + private byte[] getByteAddress(BluetoothDevice device) { + if (device == null) { + return Utils.getBytesFromAddress("00:00:00:00:00:00"); + } + return Utils.getBytesFromAddress(device.getAddress()); + } + + //Current logic is implemented only for CALL_AUDIO + //Proper Audio_type needs to be sent to device profile map + //for other audio features + private int getActiveProfile(byte[] address, int audio_type) { + DeviceProfileMap dpm = DeviceProfileMap.getDeviceProfileMapInstance(); + BluetoothDevice device = getDevice(address); + int profile = dpm.getProfile(device, ApmConst.AudioFeatures.CALL_AUDIO); + return profile; + } + + // Native methods that call into the JNI interface + private static native void classInitNative(); + private native void initNative(); + private native void cleanupNative(); + private native boolean activeDeviceUpdateNative(byte[] address, int profile, int audioType); + private native boolean setContentControlNative(int id, int profile); +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/CallAudio.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/CallAudio.java new file mode 100644 index 00000000000..ae0bcc20996 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/CallAudio.java @@ -0,0 +1,744 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * +*****************************************************************************/ + +package com.android.bluetooth.apm; + +import static android.Manifest.permission.BLUETOOTH_CONNECT; + +import android.bluetooth.BluetoothDevice; +import com.android.bluetooth.hfp.HeadsetService; +import com.android.bluetooth.hfp.HeadsetA2dpSync; +import com.android.bluetooth.apm.ApmConst; +import com.android.bluetooth.apm.MediaAudio; +import com.android.bluetooth.apm.CallControl; +import android.media.AudioManager; +import com.android.bluetooth.apm.ActiveDeviceManagerService; +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.btservice.AdapterService; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; +import com.android.bluetooth.Utils; +import android.content.Context; +import java.lang.Integer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import android.util.Log; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import android.content.Intent; +import android.os.UserHandle; + +public class CallAudio { + + private static CallAudio mCallAudio; + private static final String TAG = "APM: CallAudio"; + Map mCallDevicesMap; + private Context mContext; + private AudioManager mAudioManager; + private ActiveDeviceManagerService mActiveDeviceManager; + private AdapterService mAdapterService; + public static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; + public static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; + public static final String BLUETOOTH_PRIVILEGED = + android.Manifest.permission.BLUETOOTH_PRIVILEGED; + private static final int MAX_DEVICES = 200; + public boolean mVirtualCallStarted; + private CallControl mCallControl = null; + + private CallAudio(Context context) { + Log.d(TAG, "Initialization"); + mContext = context; + mCallDevicesMap = new ConcurrentHashMap(); + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + mActiveDeviceManager = ActiveDeviceManagerService.get(); + mAdapterService = AdapterService.getAdapterService(); + mCallControl = CallControl.get(); + } + + public static CallAudio init(Context context) { + if(mCallAudio == null) { + mCallAudio = new CallAudio(context); + CallAudioIntf.init(mCallAudio); + } + return mCallAudio; + } + + public static CallAudio get() { + return mCallAudio; + } + + public boolean connect(BluetoothDevice device) { + Log.i(TAG, "connect: " + device); + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + if(device == null) + return false; + boolean status; + if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { + Log.e(TAG, "Cannot connect to " + device + " : CONNECTION_POLICY_FORBIDDEN"); + return false; + } + + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + if (dMap == null) + return false; + + int profileID = dMap.getSupportedProfile(device, ApmConst.AudioFeatures.CALL_AUDIO); + if (profileID == ApmConst.AudioProfiles.NONE) { + Log.e(TAG, "Can Not connect to " + device + ". Device does not support call service."); + return false; + } + + CallDevice mCallDevice = mCallDevicesMap.get(device.getAddress()); + if(mCallDevice == null) { + if(mCallDevicesMap.size() >= MAX_DEVICES) + return false; + mCallDevice = new CallDevice(device, profileID); + mCallDevicesMap.put(device.getAddress(), mCallDevice); + } else if(mCallDevice.deviceConnStatus != BluetoothProfile.STATE_DISCONNECTED) { + Log.i(TAG, "Device already connected"); + return false; + } + + if((ApmConst.AudioProfiles.HFP & profileID) == ApmConst.AudioProfiles.HFP) { + HeadsetService service = HeadsetService.getHeadsetService(); + if (service == null) { + return false; + } + service.connectHfp(device); + } + + StreamAudioService mStreamService = StreamAudioService.getStreamAudioService(); + if(mStreamService != null && + (ApmConst.AudioProfiles.BAP_CALL & profileID) == ApmConst.AudioProfiles.BAP_CALL) { + mStreamService.connectLeStream(device, profileID); + } + return true; + } + + public boolean connect(BluetoothDevice device, Boolean allProfiles) { + if(allProfiles) { + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + if (dMap == null) + return false; + + int profileID = dMap.getSupportedProfile(device, ApmConst.AudioFeatures.CALL_AUDIO); + if((ApmConst.AudioProfiles.HFP & profileID) == ApmConst.AudioProfiles.HFP) { + HeadsetService service = HeadsetService.getHeadsetService(); + if (service == null) { + return false; + } + return service.connectHfp(device); + } else { + /*Common connect for LE Media and Call handled from StreamAudioService*/ + return true; + } + } + + return connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + Log.i(TAG, " disconnect: " + device); + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); + CallDevice mCallDevice; + + if(device == null) + return false; + + mCallDevice = mCallDevicesMap.get(device.getAddress()); + if(mCallDevice == null) { + Log.e(TAG, "Ignore: Device " + device + " not present in list"); + return false; + } + + if (mCallDevice.profileConnStatus[CallDevice.SCO_STREAM] != BluetoothProfile.STATE_DISCONNECTED) { + HeadsetService service = HeadsetService.getHeadsetService(); + if(service != null) { + service.disconnectHfp(device); + } + } + + if (mCallDevice.profileConnStatus[CallDevice.LE_STREAM] != BluetoothProfile.STATE_DISCONNECTED) { + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if(service != null) { + service.disconnectLeStream(device, true, false); + } + } + + return true; + } + + public boolean disconnect(BluetoothDevice device, Boolean allProfiles) { + if(allProfiles) { + CallDevice mCallDevice = mCallDevicesMap.get(device.getAddress()); + if(mCallDevice == null) { + Log.e(TAG, "Ignore: Device " + device + " not present in list"); + return false; + } + if(mCallDevice.profileConnStatus[CallDevice.SCO_STREAM] != BluetoothProfile.STATE_DISCONNECTED) { + return disconnect(device); + } else { + /*Common connect for LE Media and Call handled from StreamAudioService*/ + return true; + } + } + + return disconnect(device); + } + + public boolean startScoUsingVirtualVoiceCall() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + Log.d(TAG, "startScoUsingVirtualVoiceCall"); + BluetoothDevice mActivedevice = null; + int profile; + mActiveDeviceManager = ActiveDeviceManagerService.get(); + if(mActiveDeviceManager != null) { + mActivedevice = mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.CALL_AUDIO); + if(mActivedevice == null) { + Log.e(TAG, "startScoUsingVirtualVoiceCall failed. Active Device is null"); + return false; + } + } else { + return false; + } + + checkA2dpState(); + + profile = mActiveDeviceManager.getActiveProfile(ApmConst.AudioFeatures.CALL_AUDIO); + switch(profile) { + case ApmConst.AudioProfiles.HFP: + HeadsetService headsetService = HeadsetService.getHeadsetService(); + if(headsetService != null) { + if(headsetService.startScoUsingVirtualVoiceCall()) { + mVirtualCallStarted = true; + return true; + } + } + break; + case ApmConst.AudioProfiles.BAP_CALL: + case ApmConst.AudioProfiles.TMAP_CALL: + StreamAudioService mStreamAudioService = StreamAudioService.getStreamAudioService(); + if(mStreamAudioService != null) { + if(mStreamAudioService.startStream(mActivedevice)) { + mVirtualCallStarted = true; + mCallControl = CallControl.get(); + if (mCallControl != null) { + mCallControl.setVirtualCallActive(true); + } + return true; + } + } + break; + default: + Log.e(TAG, "Unhandled profile"); + break; + } + + Log.e(TAG, "startScoUsingVirtualVoiceCall failed. Device: " + mActivedevice); + if(ApmConst.AudioProfiles.HFP != profile) { + HeadsetService service = HeadsetService.getHeadsetService(); + if(service != null) { + service.getHfpA2DPSyncInterface().releaseA2DP(null); + } + } + return false; + } + + public boolean stopScoUsingVirtualVoiceCall() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + Log.d(TAG, "stopScoUsingVirtualVoiceCall"); + BluetoothDevice mActivedevice = null; + int profile; + mActiveDeviceManager = ActiveDeviceManagerService.get(); + if(mActiveDeviceManager != null) { + mActivedevice = mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.CALL_AUDIO); + if(mActivedevice == null) { + Log.e(TAG, "stopScoUsingVirtualVoiceCall failed. Active Device is null"); + return false; + } + } else { + return false; + } + + profile = mActiveDeviceManager.getActiveProfile(ApmConst.AudioFeatures.CALL_AUDIO); + switch(profile) { + case ApmConst.AudioProfiles.HFP: + HeadsetService headsetService = HeadsetService.getHeadsetService(); + if(headsetService != null) { + mVirtualCallStarted = false; + return headsetService.stopScoUsingVirtualVoiceCall(); + } + break; + case ApmConst.AudioProfiles.BAP_CALL: + case ApmConst.AudioProfiles.TMAP_CALL: + StreamAudioService mStreamAudioService = StreamAudioService.getStreamAudioService(); + if(mStreamAudioService != null) { + mVirtualCallStarted = false; + mCallControl = CallControl.get(); + if (mCallControl != null) { + mCallControl.setVirtualCallActive(false); + } + return mStreamAudioService.stopStream(mActivedevice); + } + break; + default: + Log.e(TAG, "Unhandled profile"); + break; + } + + Log.e(TAG, "stopScoUsingVirtualVoiceCall failed. Device: " + mActivedevice); + return false; + } + + void remoteDisconnectVirtualVoiceCall(BluetoothDevice device) { + if(device == null) + return; + ActiveDeviceManagerService mActiveDeviceManager = ActiveDeviceManagerService.get(); + if(device.equals(mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.CALL_AUDIO)) && + mActiveDeviceManager.isStableState(ApmConst.AudioFeatures.CALL_AUDIO)) { + stopScoUsingVirtualVoiceCall(); + } + } + + int getProfile(BluetoothDevice mDevice) { + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + int profileID = dMap.getProfile(mDevice, ApmConst.AudioFeatures.CALL_AUDIO); + Log.d(TAG," getProfile for device " + mDevice + " profileID " + profileID); + return profileID; + } + + void checkA2dpState() { + MediaAudio sMediaAudio = MediaAudio.get(); + BluetoothDevice sMediaActivedevice = + mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO); + //if(sMediaAudio.isA2dpPlaying(sMediaActivedevice)) { + Log.d(TAG," suspendA2DP isA2dpPlaying true " + " for device " + sMediaActivedevice); + int profileID = mActiveDeviceManager.getActiveProfile( + ApmConst.AudioFeatures.CALL_AUDIO); + if(ApmConst.AudioProfiles.HFP != profileID) { + HeadsetService service = HeadsetService.getHeadsetService(); + if(service != null) { + service.getHfpA2DPSyncInterface().suspendA2DP( + HeadsetA2dpSync.A2DP_SUSPENDED_BY_CS_CALL, null); + } + } + //} + } + + public boolean connectAudio() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + BluetoothDevice mActivedevice = null; + boolean status = false; + + mActiveDeviceManager = ActiveDeviceManagerService.get(); + if(mActiveDeviceManager != null) { + mActivedevice = mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.CALL_AUDIO); + } + Log.i(TAG, "connectAudio: device=" + mActivedevice + ", " + Utils.getUidPidString()); + + int profileID = mActiveDeviceManager.getActiveProfile(ApmConst.AudioFeatures.CALL_AUDIO); + checkA2dpState(); + + if(ApmConst.AudioProfiles.HFP == profileID) { + HeadsetService service = HeadsetService.getHeadsetService(); + if (service == null) { + status = false; + } + status = service.connectAudio(mActivedevice); + } else if(ApmConst.AudioProfiles.BAP_CALL == profileID) { + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if(service != null) { + status = service.startStream(mActivedevice); + } + } else { + Log.e(TAG, "Unhandled connect audio request for profile: " + profileID); + status = false; + } + + if(status == false) { + Log.e(TAG, "failed connect audio request for device: " + mActivedevice); + if(ApmConst.AudioProfiles.HFP != profileID) { + HeadsetService service = HeadsetService.getHeadsetService(); + if(service != null) { + service.getHfpA2DPSyncInterface().releaseA2DP(null); + } + } + } + + return status; + } + + public boolean disconnectAudio() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + BluetoothDevice mActivedevice = null; + boolean mStatus = false; + + if(mActiveDeviceManager != null) { + mActivedevice = mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.CALL_AUDIO); + } + Log.i(TAG, "disconnectAudio: device=" + mActivedevice + ", " + Utils.getUidPidString()); + + int profileID = mActiveDeviceManager.getActiveProfile(ApmConst.AudioFeatures.CALL_AUDIO); + + if(ApmConst.AudioProfiles.HFP == profileID) { + HeadsetService service = HeadsetService.getHeadsetService(); + if (service == null) { + mStatus = false; + } + mStatus = service.disconnectAudio(); + } else if(ApmConst.AudioProfiles.BAP_CALL == profileID) { + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if(service != null) { + mStatus = service.stopStream(mActivedevice); + } + } else { + Log.e(TAG, "Unhandled disconnectAudio request for profile: " + profileID); + mStatus = true; + } + + if(ApmConst.AudioProfiles.HFP != profileID) { + HeadsetService service = HeadsetService.getHeadsetService(); + if(service != null) { + service.getHfpA2DPSyncInterface().releaseA2DP(null); + } + } + return mStatus; + } + + public boolean setConnectionPolicy(BluetoothDevice device, Integer connectionPolicy) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, + "Need BLUETOOTH_PRIVILEGED permission"); + boolean mStatus; + + Log.d(TAG, "setConnectionPolicy: device=" + device + + ", connectionPolicy=" + connectionPolicy + ", " + Utils.getUidPidString()); + + mStatus = mAdapterService.getDatabase() + .setProfileConnectionPolicy(device, BluetoothProfile.HEADSET, connectionPolicy); + + if (mStatus && + connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + connect(device); + } else if (mStatus && + connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { + disconnect(device); + } + return mStatus; + } + + public int getConnectionPolicy(BluetoothDevice device) { + if(mAdapterService != null) { + int connPolicy; + connPolicy = mAdapterService.getDatabase() + .getProfileConnectionPolicy(device, BluetoothProfile.HEADSET); + Log.d(TAG, "getConnectionPolicy: device=" + device + + ", connectionPolicy=" + connPolicy); + return connPolicy; + } else { + return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; + + } + } + + public int getAudioState(BluetoothDevice device) { + if(device == null) + return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; + CallDevice mCallDevice; + mCallDevice = mCallDevicesMap.get(device.getAddress()); + if (mCallDevice == null) { + Log.w(TAG, "getAudioState: device " + device + " was never connected/connecting"); + return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; + } + return mCallDevice.scoStatus; + } + + private List getNonIdleAudioDevices() { + if(mCallDevicesMap.size() == 0) { + return new ArrayList<>(0); + } + + ArrayList devices = new ArrayList<>(); + for (CallDevice mCallDevice : mCallDevicesMap.values()) { + if (mCallDevice.scoStatus != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { + devices.add(mCallDevice.mDevice); + } + } + return devices; + } + + public boolean isAudioOn() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + int numConnectedAudioDevices = getNonIdleAudioDevices().size(); + Log.d(TAG," isAudioOn: The number of audio connected devices " + + numConnectedAudioDevices); + return numConnectedAudioDevices > 0; + } + + public List getConnectedDevices() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + Log.i(TAG, "getConnectedDevices: "); + if(mCallDevicesMap.size() == 0) { + Log.i(TAG, "no device is Connected:"); + return new ArrayList<>(0); + } + + List connectedDevices = new ArrayList<>(); + for(CallDevice mCallDevice : mCallDevicesMap.values()) { + if(mCallDevice.deviceConnStatus == BluetoothProfile.STATE_CONNECTED) { + connectedDevices.add(mCallDevice.mDevice); + } + } + Log.i(TAG, "ConnectedDevices: = " + connectedDevices.size()); + return connectedDevices; + } + + public int getConnectionState(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + + if(device == null) + return BluetoothProfile.STATE_DISCONNECTED; + CallDevice mCallDevice; + mCallDevice = mCallDevicesMap.get(device.getAddress()); + if(mCallDevice != null) + return mCallDevice.deviceConnStatus; + + return BluetoothProfile.STATE_DISCONNECTED; + } + + public boolean isVoiceOrCallActive() { + boolean isVoiceActive = isAudioOn() || mVirtualCallStarted; + HeadsetService mHeadsetService = HeadsetService.getHeadsetService(); + if(mHeadsetService != null) { + isVoiceActive = isVoiceActive || mHeadsetService.isScoOrCallActive(); + } + return isVoiceActive; + } + + private void broadcastConnStateChange(BluetoothDevice device, int fromState, int toState) { + Log.d(TAG,"broadcastConnectionState " + device + ": " + fromState + "->" + toState); + HeadsetService mHeadsetService = HeadsetService.getHeadsetService(); + if(mHeadsetService == null) { + Log.w(TAG,"broadcastConnectionState: HeadsetService not initialized. Return!"); + return; + } + + Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState); + intent.putExtra(BluetoothProfile.EXTRA_STATE, toState); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL, + BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); + } + + private void broadcastAudioState(BluetoothDevice device, int fromState, int toState) { + Log.d(TAG,"broadcastAudioState " + device + ": " + fromState + "->" + toState); + HeadsetService mHeadsetService = HeadsetService.getHeadsetService(); + if(mHeadsetService == null) { + Log.d(TAG,"broadcastAudioState: HeadsetService not initialized. Return!"); + return; + } + + Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState); + intent.putExtra(BluetoothProfile.EXTRA_STATE, toState); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL, + BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); + } + + public void onConnStateChange(BluetoothDevice device, Integer state, Integer profile) { + int prevState; + Log.w(TAG, "onConnStateChange: profile: " + profile + " state: " + + state + " for device " + device); + if(device == null) + return; + CallDevice mCallDevice = mCallDevicesMap.get(device.getAddress()); + + if(mCallDevice == null) { + if(state == BluetoothProfile.STATE_DISCONNECTED) + return; + if(mCallDevicesMap.size() >= MAX_DEVICES) { + return; + } + mCallDevice = new CallDevice(device, profile, state); + mCallDevicesMap.put(device.getAddress(), mCallDevice); + broadcastConnStateChange(device, BluetoothProfile.STATE_DISCONNECTED, state); + return; + } + + int profileIndex = mCallDevice.getProfileIndex(profile); + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + prevState = mCallDevice.deviceConnStatus; + mCallDevice.profileConnStatus[profileIndex] = state; + + if(state == BluetoothProfile.STATE_DISCONNECTED) { + dMap.profileConnectionUpdate(device, ApmConst.AudioFeatures.CALL_AUDIO, profile, false); + } + + int otherProfileConnectionState = mCallDevice.profileConnStatus[(profileIndex+1)%2]; + Log.w(TAG, " otherProfileConnectionState: " + otherProfileConnectionState); + + switch(otherProfileConnectionState) { + /*Send Broadcast based on state of other profile*/ + case BluetoothProfile.STATE_DISCONNECTED: + broadcastConnStateChange(device, prevState, state); + mCallDevice.deviceConnStatus = state; + if(state == BluetoothProfile.STATE_CONNECTED) { + int supportedProfiles = dMap.getSupportedProfile(device, ApmConst.AudioFeatures.CALL_AUDIO); + if(profile == ApmConst.AudioProfiles.HFP && + (supportedProfiles & ApmConst.AudioProfiles.BAP_CALL) == ApmConst.AudioProfiles.BAP_CALL) { + Log.w(TAG, "Connect LE Voice after HFP auto connect from remote"); + StreamAudioService mStreamService = StreamAudioService.getStreamAudioService(); + if(mStreamService != null) { + mStreamService.connectLeStream(device, ApmConst.AudioProfiles.BAP_CALL); + } + } else { + ActiveDeviceManagerService mActiveDeviceManager = ActiveDeviceManagerService.get(); + mActiveDeviceManager.setActiveDevice(device, ApmConst.AudioFeatures.CALL_AUDIO); + } + } + break; + case BluetoothProfile.STATE_CONNECTING: + int preferredProfile = dMap.getProfile(device, ApmConst.AudioFeatures.CALL_AUDIO); + boolean isPreferredProfile = (preferredProfile == profile); + if(state == BluetoothProfile.STATE_CONNECTED && isPreferredProfile) { + broadcastConnStateChange(device, prevState, state); + mCallDevice.deviceConnStatus = state; + } + break; + case BluetoothProfile.STATE_DISCONNECTING: + if(state == BluetoothProfile.STATE_CONNECTING || + state == BluetoothProfile.STATE_CONNECTED) { + broadcastConnStateChange(device, prevState, state); + mCallDevice.deviceConnStatus = state; + } + break; + case BluetoothProfile.STATE_CONNECTED: + if(state == BluetoothProfile.STATE_CONNECTED) { + if(prevState != state) { + broadcastConnStateChange(device, prevState, state); + mCallDevice.deviceConnStatus = state; + } + ActiveDeviceManagerService mActiveDeviceManager = + ActiveDeviceManagerService.get(); + mActiveDeviceManager.setActiveDevice(device, ApmConst.AudioFeatures.CALL_AUDIO); + } else if(state == BluetoothProfile.STATE_DISCONNECTED) { + if(prevState != BluetoothProfile.STATE_CONNECTED) { + broadcastConnStateChange(device, prevState, BluetoothProfile.STATE_CONNECTED); + mCallDevice.deviceConnStatus = BluetoothProfile.STATE_CONNECTED; + } else { + ActiveDeviceManagerService mActiveDeviceManager = + ActiveDeviceManagerService.get(); + if(device.equals(mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.CALL_AUDIO))) { + mActiveDeviceManager.setActiveDevice(device, ApmConst.AudioFeatures.CALL_AUDIO); + } + } + } + break; + } + + if(state == BluetoothProfile.STATE_CONNECTED) { + dMap.profileConnectionUpdate(device, ApmConst.AudioFeatures.CALL_AUDIO, profile, true); + } + } + + public void onAudioStateChange(BluetoothDevice device, Integer state) { + int prevStatus; + if(device == null) + return; + CallDevice mCallDevice = mCallDevicesMap.get(device.getAddress()); + if(mCallDevice == null) { + return; + } + + if(mCallDevice.scoStatus == state) + return; + + HeadsetService service = HeadsetService.getHeadsetService(); + int profileID = mActiveDeviceManager.getActiveProfile( + ApmConst.AudioFeatures.CALL_AUDIO); + BluetoothDevice mActivedevice = mActiveDeviceManager.getActiveDevice( + ApmConst.AudioFeatures.CALL_AUDIO); + if (service != null) { + if(!(service.shouldCallAudioBeActive() || mVirtualCallStarted)) { + if(ApmConst.AudioProfiles.BAP_CALL == profileID) { + StreamAudioService mStreamAudioService = + StreamAudioService.getStreamAudioService(); + if(mStreamAudioService != null) { + Log.w(TAG, "Call not active, disconnect stream"); + mStreamAudioService.stopStream(mActivedevice); + } + } + } + } + + prevStatus = mCallDevice.scoStatus; + mCallDevice.scoStatus = state; + VolumeManager mVolumeManager = VolumeManager.get(); + mVolumeManager.updateStreamState(device, state, ApmConst.AudioFeatures.CALL_AUDIO); + broadcastAudioState(device, prevStatus, state); + if(state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { + if(ApmConst.AudioProfiles.HFP != profileID) { + if(service != null) { + service.getHfpA2DPSyncInterface().releaseA2DP(null); + } + } + //mAudioManager.setBluetoothScoOn(false); + } /*else { + mAudioManager.setBluetoothScoOn(true); + }*/ + } + + public void setAudioParam(String param) { + mAudioManager.setParameters(param); + } + + public void setBluetoothScoOn(boolean on) { + mAudioManager.setBluetoothScoOn(on); + } + + public AudioManager getAudioManager() { + return mAudioManager; + } + + class CallDevice { + BluetoothDevice mDevice; + int[] profileConnStatus = new int[2]; + int deviceConnStatus; + int scoStatus; + + public static final int SCO_STREAM = 0; + public static final int LE_STREAM = 1; + + CallDevice(BluetoothDevice device, int profile, int state) { + mDevice = device; + if(profile == ApmConst.AudioProfiles.HFP) { + profileConnStatus[SCO_STREAM] = state; + profileConnStatus[LE_STREAM] = BluetoothProfile.STATE_DISCONNECTED; + } else { + profileConnStatus[LE_STREAM] = state; + profileConnStatus[SCO_STREAM] = BluetoothProfile.STATE_DISCONNECTED; + } + deviceConnStatus = state; + scoStatus = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;; + } + + CallDevice(BluetoothDevice device, int profile) { + this(device, profile, BluetoothProfile.STATE_DISCONNECTED); + } + + public int getProfileIndex(int profile) { + if(profile == ApmConst.AudioProfiles.HFP) + return SCO_STREAM; + else + return LE_STREAM; + } + } +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/CallControl.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/CallControl.java new file mode 100644 index 00000000000..2b0c194df33 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/CallControl.java @@ -0,0 +1,150 @@ +/****************************************************************************** + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * +*****************************************************************************/ + +package com.android.bluetooth.apm; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothAdapter; +import com.android.bluetooth.hfp.HeadsetService; +import com.android.bluetooth.hfp.HeadsetSystemInterface; +import com.android.bluetooth.apm.ApmConst; +import com.android.bluetooth.cc.CCService; +import android.media.AudioManager; +import com.android.bluetooth.apm.ActiveDeviceManagerService; +import android.content.Context; +import android.content.Intent; +import android.os.UserHandle; +import android.net.Uri; +import android.telephony.PhoneNumberUtils; +import android.telecom.PhoneAccount; +import android.os.SystemProperties; + +import android.util.Log; + + +public class CallControl { + + private static CallControl mCallControl; + private static final String TAG = "CallControl"; + private static Context mContext; + private static ActiveDeviceManagerService mActiveDeviceManager; + private static boolean isCCEnabled = true; + private CallControl(Context context) { + Log.d(TAG, "Initialization"); + mContext = context; + } + + public static void init(Context context) { + if(mCallControl == null) { + mCallControl = new CallControl(context); + CallControlIntf.init(mCallControl); + } + isCCEnabled = SystemProperties.getBoolean("persist.vendor.service.bt.cc", true); + Log.d(TAG, "isCCEnabled" + isCCEnabled); + } + + public static CallControl get() { + return mCallControl; + } + + public void phoneStateChanged(Integer numActive, Integer numHeld, Integer callState, String number, + Integer type, String name, Boolean isVirtualCall) { + Log.d(TAG, "phoneStateChanged"); + if(isCCEnabled == true) { + CCService.getCCService().phoneStateChanged(numActive, numHeld,callState,number, + type, name, isVirtualCall); + } + } + + public void setVirtualCallActive(boolean state) { + if(isCCEnabled == true) { + CCService.getCCService().setVirtualCallActive(state); + } + } + + public void clccResponse(Integer index, Integer direction, Integer status, Integer mode, Boolean mpty, + String number, Integer type) { + Log.d(TAG, "clccResponse"); + if (isCCEnabled == true) { + CCService.getCCService().clccResponse(index, direction, status, mode, mpty, number, type); + } + } + + public void updateBearerTechnology(Integer tech) { + Log.d(TAG, "updateBearerTechnology"); + if (isCCEnabled == true) { + CCService.getCCService().updateBearerProviderTechnology(tech); + } + } + + public void updateSignalStatus(Integer signal) { + Log.d(TAG, "updateSignalStatus"); + if (isCCEnabled == true) { + CCService.getCCService().updateSignalStrength(signal); + } + } + + public void updateBearerName(String name) { + Log.d(TAG, "updateBearerProviderName"); + if (isCCEnabled == true) { + CCService.getCCService().updateBearerProviderName(name); + } + } + + public void updateOriginateResult(BluetoothDevice device, Integer event, Integer res) { + Log.d(TAG, "updateOriginateResult"); + if (isCCEnabled == true) { + CCService.getCCService().updateOriginateResult(device, event, res); + } + } + public static void listenForPhoneState (int events) { + Log.d(TAG, "listenForPhoneState"); + BluetoothDevice dummyDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice("CC:CC:CC:CC:CC:CC"); + HeadsetService.getHeadsetService().getSystemInterfaceObj().getHeadsetPhoneState().listenForPhoneState(dummyDevice, events); + } + + public static void answerCall (BluetoothDevice device) { + Log.d(TAG, "answerCall"); + HeadsetService.getHeadsetService().getSystemInterfaceObj().answerCall(device); + } + + public static void hangupCall (BluetoothDevice device) { + Log.d(TAG, "hangupCall"); + HeadsetService.getHeadsetService().getSystemInterfaceObj().hangupCall(device); + } + + public static void terminateCall (BluetoothDevice device, int index) { + Log.d(TAG, "terminateCall"); + HeadsetService.getHeadsetService().getSystemInterfaceObj().terminateCall(device, index); + } + + public static boolean processChld (BluetoothDevice device, int chld) { + Log.d(TAG, "processChld"); + return HeadsetService.getHeadsetService().getSystemInterfaceObj().processChld(chld); + } + + public static boolean holdCall (BluetoothDevice device, int index) { + Log.d(TAG, "holdCall"); + return HeadsetService.getHeadsetService().getSystemInterfaceObj().holdCall(index); + } + + public static boolean listCurrentCalls () { + Log.d(TAG, "listCurrentCalls"); + return HeadsetService.getHeadsetService().getSystemInterfaceObj().listCurrentCalls(); + } + + public static boolean dialOutgoingCall(BluetoothDevice fromDevice, String dialNumber) { + Log.i(TAG, "dialOutgoingCall: from " + fromDevice); + HeadsetService service = HeadsetService.getHeadsetService(); + if (service != null) { + service.dialOutgoingCallInternal(fromDevice, dialNumber); + } + return true; + } + + public static void dial (BluetoothDevice device, String dialNumber) { + dialOutgoingCall(device, dialNumber); + } + +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/DeviceProfileMap.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/DeviceProfileMap.java new file mode 100644 index 00000000000..6b723fa88dd --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/DeviceProfileMap.java @@ -0,0 +1,800 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * +*****************************************************************************/ + +package com.android.bluetooth.apm; + +import android.content.SharedPreferences; +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.IntentFilter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothUuid; +import android.os.ParcelUuid; +import android.os.Parcelable; +import android.os.SystemProperties; +import android.util.Log; +import android.content.Context; +import com.android.internal.util.ArrayUtils; +import java.util.HashMap; +import java.util.Map; +import java.util.Arrays; +import java.util.ArrayList; +import java.lang.Integer; +import java.lang.Boolean; +import java.util.Objects; + +public class DeviceProfileMap { + + Map mSupportedProfileMap = new HashMap(); + Map mActiveProfileMap = new HashMap(); + Map mConnectedProfileMap = new HashMap(); + private Context mContext; + private static DeviceProfileMap DPMSingleInstance = null; + public static int [] mPreferredProfileList = new int[ApmConst.AudioFeatures.MAX_AUDIO_FEATURES]; + public static final String SUPPORTED_PROFILE_MAP = "bluetooth_supported_profile_map"; + public final String ACTION_SHUTDOWN = "android.intent.action.ACTION_SHUTDOWN"; + public final String ACTION_POWER_OFF = "android.intent.action.QUICKBOOT_POWEROFF"; + private final Object mLock = new Object(); + + private static final int LeMediaProfiles = ApmConst.AudioProfiles.BAP_MEDIA + | ApmConst.AudioProfiles.TMAP_MEDIA + | ApmConst.AudioProfiles.BAP_GCP + | ApmConst.AudioProfiles.BAP_RECORDING; + + private static final int LeCallProfiles = ApmConst.AudioProfiles.BAP_CALL + | ApmConst.AudioProfiles.TMAP_CALL; + + // private constructor restricted to this class itself + private DeviceProfileMap() { + Log.w(LOGTAG, "DeviceProfileMap object creation"); + } + + // static method to create instance of Singleton class + public static DeviceProfileMap getDeviceProfileMapInstance() { + if (DPMSingleInstance == null) { + DPMSingleInstance = new DeviceProfileMap(); + DeviceProfileMapIntf.init(DPMSingleInstance); + } + return DPMSingleInstance; + } + + private static final String LOGTAG = "DeviceProfileMap"; + private SharedPreferences getSupportedProfileMap() { + return mContext.getSharedPreferences(SUPPORTED_PROFILE_MAP, Context.MODE_PRIVATE); + } + + /** + * Initialize the device profile map + */ + public synchronized boolean init(Context context) { + Log.d(LOGTAG, "init: "); + // populate the supported profile list. + mContext = context; + Map allKeys = getSupportedProfileMap().getAll(); + SharedPreferences.Editor mSupportedProfileMapEditor = getSupportedProfileMap().edit(); + + for (Map.Entry entry : allKeys.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + BluetoothDevice mBluetoothDevice = BluetoothAdapter.getDefaultAdapter(). + getRemoteDevice(key); + if (value instanceof Integer && mBluetoothDevice.getBondState() + == BluetoothDevice.BOND_BONDED) { + mSupportedProfileMap.put(mBluetoothDevice, (Integer) value); + Log.d(LOGTAG, "address " + key + " from the Supported Profile Map: " + value); + } else { + Log.d(LOGTAG, "Removing " + key + " from the Supported Profile map"); + mSupportedProfileMapEditor.remove(key); + } + } + mSupportedProfileMapEditor.apply(); + //intialize the preferred profile list + int mPreferredProfileVal = + SystemProperties.getInt("persist.vendor.qcom.bluetooth.default_profiles", 0); + Log.d(LOGTAG, "init: Preferred Profile = " + mPreferredProfileVal); + int mfeature = 0; + while (mfeature < ApmConst.AudioFeatures.MAX_AUDIO_FEATURES) { + switch(mfeature) { + case ApmConst.AudioFeatures.CALL_AUDIO: + /* default preferred profile for call */ + mPreferredProfileList[mfeature] = ApmConst.AudioProfiles.HFP; + if((mPreferredProfileVal & + ApmConst.AudioProfiles.TMAP_CALL) != 0) { + mPreferredProfileList[mfeature] = ApmConst.AudioProfiles.TMAP_CALL; + } else if((mPreferredProfileVal & + ApmConst.AudioProfiles.BAP_CALL) != 0) { + mPreferredProfileList[mfeature] = ApmConst.AudioProfiles.BAP_CALL; + } + break; + case ApmConst.AudioFeatures.MEDIA_AUDIO: + mPreferredProfileList[mfeature] = ApmConst.AudioProfiles.A2DP; + if((mPreferredProfileVal & + ApmConst.AudioProfiles.TMAP_MEDIA) != 0) { + mPreferredProfileList[mfeature] = ApmConst.AudioProfiles.TMAP_MEDIA; + } else if((mPreferredProfileVal & + ApmConst.AudioProfiles.BAP_RECORDING) != 0) { + mPreferredProfileList[mfeature] = ApmConst.AudioProfiles.BAP_RECORDING; + } else if((mPreferredProfileVal & + ApmConst.AudioProfiles.BAP_MEDIA) != 0) { + mPreferredProfileList[mfeature] = ApmConst.AudioProfiles.BAP_MEDIA; + } + break; + case ApmConst.AudioFeatures.CALL_CONTROL: + mPreferredProfileList[mfeature] = ((mPreferredProfileVal & + ApmConst.AudioProfiles.CCP) != 0) ? + ApmConst.AudioProfiles.CCP : ApmConst.AudioProfiles.HFP; + break; + case ApmConst.AudioFeatures.MEDIA_CONTROL: + mPreferredProfileList[mfeature] = ((mPreferredProfileVal & + ApmConst.AudioProfiles.AVRCP) != 0) ? + ApmConst.AudioProfiles.AVRCP : ApmConst.AudioProfiles.MCP; + break; + case ApmConst.AudioFeatures.CALL_VOLUME_CONTROL: + mPreferredProfileList[mfeature] = ((mPreferredProfileVal & + (ApmConst.AudioProfiles.BAP_CALL | ApmConst.AudioProfiles.TMAP_CALL)) != 0) ? + ApmConst.AudioProfiles.VCP : ApmConst.AudioProfiles.HFP; + break; + case ApmConst.AudioFeatures.MEDIA_VOLUME_CONTROL: + mPreferredProfileList[mfeature] = ((mPreferredProfileVal & + ApmConst.AudioProfiles.AVRCP) != 0) ? + ApmConst.AudioProfiles.AVRCP : ApmConst.AudioProfiles.VCP; + break; + case ApmConst.AudioFeatures.HEARING_AID: + mPreferredProfileList[mfeature] = ((mPreferredProfileVal & + ApmConst.AudioProfiles.HAP_BREDR) != 0) ? + ApmConst.AudioProfiles.HAP_BREDR : ApmConst.AudioProfiles.HAP_LE; + break; + case ApmConst.AudioFeatures.BROADCAST_AUDIO: + mPreferredProfileList[mfeature] = ((mPreferredProfileVal & + ApmConst.AudioProfiles.BROADCAST_BREDR) != 0) ? + ApmConst.AudioProfiles.BROADCAST_BREDR : ApmConst.AudioProfiles.BROADCAST_LE; + break; + default : + break; + } + Log.w(LOGTAG, "init: Preferred Profile = " + mPreferredProfileList[mfeature] + + " for audio feature " + mfeature); + mfeature++; + } + + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothDevice.ACTION_UUID); + filter.addAction(ACTION_SHUTDOWN); + filter.addAction(ACTION_POWER_OFF); + mContext.registerReceiver(mDeviceProfileMapReceiver, filter); + + return true; + } + + private final BroadcastReceiver mDeviceProfileMapReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) { + Log.w(LOGTAG, "mDeviceProfileMapReceiver, action is null"); + return; + } + switch (action) { + case BluetoothDevice.ACTION_UUID: { + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + Parcelable[] uuids = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID); + handleDeviceUuidEvent(device, uuids); + break; + } + case ACTION_SHUTDOWN: + case ACTION_POWER_OFF: + handleDeviceShutdown(); + break; + default: + Log.w(LOGTAG, "Unknown action " + action); + } + } + }; + + private void handleDeviceUuidEvent(BluetoothDevice device, Parcelable[] uuids) { + Log.d(LOGTAG, "UUIDs found, device: " + device); + if (uuids != null) { + ParcelUuid[] uuidsToSend = new ParcelUuid[uuids.length]; + for (int i = 0; i < uuidsToSend.length; i++) { + uuidsToSend[i] = (ParcelUuid) uuids[i]; + Log.d(LOGTAG,"index=" + i + "uuid=" + uuidsToSend[i]); + } + checkIfProfileSupported(device, uuidsToSend); + } + } + + private void checkIfProfileSupported(BluetoothDevice device, ParcelUuid[] remoteDeviceUuids) { + ParcelUuid ADV_AUDIO_T_MEDIA = + ParcelUuid.fromString("00006AD0-0000-1000-8000-00805F9B34FB"); + + ParcelUuid HEARINGAID_ADV_AUDIO = + ParcelUuid.fromString("00006AD2-0000-1000-8000-00805F9B34FB"); + + ParcelUuid ADV_AUDIO_P_MEDIA = + ParcelUuid.fromString("00006AD1-0000-1000-8000-00805F9B34FB"); + + ParcelUuid ADV_AUDIO_P_VOICE = + ParcelUuid.fromString("00006AD4-0000-1000-8000-00805F9B34FB"); + + ParcelUuid ADV_AUDIO_T_VOICE = + ParcelUuid.fromString("00006AD5-0000-1000-8000-00805F9B34FB"); + + ParcelUuid ADV_AUDIO_G_MEDIA = + ParcelUuid.fromString("12994B7E-6d47-4215-8C9E-AAE9A1095BA3"); + + ParcelUuid ADV_AUDIO_W_RECORDING = + ParcelUuid.fromString("2587DB3C-CE70-4FC9-935F-777AB4188FD7"); + + if (ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.HFP)) { + updateSupportedProfileMap(device, ApmConst.AudioProfiles.HFP); + } + if (ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.A2DP_SINK)) { + updateSupportedProfileMap(device, ApmConst.AudioProfiles.A2DP); + } + if (ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.HEARING_AID)) { + updateSupportedProfileMap(device, ApmConst.AudioProfiles.HAP_BREDR); + } + if (ArrayUtils.contains(remoteDeviceUuids, BluetoothUuid.AVRCP_CONTROLLER)) { + updateSupportedProfileMap(device, ApmConst.AudioProfiles.AVRCP); + } + if (ArrayUtils.contains(remoteDeviceUuids, ADV_AUDIO_T_MEDIA)) { + updateSupportedProfileMap(device, ApmConst.AudioProfiles.TMAP_MEDIA); + } + if (ArrayUtils.contains(remoteDeviceUuids, ADV_AUDIO_T_VOICE)) { + updateSupportedProfileMap(device, ApmConst.AudioProfiles.TMAP_CALL); + } + if (ArrayUtils.contains(remoteDeviceUuids, ADV_AUDIO_P_MEDIA)) { + updateSupportedProfileMap(device, ApmConst.AudioProfiles.BAP_MEDIA); + } + if (ArrayUtils.contains(remoteDeviceUuids, ADV_AUDIO_P_VOICE)) { + updateSupportedProfileMap(device, ApmConst.AudioProfiles.BAP_CALL); + } + if (ArrayUtils.contains(remoteDeviceUuids, ADV_AUDIO_W_RECORDING)) { + updateSupportedProfileMap(device, ApmConst.AudioProfiles.BAP_RECORDING); + } + } + + private void updateSupportedProfileMap(BluetoothDevice device, int mProfile) { + synchronized (mLock) { + int mSupportedProfileBitMap = 0; + if(!mSupportedProfileMap.containsKey(device)) { + //device is not added in supported map, add to it + Log.d(LOGTAG, "updateSupportedProfileMap: device " + device + + "is not added in supported map, add it"); + mSupportedProfileMap.put(device, mSupportedProfileBitMap); + } else { + mSupportedProfileBitMap = mSupportedProfileMap.get(device); + } + Log.d(LOGTAG, "updateSupportedProfileMap: device " + device + " for profile " + + mProfile + " mSupportedProfileBitMap " + mSupportedProfileBitMap); + mSupportedProfileBitMap = mSupportedProfileBitMap | mProfile; + mSupportedProfileMap.put(device, mSupportedProfileBitMap); + Log.d(LOGTAG, "updateSupportedProfileMap: device " + device + " for profile " + + mProfile + " mSupportedProfileBitMap " + mSupportedProfileBitMap); + } + } + + public int getAllSupportedProfile(BluetoothDevice device) { + int mSupportedProfileBitMap = 0; + synchronized (mLock) { + if(!mSupportedProfileMap.containsKey(device)) { + Log.d(LOGTAG, "No Supported Profile found for the device " + device); + } else { + mSupportedProfileBitMap = mSupportedProfileMap.get(device); + } + Log.d(LOGTAG, "getAllSupportedProfile: Supported Profile for the device " + + device + " mSupportedProfileBitMap " + Integer.toHexString(mSupportedProfileBitMap)); + } + return mSupportedProfileBitMap; + } + + public int getProfile(BluetoothDevice device, Integer mAudioFeature) { + int profileMap = getSupportedProfile(device, mAudioFeature); + int preferredProfile = profileMap; + int [] mAciveProfileArray = mActiveProfileMap.get(device); + + if(profileMap == ApmConst.AudioProfiles.NONE) + return ApmConst.AudioProfiles.NONE; + + switch(mAudioFeature) { + case ApmConst.AudioFeatures.CALL_AUDIO: + int mActiveProfileForCallAudio = mAciveProfileArray[mAudioFeature]; + if(mActiveProfileForCallAudio != ApmConst.AudioProfiles.NONE) { + //active profile is set + preferredProfile = mActiveProfileForCallAudio; + return preferredProfile; + } + + if(profileMap == ApmConst.AudioProfiles.HAP_BREDR || + profileMap == ApmConst.AudioProfiles.HAP_LE) { + preferredProfile = profileMap; + return preferredProfile; + } + + int mHFP = profileMap & ApmConst.AudioProfiles.HFP; + int mLeCall = profileMap & ApmConst.AudioProfiles.TMAP_CALL; + if(mLeCall == ApmConst.AudioProfiles.NONE) + mLeCall = profileMap & ApmConst.AudioProfiles.BAP_CALL; + + if(mHFP != ApmConst.AudioProfiles.NONE && + mLeCall != ApmConst.AudioProfiles.NONE) { + preferredProfile = mPreferredProfileList[mAudioFeature]; + } else if(mHFP != ApmConst.AudioProfiles.NONE) { + preferredProfile = mHFP; + } else { + preferredProfile = mLeCall; + } + + Log.d(LOGTAG, "getProfile: device " + device + " preferredProfile: " + + preferredProfile + " for CALL_AUDIO"); + break; + + case ApmConst.AudioFeatures.MEDIA_AUDIO: + int mActiveProfileForMediaAudio = mAciveProfileArray[mAudioFeature]; + if(mActiveProfileForMediaAudio != ApmConst.AudioProfiles.NONE) { + //active profile is set + preferredProfile = mActiveProfileForMediaAudio; + Log.d(LOGTAG, "getProfile: device " + device + " Active Profile: " + + preferredProfile + " for MEDIA_AUDIO"); + return preferredProfile; + } + + if(profileMap == ApmConst.AudioProfiles.HAP_BREDR || + profileMap == ApmConst.AudioProfiles.HAP_LE) { + preferredProfile = profileMap; + return preferredProfile; + } + + int mA2dp = profileMap & ApmConst.AudioProfiles.A2DP; + int mLeMedia = profileMap & ApmConst.AudioProfiles.TMAP_MEDIA; + if(mLeMedia == ApmConst.AudioProfiles.NONE) + mLeMedia = profileMap & ApmConst.AudioProfiles.BAP_MEDIA; + + if(mA2dp != ApmConst.AudioProfiles.NONE && + mLeMedia != ApmConst.AudioProfiles.NONE) { + preferredProfile = mPreferredProfileList[mAudioFeature]; + } else if(mA2dp != ApmConst.AudioProfiles.NONE) { + preferredProfile = mA2dp; + } else { + if((preferredProfile & ApmConst.AudioProfiles.BAP_RECORDING) + != ApmConst.AudioProfiles.NONE) + preferredProfile = mPreferredProfileList[mAudioFeature]; + else + preferredProfile = mLeMedia; + } + + Log.d(LOGTAG, "getProfile: device " + device + " preferredProfile: " + + preferredProfile + " for MEDIA_AUDIO"); + break; + } + return preferredProfile; + } + + public int getSupportedProfile(BluetoothDevice device, Integer mAudioFeature) { + int [] mAciveProfileArray; + + Log.d(LOGTAG, "getSupportedProfile: for the device " + device + " AudioFeature " + mAudioFeature); + if(!mActiveProfileMap.containsKey(device)) { + // intialize the active profile but map for the device + Log.d(LOGTAG, "getSupportedProfile: intialize the active profile map for the device " + device); + mAciveProfileArray = new int[ApmConst.AudioFeatures.MAX_AUDIO_FEATURES]; + Arrays.fill(mAciveProfileArray, ApmConst.AudioProfiles.NONE); + mActiveProfileMap.put(device, mAciveProfileArray); + } else { + mAciveProfileArray = mActiveProfileMap.get(device); + } + //get the supprted profile list + int mSupportedProfileBitMap = 0; + if(!mSupportedProfileMap.containsKey(device)) { + //device is not added in supported map, add to it + Log.d(LOGTAG, "getSupportedProfile: device " + device + " is not added in supported map, add it"); + mSupportedProfileMap.put(device, mSupportedProfileBitMap); + } else { + mSupportedProfileBitMap = mSupportedProfileMap.get(device); + } + Log.d(LOGTAG, "getSupportedProfile: supported Profiles for the device " + device + + " val = " + Integer.toHexString(mSupportedProfileBitMap)); + + switch(mAudioFeature) { + case ApmConst.AudioFeatures.CALL_AUDIO: + { + int mIsHapBREDRSupported = mSupportedProfileBitMap & ApmConst.AudioProfiles.HAP_BREDR; + int mIsHapLESupported = mSupportedProfileBitMap & ApmConst.AudioProfiles.HAP_LE; + + if(mIsHapBREDRSupported != ApmConst.AudioProfiles.NONE) { + return ApmConst.AudioProfiles.HAP_BREDR; + } else if (mIsHapLESupported != ApmConst.AudioProfiles.NONE) { + return ApmConst.AudioProfiles.HAP_LE; + } + + int mCallAudioProfile = mSupportedProfileBitMap & ( ApmConst.AudioProfiles.HFP + | ApmConst.AudioProfiles.BAP_CALL + | ApmConst.AudioProfiles.TMAP_CALL); + + Log.d(LOGTAG, "getSupportedProfile: device " + device + " supports: " + + mCallAudioProfile + " for CALL_AUDIO"); + + return mCallAudioProfile; + } + + case ApmConst.AudioFeatures.MEDIA_AUDIO: + { + int mIsHapBREDRSupported = mSupportedProfileBitMap & ApmConst.AudioProfiles.HAP_BREDR; + int mIsHapLESupported = mSupportedProfileBitMap & ApmConst.AudioProfiles.HAP_LE; + + if(mIsHapBREDRSupported != ApmConst.AudioProfiles.NONE) { + return ApmConst.AudioProfiles.HAP_BREDR; + } else if (mIsHapLESupported != ApmConst.AudioProfiles.NONE) { + return ApmConst.AudioProfiles.HAP_LE; + } + + int mMediaAudioProfile = mSupportedProfileBitMap & ( ApmConst.AudioProfiles.A2DP + | ApmConst.AudioProfiles.BAP_MEDIA + | ApmConst.AudioProfiles.TMAP_MEDIA + | ApmConst.AudioProfiles.BAP_RECORDING); + + Log.d(LOGTAG, "getSupportedProfile: device " + device + " supports: " + + mMediaAudioProfile + " for MEDIA_AUDIO"); + + return mMediaAudioProfile; + } + case ApmConst.AudioFeatures.MEDIA_CONTROL: + { + int mActiveProfileForMediaControl = mAciveProfileArray + [ApmConst.AudioFeatures.MEDIA_CONTROL]; + Log.d(LOGTAG, "getSupportedProfile: device " + device + " ActiveProfile For MediaControl " + + mActiveProfileForMediaControl); + return mActiveProfileForMediaControl; + } + case ApmConst.AudioFeatures.CALL_CONTROL: + { + int mActiveProfileForCallControl = mAciveProfileArray + [ApmConst.AudioFeatures.CALL_CONTROL]; + Log.d(LOGTAG, "getSupportedProfile: device " + device + " ActiveProfile For Call Control " + + mActiveProfileForCallControl); + return mActiveProfileForCallControl; + } + case ApmConst.AudioFeatures.MEDIA_VOLUME_CONTROL: + { + int mActiveProfileForMediaVolControl = mAciveProfileArray + [ApmConst.AudioFeatures.MEDIA_VOLUME_CONTROL]; + Log.d(LOGTAG, "getSupportedProfile: device " + device + + " ActiveProfile For Media Volume Control " + mActiveProfileForMediaVolControl); + + return mActiveProfileForMediaVolControl; + } + case ApmConst.AudioFeatures.CALL_VOLUME_CONTROL: + { + int mActiveProfileForCallVolControl = mAciveProfileArray + [ApmConst.AudioFeatures.CALL_VOLUME_CONTROL]; + Log.d(LOGTAG, "getSupportedProfile: device " + device + + " ActiveProfile For call Volume Control " + mActiveProfileForCallVolControl); + + return mActiveProfileForCallVolControl; + + } + case ApmConst.AudioFeatures.BROADCAST_AUDIO: + { + int mActiveProfileForBroadCastAudio = mAciveProfileArray + [ApmConst.AudioFeatures.BROADCAST_AUDIO]; + if(mActiveProfileForBroadCastAudio != ApmConst.AudioProfiles.NONE) { + //active profile is set + return mActiveProfileForBroadCastAudio; + } + + int mIsBroadCastBREDRSupported = mSupportedProfileBitMap & + ApmConst.AudioProfiles.BROADCAST_BREDR; + int mIsBroadCastLESupported = + mSupportedProfileBitMap & ApmConst.AudioProfiles.BROADCAST_LE; + Log.d(LOGTAG, "getSupportedProfile: device " + device + " mIsBroadCastBREDRSupported " + + mIsBroadCastBREDRSupported + " mIsBroadCastLESupported " + + mIsBroadCastLESupported); + + if((mIsBroadCastBREDRSupported != 0) && (mIsBroadCastLESupported != 0)) { + return mPreferredProfileList[ApmConst.AudioFeatures.BROADCAST_AUDIO]; + } else if (mIsBroadCastBREDRSupported != 0) { + return ApmConst.AudioProfiles.BROADCAST_BREDR; + } else if(mIsBroadCastLESupported != 0) { + return ApmConst.AudioProfiles.BROADCAST_LE; + } + break; + } + case ApmConst.AudioFeatures.HEARING_AID: + { + int mActiveProfileForHearingAid = mAciveProfileArray + [ApmConst.AudioFeatures.HEARING_AID]; + if(mActiveProfileForHearingAid != ApmConst.AudioProfiles.NONE) { + //active profile is set + return mActiveProfileForHearingAid; + } + + int mIsHAPBREDRSupported = mSupportedProfileBitMap & ApmConst.AudioProfiles.HAP_BREDR; + int mIsHAPLESupported = mSupportedProfileBitMap & ApmConst.AudioProfiles.HAP_LE; + Log.d(LOGTAG, "getSupportedProfile: device " + device + " mIsHAPBREDRSupported " + + mIsHAPBREDRSupported + " mIsHAPLESupported " + mIsHAPLESupported); + + if((mIsHAPBREDRSupported != 0) && (mIsHAPLESupported !=0)) { + return mPreferredProfileList[ApmConst.AudioFeatures.HEARING_AID]; + } else if (mIsHAPBREDRSupported != 0) { + return ApmConst.AudioProfiles.HAP_BREDR; + } else if(mIsHAPLESupported != 0) { + return ApmConst.AudioProfiles.HAP_LE; + } + break; + } + default : + { + Log.w(LOGTAG, "getSupportedProfile: no profile supported for" + + mAudioFeature + " device " + device); + return ApmConst.AudioProfiles.NONE; + } + } + return ApmConst.AudioProfiles.NONE; + } + + public void profileDescoveryUpdate (BluetoothDevice device, Integer mAudioProfile) { + int mSupportedProfileBitMap = 0; + if(mSupportedProfileMap.containsKey(device)) { + mSupportedProfileBitMap = mSupportedProfileMap.get(device); + } + + mSupportedProfileBitMap = mSupportedProfileBitMap | mAudioProfile; + mSupportedProfileMap.put(device, mSupportedProfileBitMap); + } + + public void profileConnectionUpdate(BluetoothDevice device, Integer mAudioFeature, + Integer mAudioProfile, Boolean mProfileStatus) { + int mSupportedProfileBitMap = 0; + int mConnectedProfileBitMap = 0; + int [] mAciveProfileArray; + + Log.d(LOGTAG, "profileConnectionUpdate: device : " + device + " AudioProfile " + + mAudioProfile + " ProfileStatus " + mProfileStatus); + + synchronized (mLock) { + // get the Connected profile list + if(mConnectedProfileMap.containsKey(device)) { + mConnectedProfileBitMap = mConnectedProfileMap.get(device); + } + + // get the Supported profile list + if(mSupportedProfileMap.containsKey(device)) { + mSupportedProfileBitMap = mSupportedProfileMap.get(device); + } + + if(!mActiveProfileMap.containsKey(device)) { + // intialize the active profile but map for the device + Log.d(LOGTAG, "profileConnectionUpdate: intialize the active " + + " profile map for the device " + device); + mAciveProfileArray = new int[ApmConst.AudioFeatures.MAX_AUDIO_FEATURES]; + Arrays.fill(mAciveProfileArray, ApmConst.AudioProfiles.NONE); + mActiveProfileMap.put(device, mAciveProfileArray); + } else { + mAciveProfileArray = mActiveProfileMap.get(device); + } + // update the Audio profile connection in list + if (mProfileStatus) { + mSupportedProfileBitMap = mSupportedProfileBitMap | mAudioProfile; + mConnectedProfileBitMap = mConnectedProfileBitMap | mAudioProfile; + + //update the active profile list + if(mAciveProfileArray[mAudioFeature] == ApmConst.AudioProfiles.NONE) { + mAciveProfileArray[mAudioFeature] = mAudioProfile; + } else if(mAciveProfileArray[mAudioFeature] != mAudioProfile) { + // diffrent profile connected for the same mAudioFeature need to update the active list. + int mPreferredProfile = mPreferredProfileList[mAudioFeature]; + Log.d(LOGTAG, "profileConnectionUpdate: PreferredProfile for audio feature " + + mAudioFeature + " is " + mPreferredProfile + " device " + device); + if((mPreferredProfile != ApmConst.AudioProfiles.NONE) + && (mConnectedProfileBitMap & mPreferredProfile) != 0) { + mAciveProfileArray[mAudioFeature] = mPreferredProfile; + } + } + } else { + mConnectedProfileBitMap = mConnectedProfileBitMap & ~mAudioProfile; + // profile disconnect for active profile + if(mAciveProfileArray[mAudioFeature] == mAudioProfile) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + // do we need to update the active profile with other connected profile for that audio feature. + switch(mAudioFeature) { + case ApmConst.AudioFeatures.CALL_AUDIO: + if(mAudioProfile == ApmConst.AudioProfiles.HFP) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.TMAP_CALL) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.TMAP_CALL; + } else if ((mConnectedProfileBitMap & ApmConst.AudioProfiles.BAP_CALL) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.BAP_CALL; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } else if (mAudioProfile == ApmConst.AudioProfiles.TMAP_CALL + || mAudioProfile == ApmConst.AudioProfiles.BAP_CALL) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.HFP) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.HFP; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } + break; + case ApmConst.AudioFeatures.MEDIA_AUDIO: + if(mAudioProfile == ApmConst.AudioProfiles.A2DP) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.TMAP_MEDIA) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.TMAP_MEDIA; + } else if ((mConnectedProfileBitMap & ApmConst.AudioProfiles.BAP_MEDIA) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.BAP_MEDIA; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } else if (mAudioProfile == ApmConst.AudioProfiles.TMAP_MEDIA + || mAudioProfile == ApmConst.AudioProfiles.BAP_MEDIA) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.A2DP) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.A2DP; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } + break; + case ApmConst.AudioFeatures.CALL_CONTROL: + if(mAudioProfile == ApmConst.AudioProfiles.HFP) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.CCP) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.CCP; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } else if (mAudioProfile == ApmConst.AudioProfiles.CCP) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.HFP) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.HFP; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } + break; + case ApmConst.AudioFeatures.MEDIA_CONTROL: + if(mAudioProfile == ApmConst.AudioProfiles.AVRCP) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.MCP) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.MCP; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } else if (mAudioProfile == ApmConst.AudioProfiles.MCP) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.AVRCP) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.AVRCP; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } + break; + case ApmConst.AudioFeatures.MEDIA_VOLUME_CONTROL: + if(mAudioProfile == ApmConst.AudioProfiles.AVRCP) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.VCP) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.VCP; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } else if (mAudioProfile == ApmConst.AudioProfiles.VCP) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.AVRCP) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.AVRCP; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } + break; + case ApmConst.AudioFeatures.CALL_VOLUME_CONTROL: + if(mAudioProfile == ApmConst.AudioProfiles.HFP) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.VCP) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.VCP; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } else if (mAudioProfile == ApmConst.AudioProfiles.VCP) { + if((mConnectedProfileBitMap & ApmConst.AudioProfiles.HFP) > 0) { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.HFP; + } else { + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } + break; + default: + mAciveProfileArray[mAudioFeature] = ApmConst.AudioProfiles.NONE; + } + } + } + + mActiveProfileMap.put(device, mAciveProfileArray); + Log.d(LOGTAG, "profileConnectionUpdate: supported Profiles for the device " + device + + " val " + Integer.toHexString(mSupportedProfileBitMap)); + Log.d(LOGTAG, "profileConnectionUpdate: connected Profiles for the device " + device + + " val " + Integer.toHexString(mConnectedProfileBitMap)); + + mConnectedProfileMap.put(device, mConnectedProfileBitMap); + mSupportedProfileMap.put(device, mSupportedProfileBitMap); + } + } + + public boolean isProfileConnected(BluetoothDevice device, Integer mAudioProfile) { + if (device == null) return false; + int mConnectedProfileBitMap = mConnectedProfileMap.get(device); + Log.d(LOGTAG, "isProfileConnected: device: " + device + + " mAudioProfile: " + mAudioProfile + " mConnectedProfileMap: " + mConnectedProfileBitMap); + + return ((mConnectedProfileBitMap & mAudioProfile) == mAudioProfile); + } + + public void setActiveProfile(BluetoothDevice device, Integer mAudioFeature, Integer mAudioProfile) { + int [] mAciveProfileArray; + Log.d(LOGTAG, "setActiveProfile: device : " + device + " AudioProfile " + + mAudioProfile + " AudioFeature " + mAudioFeature); + synchronized (mLock) { + if(!mActiveProfileMap.containsKey(device)) { + Log.d(LOGTAG, "setActiveProfile: intialize the active profile map for the device " + + device); + mAciveProfileArray = new int[ApmConst.AudioFeatures.MAX_AUDIO_FEATURES]; + Arrays.fill(mAciveProfileArray, ApmConst.AudioProfiles.NONE); + mActiveProfileMap.put(device, mAciveProfileArray); + } else { + mAciveProfileArray = mActiveProfileMap.get(device); + } + mAciveProfileArray[mAudioFeature] = mAudioProfile; + mActiveProfileMap.put(device, mAciveProfileArray); + } + } + + static int getLeMediaProfiles() { + return LeMediaProfiles; + } + + static int getLeCallProfiles() { + return LeCallProfiles; + } + + public synchronized void handleDeviceShutdown() { + Log.d(LOGTAG, "handleDeviceShutdown: started"); + + //store the supported profiles for the bonded devices + SharedPreferences.Editor pref = getSupportedProfileMap().edit(); + for (BluetoothDevice mBluetoothDevice : mSupportedProfileMap.keySet()) { + int mSupportedProfilesVal = mSupportedProfileMap.get(mBluetoothDevice); + Log.d(LOGTAG, "cleanup: supported Profiles for the device " + mBluetoothDevice + + " val = " + Integer.toHexString(mSupportedProfilesVal)); + if(mBluetoothDevice.getBondState() == BluetoothDevice.BOND_BONDED) { + pref.putInt(mBluetoothDevice.getAddress(), mSupportedProfilesVal); + } + } + pref.apply(); + mSupportedProfileMap.clear(); + mActiveProfileMap.clear(); + mConnectedProfileMap.clear(); + + Log.d(LOGTAG, "handleDeviceShutdown: Done"); + } + + public synchronized void cleanup () { + if(DPMSingleInstance == null) { + Log.w(LOGTAG, "cleanup called without initialization, Returning"); + return; + } + + Log.d(LOGTAG, "cleanup: started"); + + //store the supported profiles for the bonded devices + SharedPreferences.Editor pref = getSupportedProfileMap().edit(); + for (BluetoothDevice mBluetoothDevice : mSupportedProfileMap.keySet()) { + int mSupportedProfilesVal = mSupportedProfileMap.get(mBluetoothDevice); + Log.d(LOGTAG, "cleanup: supported Profiles for the device " + mBluetoothDevice + + " val = " + Integer.toHexString(mSupportedProfilesVal)); + if(mBluetoothDevice.getBondState() == BluetoothDevice.BOND_BONDED) { + pref.putInt(mBluetoothDevice.getAddress(), mSupportedProfilesVal); + } + } + pref.apply(); + mSupportedProfileMap.clear(); + mActiveProfileMap.clear(); + mConnectedProfileMap.clear(); + DPMSingleInstance = null; + mContext.unregisterReceiver(mDeviceProfileMapReceiver); + + Log.d(LOGTAG, "cleanup: Done"); + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/MediaAudio.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/MediaAudio.java new file mode 100644 index 00000000000..4730ebfd681 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/MediaAudio.java @@ -0,0 +1,1190 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + **************************************************************************/ + +package com.android.bluetooth.apm; + +import static android.Manifest.permission.BLUETOOTH_CONNECT; + +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothCodecStatus; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import com.android.bluetooth.Utils; +import android.bluetooth.BluetoothUuid; +import android.bluetooth.IBluetoothA2dp; + +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.ActiveDeviceManager; +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.a2dp.A2dpService; +import com.android.bluetooth.apm.ActiveDeviceManagerService; +import com.android.bluetooth.apm.ApmConst; +import com.android.bluetooth.broadcast.BroadcastService; +import com.android.bluetooth.acm.AcmService; +import android.content.Context; +import android.content.Intent; +import android.content.BroadcastReceiver; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.util.Log; +import android.util.StatsLog; +import com.android.bluetooth.hfp.HeadsetService; + +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.os.SystemProperties; + +public class MediaAudio { + private static MediaAudio sMediaAudio; + private AdapterService mAdapterService; +// private BapBroadcastService mBapBroadcastService; + private ActiveDeviceManagerService mActiveDeviceManager; + private Context mContext; + BapBroadcastManager mBapBroadcastManager; + Map mMediaDevices; + + final ArrayList supported_codec = new ArrayList( List.of( + "LC3" + )); + + private BroadcastReceiver mCodecConfigReceiver; + private BroadcastReceiver mQosConfigReceiver; + public static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; + public static final String BLUETOOTH_PRIVILEGED = + android.Manifest.permission.BLUETOOTH_PRIVILEGED; + public static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; + public static final String ACTION_UPDATE_CODEC_CONFIG = + "qti.intent.bluetooth.action.UPDATE_CODEC_CONFIG"; + public static final String CODEC_ID = + "qti.bluetooth.extra.CODEC_ID"; + public static final String CODEC_CONFIG = + "qti.bluetooth.extra.CODEC_CONFIG"; + public static final String CHANNEL_MODE = + "qti.bluetooth.extra.CHANNEL_MODE"; + public static final String ACTION_UPDATE_QOS_CONFIG = + "qti.intent.bluetooth.action.UPDATE_QOS_CONFIG"; + public static final String QOS_CONFIG = + "qti.bluetooth.extra.QOS_CONFIG"; + private static final int MAX_DEVICES = 200; + public static final String TAG = "APM: MediaAudio"; + public static final boolean DBG = true; + + private static final long AUDIO_RECORDING_MASK = 0x00030000; + private static final long AUDIO_RECORDING_OFF = 0x00010000; + private static final long AUDIO_RECORDING_ON = 0x00020000; + + private static final long GAMING_OFF = 0x00001000; + private static final long GAMING_ON = 0x00002000; + private static final long GAMING_MODE_MASK = 0x00007000; + + private static boolean mIsRecordingEnabled; + + private AudioManager mAudioManager; + + private MediaAudio(Context context) { + Log.i(TAG, "initialization"); + + mContext = context; + mMediaDevices = new ConcurrentHashMap(); + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + Objects.requireNonNull(mAudioManager, + "AudioManager cannot be null when A2dpService starts"); + + mAdapterService = AdapterService.getAdapterService(); + mActiveDeviceManager = ActiveDeviceManagerService.get(); + + mBapBroadcastManager = new BapBroadcastManager(); + + IntentFilter codecFilter = new IntentFilter(); + codecFilter.addAction(ACTION_UPDATE_CODEC_CONFIG); + mCodecConfigReceiver = new LeCodecConfig(); + context.registerReceiver(mCodecConfigReceiver, codecFilter); + + IntentFilter qosFilter = new IntentFilter(); + qosFilter.addAction(ACTION_UPDATE_QOS_CONFIG); + mQosConfigReceiver = new QosConfigReceiver(); + context.registerReceiver(mQosConfigReceiver, qosFilter); + + mIsRecordingEnabled = + SystemProperties.getBoolean("persist.vendor.service.bt.recording_supported", false); + + //2 Setup Codec Config here + } + + public static MediaAudio init(Context context) { + if(sMediaAudio == null) { + sMediaAudio = new MediaAudio(context); + MediaAudioIntf.init(sMediaAudio); + } + return sMediaAudio; + } + + public static MediaAudio get() { + return sMediaAudio; + } + + public boolean connect(BluetoothDevice device) { + return connect (device, false, false); + } + + public boolean connect(BluetoothDevice device, Boolean allProfile) { + return connect (device, allProfile, false); + } + + public boolean autoConnect(BluetoothDevice device) { + Log.e(TAG, "autoConnect: " + device); + return connect(device, false, true); + } + + private boolean connect(BluetoothDevice device, boolean allProfile, boolean autoConnect) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + + Log.e(TAG, "connect: " + device + " allProfile: " + allProfile + + " autoConnect: " + autoConnect); + if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { + Log.e(TAG, "Cannot connect to " + device + " : CONNECTION_POLICY_FORBIDDEN"); + return false; + } + + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + if (dMap == null) + return false; + + int peer_supported_profiles = dMap.getAllSupportedProfile(device); + boolean is_peer_support_recording = + ((peer_supported_profiles & ApmConst.AudioProfiles.BAP_RECORDING) != 0); + int profileID = dMap.getSupportedProfile(device, ApmConst.AudioFeatures.MEDIA_AUDIO); + if (profileID == ApmConst.AudioProfiles.NONE) { + Log.e(TAG, "Can Not connect to " + device + ". Device does not support media service."); + return false; + } + + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + if(mMediaDevice == null) { + if(mMediaDevices.size() >= MAX_DEVICES) + return false; + mMediaDevice = new MediaDevice(device, profileID); + mMediaDevices.put(device.getAddress(), mMediaDevice); + } else if(mMediaDevice.deviceConnStatus != BluetoothProfile.STATE_DISCONNECTED) { + Log.i(TAG, "Device already connected"); + return false; + } + + if((ApmConst.AudioProfiles.A2DP & profileID) == ApmConst.AudioProfiles.A2DP) { + A2dpService service = A2dpService.getA2dpService(); + if(service != null) { + service.connectA2dp(device); + } + } + + BluetoothDevice groupDevice = device; + StreamAudioService mStreamService = StreamAudioService.getStreamAudioService(); + + if(mStreamService != null && + (ApmConst.AudioProfiles.BAP_MEDIA & profileID) == ApmConst.AudioProfiles.BAP_MEDIA) { + int defaultMediaProfile = ApmConst.AudioProfiles.BAP_MEDIA; + + /* handle common conect of call and media audio */ + if(autoConnect) { + groupDevice = mStreamService.getDeviceGroup(device); + Log.i(TAG, "Auto Connect Request. Connecting group: " + groupDevice); + } + + /*int defaultMusicProfile = dMap.getProfile(device, ApmConst.AudioFeatures.MEDIA_AUDIO); + if((ApmConst.AudioProfiles.A2DP & defaultMusicProfile) == ApmConst.AudioProfiles.A2DP) { + Log.i(TAG, "A2DP is default profile for Music, configure BAP for Gaming"); + defaultMediaProfile = ApmConst.AudioProfiles.BAP_GCP; + }*/ + + if(mIsRecordingEnabled) { + Log.i(TAG, "Add Recording profile to LE connect request"); + defaultMediaProfile = defaultMediaProfile | ApmConst.AudioProfiles.BAP_RECORDING; + } + + if(allProfile) { + int callProfileID = dMap.getSupportedProfile(device, ApmConst.AudioFeatures.CALL_AUDIO); + if((callProfileID & ApmConst.AudioProfiles.BAP_CALL) == ApmConst.AudioProfiles.BAP_CALL) { + Log.i(TAG, "Add BAP_CALL to LE connect request"); + mStreamService.connectLeStream(groupDevice, + defaultMediaProfile | ApmConst.AudioProfiles.BAP_CALL); + } else { + mStreamService.connectLeStream(groupDevice, defaultMediaProfile); + } + } else { + mStreamService.connectLeStream(groupDevice, defaultMediaProfile); + } + } + return true; + } + + public boolean disconnect(BluetoothDevice device) { + return disconnect(device, false); + } + + public boolean disconnect(BluetoothDevice device, Boolean allProfile) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); + Log.i(TAG, "Disconnect: " + device); + MediaDevice mMediaDevice = null; + + if(device == null) + return false; + + mMediaDevice = mMediaDevices.get(device.getAddress()); + if(mMediaDevice == null) { + Log.e(TAG, "Ignore: Device " + device + " not present in list"); + return false; + } + + if (mMediaDevice.profileConnStatus[MediaDevice.A2DP_STREAM] != BluetoothProfile.STATE_DISCONNECTED) { + A2dpService service = A2dpService.getA2dpService(); + if(service != null) { + service.disconnectA2dp(device); + } + } + if (mMediaDevice.profileConnStatus[MediaDevice.LE_STREAM] != BluetoothProfile.STATE_DISCONNECTED) { + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if(service != null) { + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + if (!dMap.isProfileConnected(device, ApmConst.AudioProfiles.BAP_CALL)) { + Log.d(TAG,"BAP_CALL not connected"); + allProfile = false; + } + service.disconnectLeStream(device, allProfile, true); + } + } + + return true; + } + + public List getConnectedDevices() { + Log.i(TAG, "getConnectedDevices: "); + if(mMediaDevices.size() == 0) { + return new ArrayList<>(0); + } + + List connectedDevices = new ArrayList<>(); + for(MediaDevice mMediaDevice : mMediaDevices.values()) { + if(mMediaDevice.deviceConnStatus == BluetoothProfile.STATE_CONNECTED) { + connectedDevices.add(mMediaDevice.mDevice); + } + } + return connectedDevices; + } + + public List getDevicesMatchingConnectionStates(Integer[] states) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + + Log.i(TAG, "getDevicesMatchingConnectionStates: "); + List devices = new ArrayList<>(); + if (states == null) { + return devices; + } + + BluetoothDevice [] bondedDevices = null; + bondedDevices = mAdapterService.getBondedDevices(); + if(bondedDevices == null) { + return devices; + } + + for (BluetoothDevice device : bondedDevices) { + MediaDevice mMediaDevice; + int state = BluetoothProfile.STATE_DISCONNECTED; + + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + if(dMap == null) { + return new ArrayList<>(0); + } + if(dMap.getProfile(device, ApmConst.AudioFeatures.MEDIA_AUDIO) == ApmConst.AudioProfiles.NONE) { + continue; + } + + mMediaDevice = mMediaDevices.get(device.getAddress()); + if(mMediaDevice != null) + state = mMediaDevice.deviceConnStatus; + + for(int s: states) { + if(s == state) { + devices.add(device); + break; + } + } + } + return devices; + } + + public int getConnectionState(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + + if(device == null) + return BluetoothProfile.STATE_DISCONNECTED; + + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + if(mMediaDevice != null) + return mMediaDevice.deviceConnStatus; + + return BluetoothProfile.STATE_DISCONNECTED; + } + + public int getPriority(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + if(mAdapterService != null) { + return mAdapterService.getDatabase() + .getProfileConnectionPolicy(device, BluetoothProfile.A2DP); + } + return BluetoothProfile.PRIORITY_UNDEFINED; + } + + public boolean isA2dpPlaying(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + + if(device == null) + return false; + + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + if(mMediaDevice != null) { + Log.i(TAG, "isA2dpPlaying: " + mMediaDevice.streamStatus); + return (mMediaDevice.streamStatus == BluetoothA2dp.STATE_PLAYING); + } + + return false; + } + + public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + + Log.i(TAG, "getCodecStatus: for device: " + device); + if(device == null) + return null; + + if (mBapBroadcastManager.isBapBroadcastActive()) { + return mBapBroadcastManager.getCodecStatus(); + } + + ActiveDeviceManagerService mActiveDeviceManager = ActiveDeviceManagerService.get(); + int profile = mActiveDeviceManager.getActiveProfile(ApmConst.AudioFeatures.MEDIA_AUDIO); + if(profile != ApmConst.AudioProfiles.NONE && profile != ApmConst.AudioProfiles.A2DP) { + StreamAudioService service = StreamAudioService.getStreamAudioService(); + if(service != null) { + device = service.getDeviceGroup(device); + } + } + + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + Log.i(TAG, "getCodecStatus: for mMediaDevice: " + mMediaDevice); + if(mMediaDevice == null) + return null; + + Log.i(TAG, "getCodecStatus: " + mMediaDevice.mCodecStatus); + return mMediaDevice.mCodecStatus; + } + + public void setCodecConfigPreference(BluetoothDevice mDevice, + BluetoothCodecConfig codecConfig) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + BluetoothDevice device = mDevice; + + Log.i(TAG, "setCodecConfigPreference: " + codecConfig); + if(device == null) { + if(mActiveDeviceManager != null) { + device = mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO); + } + } + if(device == null) + return; + + if (codecConfig == null) { + Log.e(TAG, "setCodecConfigPreference: Codec config can't be null"); + return; + } + long cs4 = codecConfig.getCodecSpecific4(); + + if (mActiveDeviceManager.getActiveProfile(ApmConst.AudioFeatures.MEDIA_AUDIO) == + ApmConst.AudioProfiles.BROADCAST_LE) { + AcmService mAcmService = AcmService.getAcmService(); + BluetoothDevice mPrevDevice = mActiveDeviceManager.getQueuedDevice(ApmConst.AudioFeatures.MEDIA_AUDIO); + if (mPrevDevice != null && mAcmService != null) { + if ((cs4 & AUDIO_RECORDING_MASK) == AUDIO_RECORDING_ON) { + if (mAcmService.getConnectionState(mPrevDevice) == BluetoothProfile.STATE_CONNECTED) { + device = mPrevDevice; + Log.d(TAG,"Recording request, switch device to " + device); + } else { + Log.d(TAG,"Not DUMO device, ignore recording request"); + return; + } + } + } else if ((cs4 & GAMING_MODE_MASK) == GAMING_ON) { + Log.d(TAG, "Ignore gaming mode request when broadcast is active"); + return; + } + } + + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + int supported_prfiles = dMap.getAllSupportedProfile(device); + + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + boolean peer_supports_recording = + ((supported_prfiles & ApmConst.AudioProfiles.BAP_RECORDING) != 0); + + int profileIndex = mMediaDevice.getProfileIndex(ApmConst.AudioProfiles.BAP_MEDIA); + int profileIndexA2dp = mMediaDevice.getProfileIndex(ApmConst.AudioProfiles.A2DP); + boolean is_peer_connected_for_recording = + (mMediaDevice.profileConnStatus[profileIndex] == + BluetoothProfile.STATE_CONNECTED); + boolean is_peer_connected_for_a2dp = (mMediaDevice.profileConnStatus[profileIndexA2dp] == + BluetoothProfile.STATE_CONNECTED); + Log.i(TAG, "is_peer_connected_for_recording: " + is_peer_connected_for_recording + + ", is_peer_connected_for_a2dp: " + is_peer_connected_for_a2dp); + CallAudio mCallAudio = CallAudio.get(); + boolean isInCall = mCallAudio != null && mCallAudio.isVoiceOrCallActive(); + // TODO : check the FM related rx activity + if (mActiveDeviceManager != null && + peer_supports_recording && mIsRecordingEnabled && + is_peer_connected_for_recording && is_peer_connected_for_a2dp) { + if ((cs4 & AUDIO_RECORDING_MASK) == AUDIO_RECORDING_ON) { + if(!isInCall && + !mActiveDeviceManager.isRecordingActive(device)) { + mActiveDeviceManager.enableRecording(device); + } + + } else if ((cs4 & AUDIO_RECORDING_MASK) == AUDIO_RECORDING_OFF) { + if(mActiveDeviceManager.isRecordingActive(device)) { + mActiveDeviceManager.disableRecording(device); + } + } + } + + boolean isBapConnected = (mMediaDevice.profileConnStatus[mMediaDevice.LE_STREAM] + == BluetoothProfile.STATE_CONNECTED); + + if(isBapConnected) { + long mGamingStatus = (cs4 & GAMING_MODE_MASK); + if((mGamingStatus & GAMING_ON) > 0) { + Log.w(TAG, "Turning On Gaming Mode"); + mActiveDeviceManager.enableGaming(device); + return; + } else if((mGamingStatus & GAMING_OFF) > 0) { + Log.w(TAG, "Turning Off Gaming Mode"); + mActiveDeviceManager.disableGaming(device); + return; + } + } + + int profileID = dMap.getProfile(device, ApmConst.AudioFeatures.MEDIA_AUDIO); + + if(ApmConst.AudioProfiles.A2DP == profileID) { + if(codecConfig.getCodecType() == + BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3) { + return; + } + A2dpService service = A2dpService.getA2dpService(); + if(service != null) { + service.setCodecConfigPreferenceA2dp(device, codecConfig); + return; + } + }/* else if(ApmConst.AudioProfiles.BAP == profileID) { // once implemented + LeStreamService service = LeStreamService.getLeStreamService(); + if(service != null) { + service.setCodecConfigPreferenceLeStream(device, codecConfig); + return; + } + }*/ + + } + + public void enableOptionalCodecs(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + Log.i(TAG, "enableOptionalCodecs: "); + + BluetoothCodecStatus mCodecStatus = null; + + if (device == null) { + if(mActiveDeviceManager != null) { + device = mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO); + } + } + if (device == null) { + Log.e(TAG, "enableOptionalCodecs: Invalid device"); + return; + } + + if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) { + Log.e(TAG, "enableOptionalCodecs: No optional codecs"); + return; + } + + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + + A2dpService service = A2dpService.getA2dpService(); + if(service != null) { + int profileIndex = mMediaDevice.getProfileIndex(ApmConst.AudioProfiles.A2DP); + mCodecStatus = mMediaDevice.mProfileCodecStatus[profileIndex]; + if(mCodecStatus != null) { + service.enableOptionalCodecsA2dp(device, mCodecStatus.getCodecConfig()); + } + } + // 2 Should implement common codec handling when + //vendor codecs is introduced in LE Audio + } + + public void disableOptionalCodecs(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + Log.i(TAG, "disableOptionalCodecs: "); + BluetoothCodecStatus mCodecStatus = null; + if (device == null) { + if(mActiveDeviceManager != null) { + device = mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO); + } + } + if (device == null) { + Log.e(TAG, "disableOptionalCodecs: Invalid device"); + return; + } + + if (getSupportsOptionalCodecs(device) != BluetoothA2dp.OPTIONAL_CODECS_SUPPORTED) { + Log.e(TAG, "disableOptionalCodecs: No optional codecs"); + return; + } + + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + + A2dpService service = A2dpService.getA2dpService(); + if(service != null) { + int profileIndex = mMediaDevice.getProfileIndex(ApmConst.AudioProfiles.A2DP); + mCodecStatus = mMediaDevice.mProfileCodecStatus[profileIndex]; + if(mCodecStatus != null) { + service.disableOptionalCodecsA2dp(device, mCodecStatus.getCodecConfig()); + } + } + // 2 Should implement common codec handling when + //vendor codecs is introduced in LE Audio + } + + public int getSupportsOptionalCodecs(BluetoothDevice device) { + if(mAdapterService != null) + return mAdapterService.getDatabase().getA2dpSupportsOptionalCodecs(device); + return BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED; + } + + public int supportsOptionalCodecs(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + if(mAdapterService.isTwsPlusDevice(device)) { + Log.w(TAG, "Disable optional codec support for TWS+ device"); + return BluetoothA2dp.OPTIONAL_CODECS_NOT_SUPPORTED; + } + return getSupportsOptionalCodecs(device); + } + + public int getOptionalCodecsEnabled(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + if(mAdapterService != null) + return mAdapterService.getDatabase().getA2dpOptionalCodecsEnabled(device); + return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN; + } + + public void setOptionalCodecsEnabled(BluetoothDevice device, Integer value) { + Log.i(TAG, "setOptionalCodecsEnabled: " + value); + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN + && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED + && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) { + Log.w(TAG, "Unexpected value passed to setOptionalCodecsEnabled:" + value); + return; + } + + if(mAdapterService != null) + mAdapterService.getDatabase().setA2dpOptionalCodecsEnabled(device, value); + } + + public int getConnectionPolicy(BluetoothDevice device) { + if(mAdapterService != null) + return mAdapterService.getDatabase() + .getProfileConnectionPolicy(device, BluetoothProfile.A2DP); + return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; + } + + public boolean setConnectionPolicy(BluetoothDevice device, Integer connectionPolicy) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, + "Need BLUETOOTH_PRIVILEGED permission"); + if (DBG) { + Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy); + } + boolean setSuccessfully; + setSuccessfully = mAdapterService.getDatabase() + .setProfileConnectionPolicy(device, BluetoothProfile.A2DP, connectionPolicy); + + if (setSuccessfully && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + connect(device); + } else if (setSuccessfully + && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { + disconnect(device); + } + return setSuccessfully; + } + + public boolean setSilenceMode(BluetoothDevice device, Boolean silence) { + if (DBG) { + Log.d(TAG, "setSilenceMode(" + device + "): " + silence); + } + BluetoothDevice mActiveDevice = mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO); + if (silence && Objects.equals(mActiveDevice, device)) { + mActiveDeviceManager.removeActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO, true); + } else if (!silence && null == + mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO)) { + // Set the device as the active device if currently no active device. + mActiveDeviceManager.setActiveDevice(device, ApmConst.AudioFeatures.MEDIA_AUDIO, false); + } + return true; + } + + public void onConnStateChange(BluetoothDevice device, Integer state, Integer profile) { + Log.d(TAG, "onConnStateChange: profile: " + profile + " state: " + state + " for device " + device); + if(device == null) + return; + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + + if(mMediaDevice == null) { + if(state == BluetoothProfile.STATE_DISCONNECTED) + return; + if(mMediaDevices.size() >= MAX_DEVICES) { + return; + } + mMediaDevice = new MediaDevice(device, profile, state); + mMediaDevices.put(device.getAddress(), mMediaDevice); + broadcastConnStateChange(device, BluetoothProfile.STATE_DISCONNECTED, state); + return; + } + + int profileIndex = mMediaDevice.getProfileIndex(profile); + int prevState = mMediaDevice.deviceConnStatus; + if(mMediaDevice.profileConnStatus[profileIndex] == state) { + Log.w(TAG, "Profile already in state: " + state + ". Return"); + return; + } + mMediaDevice.profileConnStatus[profileIndex] = state; + + if(state == BluetoothProfile.STATE_CONNECTED) { + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + dMap.profileConnectionUpdate(device, ApmConst.AudioFeatures.MEDIA_AUDIO, profile, true); + refreshCurrentCodec(device); + } else if(state == BluetoothProfile.STATE_DISCONNECTED) { + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + dMap.profileConnectionUpdate(device, ApmConst.AudioFeatures.MEDIA_AUDIO, profile, false); + } + + int otherProfileConnectionState = mMediaDevice.profileConnStatus[(profileIndex+1)%2]; + Log.w(TAG, " otherProfileConnectionState: " + otherProfileConnectionState); + + switch(otherProfileConnectionState) { + /*Send Broadcast based on state of other profile*/ + case BluetoothProfile.STATE_DISCONNECTED: + broadcastConnStateChange(device, prevState, state); + mMediaDevice.deviceConnStatus = state; + break; + case BluetoothProfile.STATE_CONNECTING: + if(state == BluetoothProfile.STATE_CONNECTED) { + broadcastConnStateChange(device, prevState, state); + mMediaDevice.deviceConnStatus = state; + } + break; + case BluetoothProfile.STATE_DISCONNECTING: + if(state == BluetoothProfile.STATE_CONNECTING || + state == BluetoothProfile.STATE_CONNECTED) { + broadcastConnStateChange(device, prevState, state); + mMediaDevice.deviceConnStatus = state; + } + break; + case BluetoothProfile.STATE_CONNECTED: + ActiveDeviceManagerService mActiveDeviceManager = + ActiveDeviceManagerService.get(); + if(mActiveDeviceManager == null) { + break; + } + + BluetoothDevice mActiveDevice = mActiveDeviceManager + .getActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO); + + if((state == BluetoothProfile.STATE_CONNECTED) || + (state == BluetoothProfile.STATE_DISCONNECTED && + device.equals(mActiveDevice))) { + Log.w(TAG, "onConnStateChange: Trigger Media handoff for Device: " + device); + mActiveDeviceManager.setActiveDevice(device, + ApmConst.AudioFeatures.MEDIA_AUDIO); + } + break; + } + + if (profileIndex == mMediaDevice.LE_STREAM && + state == BluetoothProfile.STATE_DISCONNECTED) { + mMediaDevice.mProfileCodecStatus[profileIndex] = null; + } + } + + public void onConnStateChange(BluetoothDevice device, int state, int profile, boolean isFirstMember) { + Log.w(TAG, "onConnStateChange: state:" + state + " for device " + device + " new group: " + isFirstMember); + if((state == BluetoothProfile.STATE_CONNECTED || state == BluetoothProfile.STATE_CONNECTING) + && isFirstMember) { + StreamAudioService mStreamAudioService = StreamAudioService.getStreamAudioService(); + BluetoothDevice groupDevice = mStreamAudioService.getDeviceGroup(device); + if(groupDevice != null) { + MediaDevice mMediaDevice = mMediaDevices.get(groupDevice.getAddress()); + if(mMediaDevice == null) { + mMediaDevice = new MediaDevice(groupDevice, profile, BluetoothProfile.STATE_CONNECTED); + mMediaDevices.put(groupDevice.getAddress(), mMediaDevice); + } else { + int profileIndex = mMediaDevice.getProfileIndex(profile); + mMediaDevice.profileConnStatus[profileIndex] = BluetoothProfile.STATE_CONNECTED; + mMediaDevice.deviceConnStatus = state; + } + } + } else if(isFirstMember && (state == BluetoothProfile.STATE_DISCONNECTING || + state == BluetoothProfile.STATE_DISCONNECTED)) { + StreamAudioService mStreamAudioService = StreamAudioService.getStreamAudioService(); + BluetoothDevice groupDevice = mStreamAudioService.getDeviceGroup(device); + MediaDevice mMediaDevice = mMediaDevices.get(groupDevice.getAddress()); + int prevState = BluetoothProfile.STATE_CONNECTED; + Log.w(TAG, "onConnStateChange: mMediaDevice: " + mMediaDevice); + if(mMediaDevice != null) { + prevState = mMediaDevice.deviceConnStatus; + int profileIndex = mMediaDevice.getProfileIndex(profile); + mMediaDevice.profileConnStatus[profileIndex] = state; + mMediaDevice.deviceConnStatus = state; + Log.w(TAG, "onConnStateChange: device: " + groupDevice + " state = " + mMediaDevice.deviceConnStatus); + } + ActiveDeviceManager mDeviceManager = AdapterService.getAdapterService().getActiveDeviceManager(); + mDeviceManager.onDeviceConnStateChange(groupDevice, state, prevState, + ApmConst.AudioFeatures.MEDIA_AUDIO); + } + onConnStateChange(device, state, profile); + } + + public void onStreamStateChange(BluetoothDevice device, Integer streamStatus) { + int prevStatus; + if(device == null) + return; + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + if(mMediaDevice == null) { + return; + } + + if(mMediaDevice.streamStatus == streamStatus) + return; + + prevStatus = mMediaDevice.streamStatus; + mMediaDevice.streamStatus = streamStatus; + Log.d(TAG, "onStreamStateChange update to volume manager"); + VolumeManager mVolumeManager = VolumeManager.get(); + mVolumeManager.updateStreamState(device, streamStatus, ApmConst.AudioFeatures.MEDIA_AUDIO); + broadcastStreamState(device, prevStatus, streamStatus); + } + + protected BluetoothCodecStatus convergeCodecConfig(MediaDevice mMediaDevice) { + BluetoothCodecStatus A2dpCodecStatus = mMediaDevice.mProfileCodecStatus[MediaDevice.A2DP_STREAM]; + BluetoothCodecStatus BapCodecStatus = mMediaDevice.mProfileCodecStatus[MediaDevice.LE_STREAM]; + BluetoothCodecStatus mCodecStatus = null; + + if(A2dpCodecStatus == null || + mMediaDevice.profileConnStatus[MediaDevice.A2DP_STREAM] != + BluetoothProfile.STATE_CONNECTED) { + return BapCodecStatus; + } + + if(BapCodecStatus == null || + mMediaDevice.profileConnStatus[MediaDevice.LE_STREAM] != + BluetoothProfile.STATE_CONNECTED) { + return A2dpCodecStatus; + } + + ActiveDeviceManagerService mActiveDeviceManager = ActiveDeviceManagerService.get(); + int mActiveProfile = mActiveDeviceManager.getActiveProfile(ApmConst.AudioFeatures.MEDIA_AUDIO); + int mActiveProfileIndex = mMediaDevice.getProfileIndex(mActiveProfile); + BluetoothCodecConfig mCodecConfig = mMediaDevice.mProfileCodecStatus[mActiveProfileIndex].getCodecConfig(); + + Log.d(TAG, "convergeCodecConfig: mActiveProfile: " + + mActiveProfile + ", mActiveProfileIndex: " + mActiveProfileIndex); + + BluetoothCodecConfig[] mCodecsLocalCapabilities = new BluetoothCodecConfig[ + A2dpCodecStatus.getCodecsLocalCapabilities().length + + BapCodecStatus.getCodecsLocalCapabilities().length]; + System.arraycopy(A2dpCodecStatus.getCodecsLocalCapabilities(), 0, mCodecsLocalCapabilities, 0, + A2dpCodecStatus.getCodecsLocalCapabilities().length); + System.arraycopy(BapCodecStatus.getCodecsLocalCapabilities(), 0, mCodecsLocalCapabilities, + A2dpCodecStatus.getCodecsLocalCapabilities().length, + BapCodecStatus.getCodecsLocalCapabilities().length); + + BluetoothCodecConfig[] mCodecsSelectableCapabilities = new BluetoothCodecConfig[ + A2dpCodecStatus.getCodecsSelectableCapabilities().length + + BapCodecStatus.getCodecsSelectableCapabilities().length]; + System.arraycopy(A2dpCodecStatus.getCodecsSelectableCapabilities(), 0, mCodecsSelectableCapabilities, 0, + A2dpCodecStatus.getCodecsSelectableCapabilities().length); + System.arraycopy(BapCodecStatus.getCodecsSelectableCapabilities(), 0, mCodecsSelectableCapabilities, + A2dpCodecStatus.getCodecsSelectableCapabilities().length, + BapCodecStatus.getCodecsSelectableCapabilities().length); + + mCodecStatus = new BluetoothCodecStatus(mCodecConfig, + mCodecsLocalCapabilities, mCodecsSelectableCapabilities); + return mCodecStatus; + } + + public void onCodecConfigChange(BluetoothDevice device, BluetoothCodecStatus mCodecStatus, Integer profile) { + onCodecConfigChange(device, mCodecStatus, profile, true); + } + + protected void refreshCurrentCodec(BluetoothDevice device) { + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + if(mMediaDevice == null) { + return; + } + + mMediaDevice.mCodecStatus = convergeCodecConfig(mMediaDevice); + + Log.d(TAG, "refreshCurrentCodec: " + device + ", " + mMediaDevice.mCodecStatus); + + broadcastCodecStatus(device, mMediaDevice.mCodecStatus); + } + + public void onCodecConfigChange(BluetoothDevice device, + BluetoothCodecStatus codecStatus, Integer profile, Boolean updateAudio) { + Log.w(TAG, "onCodecConfigChange: for profile:" + profile + " for device " + + device + " update audio: " + updateAudio + " with status " + codecStatus); + if(device == null || codecStatus == null) + return; + + MediaDevice mMediaDevice = mMediaDevices.get(device.getAddress()); + BluetoothCodecStatus prevCodecStatus = null; + //BapBroadcastService mBapBroadcastService = BapBroadcastService.getBapBroadcastService(); + if (mMediaDevice == null && profile == ApmConst.AudioProfiles.BROADCAST_LE) { + Log.d(TAG,"LE Broadcast codec change"); + } else if(mMediaDevice == null) { + Log.e(TAG, "No entry in Device Profile map for device: " + device); + return; + } + if (mMediaDevice != null) { + int profileIndex = mMediaDevice.getProfileIndex(profile); + Log.d(TAG, "profileIndex: " + profileIndex); + + if(codecStatus.equals(mMediaDevice.mProfileCodecStatus[profileIndex])) { + Log.w(TAG, "onCodecConfigChange: Codec already updated for the device and profile"); + return; + } + + mMediaDevice.mProfileCodecStatus[profileIndex] = codecStatus; + prevCodecStatus = mMediaDevice.mCodecStatus; + + /* Check the codec status for alternate Media profile for this device */ + if(mMediaDevice.mProfileCodecStatus[(profileIndex+1)%2] != null) { + mMediaDevice.mCodecStatus = convergeCodecConfig(mMediaDevice); + } else { + mMediaDevice.mCodecStatus = codecStatus; + } + + Log.w(TAG, "BroadCasting codecstatus " + mMediaDevice.mCodecStatus + + " for device: " + device); + broadcastCodecStatus(device, mMediaDevice.mCodecStatus); + } + + if(prevCodecStatus != null && mMediaDevice != null) { + if (prevCodecStatus.getCodecConfig().equals(mMediaDevice.mCodecStatus.getCodecConfig())) { + Log.d(TAG, "Previous and current codec config are same. Return"); + return; + } + } + + ActiveDeviceManagerService mActiveDeviceManager = ActiveDeviceManagerService.get(); + if(mActiveDeviceManager != null && (!mActiveDeviceManager.isStableState(ApmConst.AudioFeatures.MEDIA_AUDIO))) { + Log.d(TAG, "SHO under progress. MM Audio will be updated after SHO completes"); + return; + } + + if(device.equals(mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO)) && updateAudio) { + VolumeManager mVolumeManager = VolumeManager.get(); + int currentVolume = mVolumeManager.getActiveVolume(ApmConst.AudioFeatures.MEDIA_AUDIO); + if (profile == ApmConst.AudioProfiles.BROADCAST_LE) + currentVolume = 15; + if (mAudioManager != null) { + BluetoothDevice groupDevice = device; + if(profile == ApmConst.AudioProfiles.BAP_MEDIA) { + StreamAudioService mStreamAudioService = StreamAudioService.getStreamAudioService(); + groupDevice = mStreamAudioService.getDeviceGroup(device); + } + Log.d(TAG, "onCodecConfigChange Calling handleBluetoothA2dpActiveDeviceChange"); + mAudioManager.handleBluetoothA2dpActiveDeviceChange(groupDevice, + BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, + true, currentVolume); + } + } + } + + private void broadcastConnStateChange(BluetoothDevice device, int prevState, int newState) { + A2dpService mA2dpService = A2dpService.getA2dpService(); + if (mA2dpService != null) { + Log.d(TAG, "Broadcast Conn State Change: " + prevState + "->" + newState + " for device " + device); + Intent intent = new Intent(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); + intent.putExtra(BluetoothProfile.EXTRA_STATE, newState); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mA2dpService.sendBroadcast(intent, BLUETOOTH_CONNECT, + Utils.getTempAllowlistBroadcastOptions()); + } + } + + private void broadcastStreamState(BluetoothDevice device, int prevStatus, int streamStatus) { + A2dpService mA2dpService = A2dpService.getA2dpService(); + if (mA2dpService != null) { + Intent intent = new Intent(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevStatus); + intent.putExtra(BluetoothProfile.EXTRA_STATE, streamStatus); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mA2dpService.sendBroadcast(intent, BLUETOOTH_CONNECT, + Utils.getTempAllowlistBroadcastOptions()); + } + } + + private void broadcastCodecStatus (BluetoothDevice device, BluetoothCodecStatus mCodecStatus) { + A2dpService mA2dpService = A2dpService.getA2dpService(); + if (mA2dpService != null) { + Intent intent = new Intent(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED); + intent.putExtra(BluetoothCodecStatus.EXTRA_CODEC_STATUS, mCodecStatus); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mA2dpService.sendBroadcast(intent, BLUETOOTH_CONNECT, + Utils.getTempAllowlistBroadcastOptions()); + } + } + + public boolean isValidCodec (String mCodec) { + return supported_codec.contains(mCodec); + } + + public AudioManager getAudioManager() { + return mAudioManager; + } + + class MediaDevice { + BluetoothDevice mDevice; + int[] profileConnStatus = new int[2]; + int deviceConnStatus; + int streamStatus; + private BluetoothCodecStatus mCodecStatus; + private BluetoothCodecStatus[] mProfileCodecStatus = new BluetoothCodecStatus[2]; + + public static final int A2DP_STREAM = 0; + public static final int LE_STREAM = 1; + + MediaDevice(BluetoothDevice device, int profile, int state) { + profileConnStatus[A2DP_STREAM] = BluetoothProfile.STATE_DISCONNECTED; + profileConnStatus[LE_STREAM] = BluetoothProfile.STATE_DISCONNECTED; + mDevice = device; + if((profile & ApmConst.AudioProfiles.A2DP) != ApmConst.AudioProfiles.NONE) { + profileConnStatus[A2DP_STREAM] = state; + } + if((profile & (ApmConst.AudioProfiles.TMAP_MEDIA | ApmConst.AudioProfiles.BAP_MEDIA)) != + ApmConst.AudioProfiles.NONE) { + profileConnStatus[LE_STREAM] = state; + } + deviceConnStatus = state; + streamStatus = BluetoothA2dp.STATE_NOT_PLAYING; + } + + MediaDevice(BluetoothDevice device, int profile) { + this(device, profile, BluetoothProfile.STATE_DISCONNECTED); + } + + public int getProfileIndex(int profile) { + if(profile == ApmConst.AudioProfiles.A2DP) + return A2DP_STREAM; + else + return LE_STREAM; + } + } + + private class LeCodecConfig extends BroadcastReceiver { + /*am broadcast -a qti.intent.bluetooth.action.UPDATE_CODEC_CONFIG --es + qti.bluetooth.extra.CODEC_ID "LC3" --es qti.bluetooth.extra.CODEC_CONFIG ""*/ + + ArrayList supported_codec_config = new ArrayList( List.of( + /* config ID Sampling Freq Octets/Frame */ + "8_1", /* 8 26 */ + "8_2", /* 8 30 */ + "16_1", /* 16 30 */ + "16_2", /* 16 40 */ + "24_1", /* 24 45 */ + "24_2", /* 24 60 */ + "32_1", /* 32 60 */ + "32_2", /* 32 80 */ + "441_1",/* 44.1 98 */ + "441_2",/* 44.1 130 */ + "48_1", /* 48 75 */ + "48_2", /* 48 100 */ + "48_3", /* 48 90 */ + "48_4", /* 48 120 */ + "48_5", /* 48 117 */ + "48_6", /* 48 155 */ + "GCP_TX", + "GCP_TX_RX" + )); + + Map channel_mode = Map.of( + "NONE", 0, + "MONO", 1, + "STEREO", 2 + ); + + @Override + public void onReceive(Context context, Intent intent) { + if (!ACTION_UPDATE_CODEC_CONFIG.equals(intent.getAction())) { + return; + } + String mCodecId = intent.getStringExtra(CODEC_ID); + if(mCodecId == null || !isValidCodec(mCodecId)) { + Log.w(TAG, "Invalid Codec " + mCodecId); + return; + } + String mCodecConfig = intent.getStringExtra(CODEC_CONFIG); + if(mCodecConfig == null || !isValidCodecConfig(mCodecConfig)) { + Log.w(TAG, "Invalid Codec Config " + mCodecConfig); + return; + } + + int mChannelMode = BluetoothCodecConfig.CHANNEL_MODE_NONE; + String chMode = intent.getStringExtra(CHANNEL_MODE); + if(chMode != null && channel_mode.containsKey(chMode)) { + mChannelMode = channel_mode.get(chMode); + } + + ActiveDeviceManagerService mActiveDeviceManager + = ActiveDeviceManagerService.get(); + int profile = mActiveDeviceManager.getActiveProfile(ApmConst.AudioFeatures.MEDIA_AUDIO); + if(profile == ApmConst.AudioProfiles.BROADCAST_LE) { + /*Update Broadcast module here*/ + mBapBroadcastManager.setCodecPreference(mCodecConfig, mChannelMode); + } else if (profile == ApmConst.AudioProfiles.BAP_MEDIA) { + BluetoothDevice device = mActiveDeviceManager.getActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO); + StreamAudioService service = StreamAudioService.getStreamAudioService(); + service.setCodecConfig(device, mCodecConfig, mChannelMode); + } + Log.i(TAG, "Codec Config Request: Codec Name: " + mCodecId + " Config ID: " + + mCodecConfig + " mChannelMode: " + mChannelMode + " for profile: " + profile); + } + + boolean isValidCodecConfig (String mCodecConfig) { + return supported_codec_config.contains(mCodecConfig); + } + } + + private class QosConfigReceiver extends BroadcastReceiver { + /*am broadcast -a qti.intent.bluetooth.action.UPDATE_QOS_CONFIG --es + qti.bluetooth.extra.CODEC_ID "LC3" --es qti.bluetooth.extra.QOS_CONFIG ""*/ + boolean enable = false; + + ArrayList supported_Qos_config = new ArrayList( List.of( + "8_1_1", + "8_2_1", + "16_1_1", + "16_2_1", + "24_1_1", + "24_2_1", + "32_1_1", + "32_2_1", + "441_1_1", + "441_2_1", + "48_1_1", + "48_2_1", + "48_3_1", + "48_4_1", + "48_5_1", + "48_6_1", + + "8_1_2", + "8_2_2", + "16_1_2", + "16_2_2", + "24_1_2", + "24_2_2", + "32_1_2", + "32_2_2", + "441_1_2", + "441_2_2", + "48_1_2", + "48_2_2", + "48_3_2", + "48_4_2", + "48_5_2", + "48_6_2" + )); + + @Override + public void onReceive(Context context, Intent intent) { + if(!enable) + return; + if (!ACTION_UPDATE_QOS_CONFIG.equals(intent.getAction())) { + return; + } + + String mCodecId = intent.getStringExtra(CODEC_ID); + if(mCodecId == null || !isValidCodec(mCodecId)) { + Log.w(TAG, "Invalid Codec " + mCodecId); + return; + } + String mQosConfig = intent.getStringExtra(QOS_CONFIG); + if(mQosConfig == null || !isValidQosConfig(mQosConfig)) { + Log.w(TAG, "Invalid QosConfig " + mQosConfig); + return; + } + + ActiveDeviceManagerService mActiveDeviceManager + = ActiveDeviceManagerService.get(); + int profile = mActiveDeviceManager.getActiveProfile(ApmConst.AudioFeatures.MEDIA_AUDIO); + if(profile == ApmConst.AudioProfiles.BROADCAST_LE) { + /*Update Broadcast module here*/ + } else if (profile == ApmConst.AudioProfiles.BAP_MEDIA) { + /*Update ACM here*/ + } + Log.i(TAG, "New Qos Config ID: " + mQosConfig + " for profile: " + profile); + } + + boolean isValidQosConfig(String mQosConfig) { + return supported_Qos_config.contains(mQosConfig); + } + } + + class BapBroadcastManager { + void setCodecPreference(String codecConfig, int channelMode) { + BroadcastService mBroadcastService = BroadcastService.getBroadcastService(); + if(mBroadcastService != null) { + mBroadcastService.setCodecPreference(codecConfig, channelMode); + } + } + + BluetoothCodecStatus getCodecStatus() { + BroadcastService mBroadcastService = BroadcastService.getBroadcastService(); + if(mBroadcastService != null) { + return mBroadcastService.getCodecStatus(); + } + return null; + } + + boolean isBapBroadcastActive() { + BroadcastService mBroadcastService = BroadcastService.getBroadcastService(); + if(mBroadcastService != null) { + return mBroadcastService.isBroadcastActive(); + } + return false; + } + } +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/MediaControlManager.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/MediaControlManager.java new file mode 100644 index 00000000000..44cbd0376b8 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/MediaControlManager.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * + **************************************************************************/ + +package com.android.bluetooth.apm; + +import android.os.Binder; +import android.os.HandlerThread; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.SystemProperties; +import android.util.Log; +import com.android.internal.util.ArrayUtils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import android.content.Context; +import android.content.Intent; +import android.content.BroadcastReceiver; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.media.AudioAttributes; +import android.media.AudioPlaybackConfiguration; +import android.media.MediaDescription; +import android.media.MediaMetadata; +import android.media.session.MediaSession; +import android.media.session.MediaSession.QueueItem; +import android.media.session.MediaSessionManager; +import android.media.session.PlaybackState; +import android.util.Log; +import android.util.StatsLog; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; + +import com.android.bluetooth.avrcp.MediaController; +import com.android.bluetooth.apm.StreamAudioService; +import com.android.bluetooth.mcp.McpService; + +public class MediaControlManager { + private static final boolean DBG = true; + private static final String TAG = "APM: MediaControlManager"; + + static MediaControlManager mMediaControlManager = null; + + PlaybackCallback mPlaybackCallbackCb; + //MediaControlCallback mMediaControlCallbackCb; + BroadcastReceiver mMediaControlReceiver; + private static Context mContext; + //private AudioManager mAudioManager; + private Handler mHandler; + private McpService mMcpService; + public static final String MusicPlayerControlServiceName = "com.android.bluetooth.mcp.McpService"; + public static final int MUSIC_PLAYER_CONTROL = McpService.MUSIC_PLAYER_CONTROL; + private MediaControlManager () { + mPlaybackCallbackCb = new PlaybackCallback(); + //mMediaControlCallbackCb = new MediaControlCallback(); + mMediaControlReceiver = new MediaControlReceiver(); + } + + public static MediaControlManager get() { + if(mMediaControlManager == null) { + mMediaControlManager = new MediaControlManager(); + } + Log.v(TAG, "get"); + return mMediaControlManager; + } + + public static void make(Context context) { + if(mMediaControlManager == null) { + mMediaControlManager = new MediaControlManager(); + mMediaControlManager.init(context); + MediaControlManagerIntf.init(mMediaControlManager); + Log.v(TAG, "init, New mMediaControlManager instance"); + } + } + + public void init(Context context) { + mContext = context; + + + + /*mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + + HandlerThread thread = new HandlerThread("MediaControlThread"); + Looper looper = thread.getLooper(); + mHandler = new Handler(looper); + mAudioManager.registerAudioPlaybackCallback(mPlaybackCallbackCb, + mHandler);*/ + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + mContext.registerReceiver(mMediaControlReceiver, filter); + Log.v(TAG, "init done"); + } + + private void updateCurrentPlayer (int playerId, int browseId) { + } + + void handlePassthroughCmd(int op, int state) { + } + + private class PlaybackCallback extends AudioManager.AudioPlaybackCallback { + @Override + public void onPlaybackConfigChanged(List configs) { + super.onPlaybackConfigChanged(configs); + + /*Update Playback config*/ + } + } + + + public void onMetadataChanged(MediaMetadata metadata) { + /*Update Metadata change*/ + Log.v(TAG, "onMetadataChanged"); + mMcpService = McpService.getMcpService(); + if (mMcpService != null) { + mMcpService.updateMetaData(metadata); + } + } + + public synchronized void onPlaybackStateChanged(PlaybackState state) { + /*Update Playback State*/ + Log.v(TAG, "onPlaybackStateChanged"); + mMcpService = McpService.getMcpService(); + if (mMcpService != null) { + mMcpService.updatePlaybackState(state); + } + + } + + public synchronized void onPackageChanged(String packageName) { + Log.v(TAG, "onPackageChanged"); + mMcpService = McpService.getMcpService(); + boolean removed = false; + if (packageName == null) + removed = true; + if (mMcpService != null) { + mMcpService.updatePlayerName(packageName, removed); + } + } + public void onSessionDestroyed(String packageName) { + Log.v(TAG, "onSessionDestroyed"); + mMcpService = McpService.getMcpService(); + if (mMcpService != null) { + mMcpService.updatePlayerName(packageName, true); + } + } + + public void onQueueChanged(List queue) { + + } + + private class MediaControlReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String packageName = null; + String action = intent.getAction(); + boolean removed = false; + Log.v(TAG, "action " + action); + if(action == null) + return; + + switch(action) { + case Intent.ACTION_PACKAGE_REMOVED: + packageName = intent.getData().getSchemeSpecificPart(); + /*handle package removed*/ + removed = true; + break; + case Intent.ACTION_PACKAGE_ADDED: + packageName = intent.getData().getSchemeSpecificPart(); + /*handle package added*/ + break; + case Intent.ACTION_PACKAGE_CHANGED: + packageName = intent.getData().getSchemeSpecificPart(); + /*handle package changed*/ + break; + default : + break; + } + mMcpService = McpService.getMcpService(); + if (mMcpService != null) { + mMcpService.updatePlayerName(packageName, removed); + } + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/StreamAudioService.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/StreamAudioService.java new file mode 100644 index 00000000000..5835f80f14b --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/StreamAudioService.java @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + **************************************************************************/ + +package com.android.bluetooth.apm; + +import static com.android.bluetooth.Utils.enforceBluetoothPermission; +import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission; + +import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothCodecStatus; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.IBluetoothVcp; + +import android.os.Binder; +import android.os.HandlerThread; +import android.os.Handler; +import android.os.Message; +import android.os.SystemProperties; +import android.util.Log; + +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.acm.AcmService; + +public class StreamAudioService extends ProfileService { + private static final boolean DBG = true; + private static final String TAG = "APM: StreamAudioService:"; + public static final int LE_AUDIO_UNICAST = 26; + + public static final String CoordinatedAudioServiceName = "com.android.bluetooth.acm.AcmService"; + public static final int COORDINATED_AUDIO_UNICAST = AcmService.ACM_AUDIO_UNICAST; + + private static StreamAudioService sStreamAudioService; + private ActiveDeviceManagerService mActiveDeviceManager; + private MediaAudio mMediaAudio; + private VolumeManager mVolumeManager; + private final Object mVolumeManagerLock = new Object(); + @Override + protected void create() { + Log.i(TAG, "create()"); + } + + private static final int BAP = 0x01; + private static final int GCP = 0x02; + private static final int WMCP = 0x04; + private static final int VMCP = 0x08; + private static final int BAP_CALL = 0x10; + + private static final int MEDIA_CONTEXT = 1; + private static final int VOICE_CONTEXT = 2; + + @Override + protected boolean start() { + if(sStreamAudioService != null) { + Log.i(TAG, "StreamAudioService already started"); + return true; + } + Log.i(TAG, "start()"); + + ApmConst.setLeAudioEnabled(true); + ApmConstIntf.init(); + + setStreamAudioService(this); + + mActiveDeviceManager = ActiveDeviceManagerService.get(this); + mMediaAudio = MediaAudio.init(this); + + DeviceProfileMap dpm = DeviceProfileMap.getDeviceProfileMapInstance(); + dpm.init(this); + CallAudio mCallAudio = CallAudio.init(this); + synchronized (mVolumeManagerLock) { + mVolumeManager = VolumeManager.init(this); + } + + Log.i(TAG, "start() complete"); + return true; + } + + @Override + protected boolean stop() { + Log.w(TAG, "stop() called"); + if (sStreamAudioService == null) { + Log.w(TAG, "stop() called before start()"); + return true; + } + + if (mActiveDeviceManager != null) { + mActiveDeviceManager.disable(); + mActiveDeviceManager.cleanup(); + } + + DeviceProfileMap dMap = DeviceProfileMap.getDeviceProfileMapInstance(); + dMap.cleanup(); + return true; + } + + @Override + protected void cleanup() { + Log.i(TAG, "cleanup()"); + synchronized (mVolumeManagerLock) { + mVolumeManager.cleanup(); + mVolumeManager = null; + } + setStreamAudioService(null); + } + + public boolean connectLeStream(BluetoothDevice device, int profile) { + AcmService mAcmService = AcmService.getAcmService(); + int mContext = getContext(profile); + + if(mContext == 0) { + Log.e(TAG, "No valid context for profiles passed"); + return false; + } + return mAcmService.connect(device, mContext, getAcmProfileID(profile), MEDIA_CONTEXT); + //return mAcmService.connect(device, VOICE_CONTEXT, BAP_CALL, VOICE_CONTEXT); + //return mAcmService.connect(device, MEDIA_CONTEXT, BAP|WMCP, MEDIA_CONTEXT); + } + + public boolean disconnectLeStream(BluetoothDevice device, boolean callAudio, boolean mediaAudio) { + AcmService mAcmService = AcmService.getAcmService(); + if(callAudio && mediaAudio) + return mAcmService.disconnect(device, VOICE_CONTEXT | MEDIA_CONTEXT); + //return mAcmService.disconnect(device, VOICE_CONTEXT); + //return mAcmService.disconnect(device, MEDIA_CONTEXT); + else if(mediaAudio) + return mAcmService.disconnect(device, MEDIA_CONTEXT); + else if(callAudio) + return mAcmService.disconnect(device, VOICE_CONTEXT); + + return false; + } + + public boolean startStream(BluetoothDevice device) { + AcmService mAcmService = AcmService.getAcmService(); + return mAcmService.StartStream(device, VOICE_CONTEXT); + } + + public boolean stopStream(BluetoothDevice device) { + AcmService mAcmService = AcmService.getAcmService(); + return mAcmService.StopStream(device, VOICE_CONTEXT); + } + + public int setActiveDevice(BluetoothDevice device, int profile, boolean playReq) { + AcmService mAcmService = AcmService.getAcmService(); + if (mAcmService == null && device == null) { + Log.w(TAG, ": device is null, fake success."); + return mActiveDeviceManager.SHO_SUCCESS; + } + + if(ApmConst.AudioProfiles.BAP_MEDIA == profile) { + return mAcmService.setActiveDevice(device, MEDIA_CONTEXT, BAP, playReq); + } else if(ApmConst.AudioProfiles.BAP_GCP == profile){ + return mAcmService.setActiveDevice(device, MEDIA_CONTEXT, GCP, playReq); + } else if(ApmConst.AudioProfiles.BAP_RECORDING == profile){ + return mAcmService.setActiveDevice(device, MEDIA_CONTEXT, WMCP, playReq); + } else { + return mAcmService.setActiveDevice(device, VOICE_CONTEXT, BAP_CALL, playReq); + //return mAcmService.setActiveDevice(device, MEDIA_CONTEXT, BAP, playReq); + } + } + + public void setCodecConfig(BluetoothDevice device, String codecID, int channelMode) { + AcmService mAcmService = AcmService.getAcmService(); + mAcmService.ChangeCodecConfigPreference(device, codecID); + } + + public BluetoothDevice getDeviceGroup(BluetoothDevice device){ + AcmService mAcmService = AcmService.getAcmService(); + return mAcmService.getGroup(device); + } + + public void onConnectionStateChange(BluetoothDevice device, int state, int audioType, boolean primeDevice) { + MediaAudio mMediaAudio = MediaAudio.get(); + CallAudio mCallAudio = CallAudio.get(); + int profile = ApmConst.AudioFeatures.MAX_AUDIO_FEATURES; + if(audioType == ApmConst.AudioFeatures.CALL_AUDIO) { + mCallAudio.onConnStateChange(device, state, ApmConst.AudioProfiles.BAP_CALL); + } else if(audioType == ApmConst.AudioFeatures.MEDIA_AUDIO) { + boolean isCsipDevice = (device != null) && + getDeviceGroup(device).getAddress().contains(ApmConst.groupAddress); + if(isCsipDevice) + mMediaAudio.onConnStateChange(device, state, ApmConst.AudioProfiles.BAP_MEDIA, primeDevice); + else + mMediaAudio.onConnStateChange(device, state, ApmConst.AudioProfiles.BAP_MEDIA); + } + } + + public void onStreamStateChange(BluetoothDevice device, int state, int audioType) { + MediaAudio mMediaAudio = MediaAudio.get(); + CallAudio mCallAudio = CallAudio.get(); + if(audioType == ApmConst.AudioFeatures.MEDIA_AUDIO) + mMediaAudio.onStreamStateChange(device, state); + else if(audioType == ApmConst.AudioFeatures.CALL_AUDIO) + mCallAudio.onAudioStateChange(device, state); + } + + public void onActiveDeviceChange(BluetoothDevice device, int audioType) { + if (mActiveDeviceManager != null) + mActiveDeviceManager.onActiveDeviceChange(device, audioType); + } + + public void onMediaCodecConfigChange(BluetoothDevice device, BluetoothCodecStatus codecStatus, int audioType) { + MediaAudio mMediaAudio = MediaAudio.get(); + mMediaAudio.onCodecConfigChange(device, codecStatus, ApmConst.AudioProfiles.BAP_MEDIA); + } + + public void onMediaCodecConfigChange(BluetoothDevice device, BluetoothCodecStatus codecStatus, int audioType, boolean updateAudio) { + MediaAudio mMediaAudio = MediaAudio.get(); + mMediaAudio.onCodecConfigChange(device, codecStatus, ApmConst.AudioProfiles.BAP_MEDIA, updateAudio); + } + + public void setCallAudioParam(String param) { + CallAudio mCallAudio = CallAudio.get(); + mCallAudio.setAudioParam(param); + } + + public void setCallAudioOn(boolean on) { + CallAudio mCallAudio = CallAudio.get(); + mCallAudio.setBluetoothScoOn(on); + } + + public int getVcpConnState(BluetoothDevice device) { + synchronized (mVolumeManagerLock) { + if (mVolumeManager == null) + return BluetoothProfile.STATE_DISCONNECTED; + return mVolumeManager.getConnectionState(device); + } + } + + public int getConnectionMode(BluetoothDevice device) { + synchronized (mVolumeManagerLock) { + if (mVolumeManager == null) + return BluetoothProfile.STATE_DISCONNECTED; + return mVolumeManager.getConnectionMode(device); + } + } + + public void setAbsoluteVolume(BluetoothDevice device, int volume) { + synchronized (mVolumeManagerLock) { + if (mVolumeManager != null) + mVolumeManager.updateBroadcastVolume(device, volume); + } + } + + public int getAbsoluteVolume(BluetoothDevice device) { + synchronized (mVolumeManagerLock) { + if (mVolumeManager == null) + return 7; + return mVolumeManager.getBassVolume(device); + } + } + + public void setMute(BluetoothDevice device, boolean muteStatus) { + synchronized (mVolumeManagerLock) { + if (mVolumeManager != null) + mVolumeManager.setMute(device, muteStatus); + } + } + + public boolean isMute(BluetoothDevice device) { + synchronized (mVolumeManagerLock) { + if (mVolumeManager == null) + return false; + return mVolumeManager.getMuteStatus(device); + } + } + + private int getContext(int profileID) { + int context = 0; + if((DeviceProfileMap.getLeMediaProfiles() & profileID) > 0) { + context = (context|MEDIA_CONTEXT); + } + + if((DeviceProfileMap.getLeCallProfiles() & profileID) > 0) { + context = (context|VOICE_CONTEXT); + } + return context; + } + + private int getAcmProfileID (int ProfileID) { + int AcmProfileID = 0; + if((ApmConst.AudioProfiles.BAP_MEDIA & ProfileID) == ApmConst.AudioProfiles.BAP_MEDIA) + AcmProfileID = BAP; + if((ApmConst.AudioProfiles.BAP_CALL & ProfileID) == ApmConst.AudioProfiles.BAP_CALL) + AcmProfileID = AcmProfileID | BAP_CALL; + if((ApmConst.AudioProfiles.BAP_GCP & ProfileID) == ApmConst.AudioProfiles.BAP_GCP) + AcmProfileID = AcmProfileID | GCP; + if((ApmConst.AudioProfiles.BAP_RECORDING & ProfileID) == ApmConst.AudioProfiles.BAP_RECORDING) + AcmProfileID = AcmProfileID | WMCP; + return AcmProfileID; + } + + @Override + protected IProfileServiceBinder initBinder() { + return new LeAudioUnicastBinder(this); + } + + private static class LeAudioUnicastBinder extends IBluetoothVcp.Stub implements IProfileServiceBinder { + + StreamAudioService mService; + LeAudioUnicastBinder(StreamAudioService service) { + mService = service; + } + + @Override + public void cleanup() { + } + + @Override + public int getConnectionState(BluetoothDevice device) { + if(mService == null) + return BluetoothProfile.STATE_DISCONNECTED; + return mService.getVcpConnState(device); + } + + @Override + public int getConnectionMode(BluetoothDevice device) { + if(mService != null) { + return mService.getConnectionMode(device); + } + return 0; + } + + @Override + public void setAbsoluteVolume(BluetoothDevice device, int volume) { + if(mService != null) { + mService.setAbsoluteVolume(device, volume); + } + } + + @Override + public int getAbsoluteVolume(BluetoothDevice device) { + if(mService == null) + return 7; + return mService.getAbsoluteVolume(device); + } + + @Override + public void setMute (BluetoothDevice device, boolean enableMute) { + if(mService != null) { + mService.setMute(device, enableMute); + } + } + + @Override + public boolean isMute(BluetoothDevice device) { + if(mService != null) { + return mService.isMute(device); + } + return false; + } + } + + public static StreamAudioService getStreamAudioService() { + return sStreamAudioService; + } + + private static synchronized void setStreamAudioService(StreamAudioService instance) { + if (DBG) { + Log.d(TAG, "setStreamAudioService(): set to: " + instance); + } + sStreamAudioService = instance; + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/VolumeManager.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/VolumeManager.java new file mode 100644 index 00000000000..79239872b40 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/VolumeManager.java @@ -0,0 +1,765 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + **************************************************************************/ + +package com.android.bluetooth.apm; + +import android.bluetooth.BleBroadcastAudioScanAssistManager; +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothHeadset; +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.a2dp.A2dpService; +import com.android.bluetooth.avrcp.Avrcp_ext; +import com.android.bluetooth.acm.AcmService; +import com.android.bluetooth.bc.BCService; +import com.android.bluetooth.hfp.HeadsetService; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.media.AudioManager; +import android.util.Log; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.List; +import java.util.Map; + +public class VolumeManager { + public static final String TAG = "APM: VolumeManager"; + private static VolumeManager mVolumeManager = null; + private DeviceVolume mMedia; + private DeviceVolume mCall; + private DeviceVolume mBroadcast; + private DeviceProfileMap dpm; + private MediaAudio mMediaAudio; + private CallAudio mCallAudio; + private static Context mContext; + BroadcastReceiver mVolumeManagerReceiver; + Map AbsVolumeSupport; + + public static final String CALL_VOLUME_MAP = "bluetooth_call_volume_map"; + public static final String MEDIA_VOLUME_MAP = "bluetooth_media_volume_map"; + public static final String BROADCAST_VOLUME_MAP = "bluetooth_broadcast_volume_map"; + public final String ACTION_SHUTDOWN = "android.intent.action.ACTION_SHUTDOWN"; + public final String ACTION_POWER_OFF = "android.intent.action.QUICKBOOT_POWEROFF"; + + private VolumeManager() { + mCall = new DeviceVolume(mContext, CALL_VOLUME_MAP); + mMedia = new DeviceVolume(mContext, MEDIA_VOLUME_MAP); + mBroadcast = new DeviceVolume(mContext, BROADCAST_VOLUME_MAP); + + dpm = DeviceProfileMap.getDeviceProfileMapInstance(); + mMediaAudio = MediaAudio.get(); + mCallAudio = CallAudio.get(); + + AbsVolumeSupport = new ConcurrentHashMap(); + + mVolumeManagerReceiver = new VolumeManagerReceiver(); + IntentFilter filter = new IntentFilter(); + filter.addAction(AudioManager.VOLUME_CHANGED_ACTION); + filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + filter.addAction(ACTION_SHUTDOWN); + filter.addAction(ACTION_POWER_OFF); + filter.addAction(BleBroadcastAudioScanAssistManager.ACTION_BROADCAST_SOURCE_INFO); + mContext.registerReceiver(mVolumeManagerReceiver, filter); + } + + public static VolumeManager init (Context context) { + mContext = context; + + if(mVolumeManager == null) { + mVolumeManager = new VolumeManager(); + VolumeManagerIntf.init(mVolumeManager); + } + + return mVolumeManager; + } + + public void cleanup() { + Log.i(TAG, "cleanup"); + handleDeviceShutdown(); + synchronized (mVolumeManager) { + mCall = null; + mMedia = null; + mBroadcast = null; + mContext.unregisterReceiver(mVolumeManagerReceiver); + mVolumeManagerReceiver = null; + AbsVolumeSupport.clear(); + AbsVolumeSupport = null; + mVolumeManager = null; + } + } + + public static VolumeManager get() { + return mVolumeManager; + } + + private DeviceVolume VolumeType(int mAudioType) { + if(ApmConst.AudioFeatures.CALL_AUDIO == mAudioType) { + return mCall; + } else if(ApmConst.AudioFeatures.MEDIA_AUDIO == mAudioType) { + return mMedia; + } else if(ApmConst.AudioFeatures.BROADCAST_AUDIO == mAudioType) { + return mBroadcast; + } + return null; + } + + public int getConnectionMode(BluetoothDevice device) { + AcmService mAcmService = AcmService.getAcmService(); + if(mAcmService == null) { + return -1; + } + return mAcmService.getVcpConnMode(device); + } + + public void setMediaAbsoluteVolume (Integer volume) { + if(mMedia.mDevice == null) { + Log.e (TAG, "setMediaAbsoluteVolume: No Device Active for Media. Ignore"); + return; + } + mMedia.updateVolume(volume); + + if(ApmConst.AudioProfiles.AVRCP == mMedia.mProfile) { + Avrcp_ext mAvrcp = Avrcp_ext.get(); + if(mAvrcp != null) { + Log.i (TAG, "setMediaAbsoluteVolume: Updating new volume to AVRCP: " + volume); + mAvrcp.setAbsoluteVolume(volume); + } + } else if(ApmConst.AudioProfiles.VCP == mMedia.mProfile) { + AcmService mAcmService = AcmService.getAcmService(); + if(mAcmService != null) { + Log.i (TAG, "setMediaAbsoluteVolume: Updating new volume to VCP: " + volume); + mMedia.updateVolume(volume); + mAcmService.setAbsoluteVolume(mMedia.mDevice, volume, ApmConst.AudioFeatures.MEDIA_AUDIO); + } + } + } + + public void updateMediaStreamVolume (Integer volume) { + if(mMedia.mDevice == null) { + Log.e (TAG, "updateMediaStreamVolume: No Device Active for Media. Ignore"); + return; + } + + if(mMedia.mSupportAbsoluteVolume) { + /* Ignore: Will update volume via API call */ + return; + } + mMedia.updateVolume(volume); + } + + public void updateBroadcastVolume (BluetoothDevice device, int volume) { + int callAudioState = mCallAudio.getAudioState(device); + boolean isCall = (callAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTING || + callAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTED); + if (isCall) { + Log.e(TAG, "Call in progress, ignore volume change"); + return; + } + + mBroadcast.updateVolume(device, volume); + AcmService mAcmService = AcmService.getAcmService(); + BluetoothDevice mGroupDevice = mAcmService.getGroup(device); + mAcmService.setAbsoluteVolume(mGroupDevice, volume, ApmConst.AudioFeatures.BROADCAST_AUDIO); + mBroadcast.updateVolume(mGroupDevice, volume); + } + + public void setMute(BluetoothDevice device, boolean muteStatus) { + AcmService mAcmService = AcmService.getAcmService(); + BluetoothDevice mGroupDevice = mAcmService.getGroup(device); + mAcmService.setMute(mGroupDevice, muteStatus); + } + + public void restoreCallVolume (Integer volume) { + if(mCall.mDevice == null) { + Log.e (TAG, "restoreCallVolume: No Device Active for Call. Ignore"); + return; + } + + if(ApmConst.AudioProfiles.HFP == mCall.mProfile) { + // Ignore restoring call volume for HFP case + Log.w (TAG, "restoreCallVolume: Ignore restore call volume for HFP"); + } else if(ApmConst.AudioProfiles.VCP == mCall.mProfile) { + AcmService mAcmService = AcmService.getAcmService(); + if(mAcmService != null) { + Log.i (TAG, "restoreCallVolume: Updating new volume to VCP: " + volume); + mCall.updateVolume(volume); + mAcmService.setAbsoluteVolume(mCall.mDevice, volume, ApmConst.AudioFeatures.CALL_AUDIO); + } + // TODO: Restore call volume to MM-Audio also + } + } + + public void setCallVolume (Intent intent) { + if(mCall.mDevice == null) { + Log.e (TAG, "setCallVolume: No Device Active for Call. Ignore"); + return; + } + + int volume = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0); + if(ApmConst.AudioProfiles.HFP == mCall.mProfile) { + Log.i (TAG, "setCallVolume: Updating new volume to HFP: " + volume); + HeadsetService headsetService = HeadsetService.getHeadsetService(); + headsetService.setIntentScoVolume(intent); + } else if(ApmConst.AudioProfiles.VCP == mCall.mProfile) { + Log.i (TAG, "setCallVolume: mCall volume: " + mCall.mVolume + ", volume: " + volume); + // Avoid updating same call volume after remote volume change + if (volume == mCall.mVolume) { + Log.w (TAG, "setCallVolume: Ignore updating same call volume to remote"); + return; + } + AcmService mAcmService = AcmService.getAcmService(); + if(mAcmService != null) { + Log.i (TAG, "setCallVolume: Updating new volume to VCP: " + volume); + mCall.updateVolume(volume); + mAcmService.setAbsoluteVolume(mCall.mDevice, volume, ApmConst.AudioFeatures.CALL_AUDIO); + } + } + } + + public int getConnectionState(BluetoothDevice device) { + AcmService mAcmService = AcmService.getAcmService(); + return mAcmService.getVcpConnState(device); + } + + public void onConnStateChange(BluetoothDevice device, Integer state, Integer profile) { + Log.d (TAG, "onConnStateChange: state: " + state + " Profile: " + profile); + if (device == null) { + Log.e (TAG, "onConnStateChange: device is null. Ignore"); + return; + } + + AcmService mAcmService = AcmService.getAcmService(); + BluetoothDevice mGroupDevice; + if(mAcmService != null) { + mGroupDevice = mAcmService.getGroup(device); + } else { + mGroupDevice = device; + } + + if (mGroupDevice.equals(mMedia.mDevice)) { + mMedia.mProfile = + dpm.getProfile(mGroupDevice, ApmConst.AudioFeatures.MEDIA_VOLUME_CONTROL); + } + if (mGroupDevice.equals(mCall.mDevice)) { + mCall.mProfile = + dpm.getProfile(mGroupDevice, ApmConst.AudioFeatures.CALL_VOLUME_CONTROL); + } + + if (state == BluetoothProfile.STATE_CONNECTED) { + int audioType = getActiveAudioType(device); + if (ApmConst.AudioFeatures.MEDIA_AUDIO == audioType && mMedia.mProfile == profile) { + Log.d (TAG, "onConnStateChange: Media is streaming or active, update media volume"); + setMediaAbsoluteVolume(mMedia.mVolume); + } else if (ApmConst.AudioFeatures.CALL_AUDIO == audioType && + mCall.mProfile == profile) { + Log.d (TAG, "onConnStateChange: Call is streaming, update call volume"); + restoreCallVolume(mCall.mVolume); + } else if (ApmConst.AudioFeatures.BROADCAST_AUDIO == audioType) { + Log.d (TAG, "onConnStateChange: Broadcast is streaming, update broadcast volume"); + updateBroadcastVolume(device, getBassVolume(device)); + } + } + } + + public void onVolumeChange(Integer volume, Integer audioType, Boolean showUI) { + int flag = showUI ? AudioManager.FLAG_SHOW_UI : 0; + if(audioType == ApmConst.AudioFeatures.CALL_AUDIO){ + mCall.updateVolume(volume); + mCallAudio.getAudioManager().setStreamVolume(AudioManager.STREAM_BLUETOOTH_SCO, + volume, flag); + } else if(audioType == ApmConst.AudioFeatures.MEDIA_AUDIO) { + mMedia.updateVolume(volume); + mMediaAudio.getAudioManager().setStreamVolume(AudioManager.STREAM_MUSIC, volume, + flag | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME); + } + } + + public void onVolumeChange(BluetoothDevice device, Integer volume, Integer audioType) { + if ((VolumeType(audioType) == mCall && device.equals(mCall.mDevice)) || + (VolumeType(audioType) == mMedia && device.equals(mMedia.mDevice))) { + onVolumeChange(volume, audioType, true); + } else { + mBroadcast.updateVolume(device, volume); + } + } + + public void onMuteStatusChange(BluetoothDevice device, boolean isMute, int audioType) { + } + + + public void onActiveDeviceChange(BluetoothDevice device, int audioType) { + if(device == null) { + synchronized(mVolumeManager) { + if(VolumeType(audioType) != null) + VolumeType(audioType).reset(); + } + } else { + int mProfile = dpm.getProfile(device, audioType == ApmConst.AudioFeatures.CALL_AUDIO? + ApmConst.AudioFeatures.CALL_VOLUME_CONTROL:ApmConst.AudioFeatures.MEDIA_VOLUME_CONTROL); + DeviceVolume mDeviceVolume = VolumeType(audioType); + mDeviceVolume.updateDevice(device, mProfile); + Log.i(TAG, "ActiveDeviceChange: device: " + mDeviceVolume.mDevice + ". AudioType: " + audioType); + if(mDeviceVolume.equals(mMedia)) { + int mAbsVolSupportProfiles = AbsVolumeSupport.getOrDefault(device.getAddress(), 0); + boolean isAbsSupported = ((mProfile & mAbsVolSupportProfiles) != 0) ? true : false; + Log.i(TAG, "isAbsoluteVolumeSupport: " + isAbsSupported); + mDeviceVolume.mSupportAbsoluteVolume = isAbsSupported; + mMediaAudio.getAudioManager().avrcpSupportsAbsoluteVolume ( + device.getAddress(), isAbsSupported); + + Log.i(TAG, "ActiveDeviceChange: Profile: " + mProfile + ". New Volume: " + mDeviceVolume.mVolume); + if (!isBroadcastAudioSynced(device) || + (mMediaAudio.isA2dpPlaying(device) && mMediaAudio.getAudioManager().isMusicActive())) { + setMediaAbsoluteVolume(mDeviceVolume.mVolume); + } + } + } + } + + public void updateStreamState(BluetoothDevice device, Integer streamState, Integer audioType) { + boolean isMusicActive = false; + if (device == null) { + Log.e (TAG, "updateStreamState: device is null. Ignore"); + return; + } + if (audioType == ApmConst.AudioFeatures.MEDIA_AUDIO && + streamState == BluetoothA2dp.STATE_PLAYING) { + isMusicActive = mMediaAudio.getAudioManager().isMusicActive(); + } + Log.d(TAG, "updateStreamState, device: " + device + " type: " + audioType + + " streamState: " + streamState + " isMusicActive: " + isMusicActive); + + AcmService mAcmService = AcmService.getAcmService(); + BluetoothDevice mGroupDevice; + if(mAcmService != null) { + mGroupDevice = mAcmService.getGroup(device); + } else { + mGroupDevice = device; + } + + if ((audioType == ApmConst.AudioFeatures.MEDIA_AUDIO && + streamState == BluetoothA2dp.STATE_NOT_PLAYING) || + (audioType == ApmConst.AudioFeatures.CALL_AUDIO && + streamState == BluetoothHeadset.STATE_AUDIO_DISCONNECTED)) { + if (isBroadcastAudioSynced(device)) { + handleBroadcastAudioSynced(device); + } + } else if (audioType == ApmConst.AudioFeatures.MEDIA_AUDIO && + streamState == BluetoothA2dp.STATE_PLAYING && isMusicActive) { + if (mGroupDevice.equals(mMedia.mDevice)) { + Log.d(TAG, "Restore volume for A2dp streaming"); + setMediaAbsoluteVolume(mMedia.mVolume); + } + } else if (audioType == ApmConst.AudioFeatures.CALL_AUDIO && + streamState == BluetoothHeadset.STATE_AUDIO_CONNECTED) { + if (mGroupDevice.equals(mCall.mDevice)) { + Log.d(TAG, "Restore volume for call"); + restoreCallVolume(mCall.mVolume); + } + } + } + + public int getActiveAudioType(BluetoothDevice device) { + int callAudioState = mCallAudio.getAudioState(device); + boolean isCall = (callAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTING || + callAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTED); + int audioType = -1; + + if (device == null) { + Log.e (TAG, "getActiveAudioType: device is null. Ignore"); + return audioType; + } + + AcmService mAcmService = AcmService.getAcmService(); + BluetoothDevice mGroupDevice; + if(mAcmService != null) { + mGroupDevice = mAcmService.getGroup(device); + } else { + mGroupDevice = device; + } + + if (mMediaAudio.isA2dpPlaying(device) && + mMediaAudio.getAudioManager().isMusicActive()) { + if (mGroupDevice.equals(mMedia.mDevice)) { + Log.d(TAG, "Active Media audio is streaming"); + audioType = ApmConst.AudioFeatures.MEDIA_AUDIO; + } + } else if (isCall) { + if (mGroupDevice.equals(mCall.mDevice)) { + Log.d(TAG, "Active Call audio is streaming"); + audioType = ApmConst.AudioFeatures.CALL_AUDIO; + } + } else if (isBroadcastAudioSynced(device)) { + Log.d(TAG, "Broadcast audio is streaming"); + audioType = ApmConst.AudioFeatures.BROADCAST_AUDIO; + } else { + Log.d(TAG, "None of audio is streaming"); + ActiveDeviceManagerService activeDeviceManager = + ActiveDeviceManagerService.get(mContext); + BluetoothDevice activeDevice = + activeDeviceManager.getActiveDevice(ApmConst.AudioFeatures.MEDIA_AUDIO); + if (mGroupDevice.equals(mMedia.mDevice) && mGroupDevice.equals(activeDevice)) { + Log.d(TAG, "Peer is Media active, set for media type by default"); + audioType = ApmConst.AudioFeatures.MEDIA_AUDIO; + } else { + Log.d(TAG, "Inactive peer, unknow audio type"); + } + } + + Log.d(TAG, "getActiveAudioType: ret " + audioType); + return audioType; + } + + /*Should be called by AVRCP and VCP after every connection*/ + public void setAbsoluteVolumeSupport(BluetoothDevice device, Boolean isSupported, + Integer initVol, Integer profile) { + setAbsoluteVolumeSupport(device, isSupported, profile); + } + + public void setAbsoluteVolumeSupport(BluetoothDevice device, Boolean isSupported, + Integer profile) { + Log.i(TAG, "setAbsoluteVolumeSupport device " + device + " profile " + profile + + " isSupported " + isSupported); + if(device == null) + return; + + int mProfile = dpm.getProfile(device, ApmConst.AudioFeatures.MEDIA_VOLUME_CONTROL); + int mAbsVolSupportProfiles = AbsVolumeSupport.getOrDefault(device.getAddress(), 0); + if (isSupported) { + mAbsVolSupportProfiles = mAbsVolSupportProfiles | profile; + } else { + mAbsVolSupportProfiles = mAbsVolSupportProfiles & ~profile; + } + + if(device.equals(mMedia.mDevice)) { + boolean isAbsSupported = ((mProfile & mAbsVolSupportProfiles) != 0) ? true : false; + Log.i(TAG, "Update abs volume support: " + isAbsSupported); + mMedia.mSupportAbsoluteVolume = isAbsSupported; + mMediaAudio.getAudioManager().avrcpSupportsAbsoluteVolume ( + device.getAddress(), isAbsSupported); + + if(mMedia.mProfile == ApmConst.AudioProfiles.NONE) { + mMedia.mProfile = mProfile; + Log.i(TAG, "setAbsoluteVolumeSupport: Profile: " + mMedia.mProfile); + } + } + AbsVolumeSupport.put(device.getAddress(), mAbsVolSupportProfiles); + } + + public void saveVolume(Integer audioType) { + VolumeType(audioType).saveVolume(); + } + + public int getSavedVolume(BluetoothDevice device, Integer audioType) { + return VolumeType(audioType).getSavedVolume(device); + } + + public int getActiveVolume(Integer audioType) { + return VolumeType(audioType).mVolume; + } + + public int getBassVolume(BluetoothDevice device) { + AcmService mAcmService = AcmService.getAcmService(); + BluetoothDevice mGroupDevice = mAcmService.getGroup(device); + int volume = mBroadcast.getVolume(mGroupDevice); + Log.i(TAG, "getBassVolume: " + device + " volume: " + volume); + return volume; + } + + public boolean getMuteStatus(BluetoothDevice device) { + AcmService mAcmService = AcmService.getAcmService(); + if(mAcmService == null) { + return false; + } + return mAcmService.isVcpMute(device); + } + + boolean isBroadcastAudioSynced(BluetoothDevice device) { + BCService mBCService = BCService.getBCService(); + if (mBCService == null || device == null) return false; + List srcInfos = + mBCService.getAllBroadcastSourceInformation(device); + if (srcInfos == null || srcInfos.size() == 0) { + Log.e(TAG, "source Infos not available"); + return false; + } + + for (int i=0; i mBassVolMap; + + Context mContext; + private String mAudioTypeStr; + public static final int SAFE_VOL = 7; + public String mVolumeMap; + + DeviceVolume(Context context, String map) { + this.reset(); + mContext = context; + mVolumeMap = map; + mSupportAbsoluteVolume = false; + + if(map == "bluetooth_call_volume_map") { + mAudioTypeStr = "Call"; + } + else if(map == "bluetooth_media_volume_map") { + mAudioTypeStr = "Media"; + } + else { + mAudioTypeStr = "Broadcast"; + mBassVolMap = new ConcurrentHashMap(); + } + + Map allKeys = getVolumeMap().getAll(); + SharedPreferences.Editor pref = getVolumeMap().edit(); + for (Map.Entry entry : allKeys.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + BluetoothDevice d = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(key); + + if (value instanceof Integer && d.getBondState() == BluetoothDevice.BOND_BONDED) { + if (mAudioTypeStr.equals("Broadcast")) { + mBassVolMap.put(key, (Integer) value); + Log.w(TAG, "address " + key + " from the broadcast volume map volume :" + value); + } + } else { + Log.w(TAG, "Removing " + key + " from the " + mAudioTypeStr + " volume map"); + pref.remove(key); + } + } + pref.apply(); + } + + void updateDevice (BluetoothDevice device, int profile) { + mDevice = device; + mProfile = profile; + + mVolume = getSavedVolume(device); + Log.i (TAG, "New " + mAudioTypeStr + " device: " + mDevice + " Vol: " + mVolume); + } + + int getSavedVolume (BluetoothDevice device) { + int mSavedVolume; + SharedPreferences pref = getVolumeMap(); + mSavedVolume = pref.getInt(device.getAddress(), SAFE_VOL); + return mSavedVolume; + } + + void updateVolume (int volume) { + mVolume = volume; + } + + void updateVolume (BluetoothDevice device, int volume) { + if(mAudioTypeStr.equals("Broadcast")) { + Log.i(TAG, "updateVolume, device " + device + " volume: " + volume); + mBassVolMap.put(device.getAddress(), volume); + } + } + int getVolume(BluetoothDevice device) { + if(device == null) { + Log.e (TAG, "Null Device passed"); + return 7; + } + if(mAudioTypeStr.equals("Broadcast")) { + if(mBassVolMap.containsKey(device.getAddress())) { + return mBassVolMap.getOrDefault(device.getAddress(), 7); + } else { + int mSavedVolume = getSavedVolume(device); + mBassVolMap.put(device.getAddress(), mSavedVolume); + Log.i(TAG, "get saved volume, device " + device + " volume: " + mSavedVolume); + return mSavedVolume; + } + } + return 7; + } + private SharedPreferences getVolumeMap() { + return mContext.getSharedPreferences(mVolumeMap, Context.MODE_PRIVATE); + } + + public void saveVolume() { + if(mAudioTypeStr.equals("Broadcast")) { + saveBroadcastVolume(); + return; + } + + if(mDevice == null) { + Log.e (TAG, "saveVolume: No Device Active for " + mAudioTypeStr + ". Ignore"); + return; + } + + SharedPreferences.Editor pref = getVolumeMap().edit(); + pref.putInt(mDevice.getAddress(), mVolume); + pref.apply(); + Log.i (TAG, "Saved " + mAudioTypeStr + " Volume: " + mVolume + " for device: " + mDevice); + } + + public void saveBroadcastVolume() { + SharedPreferences.Editor pref = getVolumeMap().edit(); + for(Map.Entry itr : mBassVolMap.entrySet()) { + pref.putInt(itr.getKey(), itr.getValue()); + } + pref.apply(); + } + + public void saveVolume(BluetoothDevice device) { + if(device == null) { + Log.e (TAG, "Null Device passed"); + return; + } + if(mAudioTypeStr.equals("Broadcast")) { + int mVol = mBassVolMap.getOrDefault(device.getAddress(), 7); + SharedPreferences.Editor pref = getVolumeMap().edit(); + pref.putInt(device.getAddress(), mVol); + pref.apply(); + } + } + + void removeDevice(BluetoothDevice device) { + if(mAudioTypeStr.equals("Broadcast")) { + Log.i (TAG, "Remove device " + device + " from broadcast volume map "); + mBassVolMap.remove(device.getAddress()); + } + SharedPreferences.Editor pref = getVolumeMap().edit(); + pref.remove(device.getAddress()); + pref.apply(); + } + + void reset () { + Log.i (TAG, "Reset " + mAudioTypeStr + " Device: " + mDevice); + mDevice = null; + mVolume = SAFE_VOL; + mProfile = ApmConst.AudioProfiles.NONE; + } + } + + private class VolumeManagerReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if(action == null) + return; + + switch(action) { + case AudioManager.VOLUME_CHANGED_ACTION: + int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); + int volumeValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0); + if(streamType == AudioManager.STREAM_BLUETOOTH_SCO) { + setCallVolume(intent); + } else { + updateMediaStreamVolume(volumeValue); + } + break; + + case BluetoothDevice.ACTION_BOND_STATE_CHANGED: + int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.ERROR); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if(device == null) + return; + + if(state == BluetoothDevice.BOND_NONE) { + handleDeviceUnbond(device); + } + break; + + case BleBroadcastAudioScanAssistManager.ACTION_BROADCAST_SOURCE_INFO: + BleBroadcastSourceInfo sourceInfo = intent.getParcelableExtra( + BleBroadcastSourceInfo.EXTRA_SOURCE_INFO); + device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + + if (device == null || sourceInfo == null) { + Log.w (TAG, "Bluetooth Device or Source info is null"); + break; + } + + if (sourceInfo.getAudioSyncState() == + BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED) { + handleBroadcastAudioSynced(device); + } + break; + + case ACTION_SHUTDOWN: + case ACTION_POWER_OFF: + handleDeviceShutdown(); + break; + } + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BCService.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BCService.java new file mode 100644 index 00000000000..ebcbf81b541 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BCService.java @@ -0,0 +1,1691 @@ +/* + * Copyright (c) 2020 The Linux Foundation. All rights reserved. + * + * Copyright (C) 2016 The Android Open Source Project + * + * 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. + */ + +package com.android.bluetooth.bc; + +import static android.Manifest.permission.BLUETOOTH_CONNECT; + + +import android.app.ActivityManager; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.IBluetoothSyncHelper; +import android.bluetooth.IBluetoothManager; +import android.bluetooth.IBleBroadcastAudioScanAssistCallback; +import android.bluetooth.BluetoothSyncHelper; +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.BleBroadcastSourceChannel; +import android.bluetooth.BleBroadcastAudioScanAssistManager; +import android.bluetooth.BleBroadcastAudioScanAssistCallback; + +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Process; +import android.os.RemoteException; +import android.os.HandlerThread; +import android.util.Log; +import android.os.ParcelUuid; +import android.bluetooth.BluetoothUuid; +import java.util.ArrayList; +import android.os.ServiceManager; + +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.PeriodicAdvertisingCallback; +import android.bluetooth.le.PeriodicAdvertisingManager; +import android.bluetooth.le.PeriodicAdvertisingReport; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; + +import com.android.bluetooth.BluetoothMetricsProto; +import com.android.bluetooth.Utils; +import com.android.bluetooth.btservice.MetricsLogger; +import com.android.bluetooth.btservice.ProfileService; +import com.android.internal.annotations.VisibleForTesting; +import com.android.bluetooth.btservice.AdapterService; +//*_CSIP +//CSIP related imports +import com.android.bluetooth.groupclient.GroupService; +import android.bluetooth.BluetoothGroupCallback; +import android.bluetooth.DeviceGroup; +//_CSIP*/ + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.UUID; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.Objects; +import java.util.NoSuchElementException; +import android.os.SystemProperties; + + +import com.android.internal.util.ArrayUtils; +/** @hide */ +public class BCService extends ProfileService { + private static final boolean DBG = true; + private static final String TAG = BCService.class.getSimpleName(); + + private static final ParcelUuid CAS_UUID = null;//ParcelUuid.fromString("000089FF-0000-1000-8000-00805F9B34FB"); + + public static final String BC_ID = "0000184F-0000-1000-8000-00805F9B34FB"; + public static final String BS_ID = "00001852-0000-1000-8000-00805F9B34FB"; + private static BCService sBCService; + private static final int MAX_BASS_CLIENT_STATE_MACHINES = 10; + private static final int MAX_BASS_CLIENT_CSET_MEMBERS = 10; + private final Map mStateMachines = + new HashMap<>(); + private HandlerThread mStateMachinesThread; + private final Map mSetManagers = + new HashMap<>(); + private HandlerThread mSetManagerThread; + + private AdapterService mAdapterService; + + private Map> mAppCallbackMap = + new HashMap>(); + + private BassUtils bassUtils = null; + public static final int INVALID_SYNC_HANDLE = -1; + public static final int INVALID_ADV_SID = -1; + public static final int INVALID_ADV_ADDRESS_TYPE = -1; + public static final int INVALID_ADV_INTERVAL = -1; + public static final int INVALID_BROADCAST_ID = -1; + private Map mActiveSourceMap; + + //*_CSIP + //CSET interfaces + private GroupService mSetCoordinator = GroupService.getGroupService(); + public int mCsipAppId = -1; + private int mQueuedOps = 0; + //_CSIP*/ + + /*Caching the PAresults from Broadcast source*/ + /*This is stored at service so that each device state machine can access + and use it as needed. Once the periodic sync in cancelled, this data will bre + removed to ensure stable data won't used*/ + /*broadcastSrcDevice, syncHandle*/ + private Map mSyncHandleMap; + /*syncHandle, parsed BaseData data*/ + private Map mSyncHandleVsBaseInfo; + /*bcastSrcDevice, corresponding PAResultsMap*/ + private Map mPAResultsMap; + public class PAResults { + public BluetoothDevice mDevice; + public int mAddressType; + public int mAdvSid; + public int mSyncHandle; + public byte metaDataLength; + public byte[] metaData; + public int mPAInterval; + public int mBroadcastId; + + PAResults(BluetoothDevice device, int addressType, + int syncHandle, int advSid, int paInterval, int broadcastId) { + mDevice = device; + mAddressType = addressType; + mAdvSid = advSid; + mSyncHandle = syncHandle; + mPAInterval = paInterval; + mBroadcastId = broadcastId; + } + + public void updateSyncHandle(int syncHandle) { + mSyncHandle = syncHandle; + } + + public void updateAdvSid(int advSid) { + mAdvSid = advSid; + } + + public void updateAddressType(int addressType) { + mAddressType = addressType; + } + + public void updateAdvInterval(int advInterval) { + mPAInterval = advInterval; + } + + public void updateBroadcastId(int broadcastId) { + mBroadcastId = broadcastId; + } + + public void print() { + log("-- PAResults --"); + log("mDevice:" + mDevice); + log("mAddressType:" + mAddressType); + log("mAdvSid:" + mAdvSid); + log("mSyncHandle:" + mSyncHandle); + log("mPAInterval:" + mPAInterval); + log("mBroadcastId:" + mBroadcastId); + log("-- END: PAResults --"); + } + }; + + public void updatePAResultsMap(BluetoothDevice device, int addressType, int syncHandle, int advSid, int advInterval, int bId) { + log("updatePAResultsMap: device: " + device); + log("updatePAResultsMap: syncHandle: " + syncHandle); + log("updatePAResultsMap: advSid: " + advSid); + log("updatePAResultsMap: addressType: " + addressType); + log("updatePAResultsMap: advInterval: " + advInterval); + log("updatePAResultsMap: broadcastId: " + bId); + log("mSyncHandleMap" + mSyncHandleMap); + log("mPAResultsMap" + mPAResultsMap); + //Cache the SyncHandle + if (mSyncHandleMap != null) { + Integer i = new Integer(syncHandle); + mSyncHandleMap.put(device, i); + } + if (mPAResultsMap != null) { + PAResults paRes = mPAResultsMap.get(device); + if (paRes == null) { + log("PAResmap: add >>>"); + paRes = new PAResults (device, addressType, + syncHandle, advSid, advInterval, bId); + if (paRes != null) { + paRes.print(); + mPAResultsMap.put(device, paRes); + } + } else { + if (advSid != INVALID_ADV_SID) { + paRes.updateAdvSid(advSid); + } + if (syncHandle != INVALID_SYNC_HANDLE) { + paRes.updateSyncHandle(syncHandle); + } + if (addressType != INVALID_ADV_ADDRESS_TYPE) { + paRes.updateAddressType(addressType); + } + if (advInterval != INVALID_ADV_INTERVAL) { + paRes.updateAdvInterval(advInterval); + } + if (bId != INVALID_BROADCAST_ID) { + paRes.updateBroadcastId(bId); + } + log("PAResmap: update >>>"); + paRes.print(); + mPAResultsMap.replace(device, paRes); + } + } + log(">>mPAResultsMap" + mPAResultsMap); + } + + public PAResults getPAResults(BluetoothDevice device) { + PAResults res = null; + if (mPAResultsMap != null) { + res = mPAResultsMap.get(device); + } else { + Log.e(TAG, "getPAResults: mPAResultsMap is null"); + } + return res; + } + public PAResults clearPAResults(BluetoothDevice device) { + PAResults res = null; + if (mPAResultsMap != null) { + res = mPAResultsMap.remove(device); + } else { + Log.e(TAG, "getPAResults: mPAResultsMap is null"); + } + return res; + } + + public void updateBASE(int syncHandlemap, BaseData base) { + if (mSyncHandleVsBaseInfo != null) { + log("updateBASE : mSyncHandleVsBaseInfo>>"); + mSyncHandleVsBaseInfo.put(syncHandlemap, base); + } else { + Log.e(TAG, "updateBASE: mSyncHandleVsBaseInfo is null"); + } + } + + public BaseData getBASE(int syncHandlemap) { + BaseData base = null; + if (mSyncHandleVsBaseInfo != null) { + log("getBASE : syncHandlemap::" + syncHandlemap); + base = mSyncHandleVsBaseInfo.get(syncHandlemap); + } else { + Log.e(TAG, "getBASE: mSyncHandleVsBaseInfo is null"); + } + log("getBASE returns" + base); + return base; + } + + public void clearBASE(int syncHandlemap) { + if (mSyncHandleVsBaseInfo != null) { + log("clearBASE : mSyncHandleVsBaseInfo>>"); + mSyncHandleVsBaseInfo.remove(syncHandlemap); + } else { + Log.e(TAG, "updateBASE: mSyncHandleVsBaseInfo is null"); + } + } + + public void setActiveSyncedSource(BluetoothDevice scanDelegator, BluetoothDevice sourceDevice) { + log("setActiveSyncedSource: scanDelegator" + scanDelegator + ":: sourceDevice:" + sourceDevice); + if (sourceDevice == null) { + mActiveSourceMap.remove(scanDelegator); + } else { + mActiveSourceMap.put(scanDelegator, sourceDevice); + } + } + + public BluetoothDevice getActiveSyncedSource(BluetoothDevice scanDelegator) { + BluetoothDevice currentSource = mActiveSourceMap.get(scanDelegator); + log("getActiveSyncedSource: scanDelegator" + scanDelegator + "returning " + currentSource); + return currentSource; + } + + @Override + protected IProfileServiceBinder initBinder() { + return new BluetoothSyncHelperBinder(this); + } + + //*_CSIP + private BluetoothGroupCallback mBluetoothGroupCallback = new BluetoothGroupCallback() { + public void onGroupClientAppRegistered(int status, int appId) { + log("onCsipAppRegistered:" + status + "appId: " + appId); + if (status == 0) { + mCsipAppId = appId; + } else { + Log.e(TAG, "Csip registeration failed, status:" + status); + } + } + + public void onConnectionStateChanged (int state, BluetoothDevice device) { + log("onConnectionStateChanged: Device: " + device + "state: " + state); + //notify the statemachine about CSIP connection + synchronized (mStateMachines) { + BassClientStateMachine stateMachine = getOrCreateStateMachine(device); + Message m = stateMachine.obtainMessage(BassClientStateMachine.CSIP_CONNECTION_STATE_CHANGED); + m.obj = state; + stateMachine.sendMessage(m); + } + } + + public void onNewGroupFound (int setId, BluetoothDevice device, UUID uuid) { } + public void onGroupDiscoveryStatusChanged (int setId, int status, int reason) { } + public void onGroupDeviceFound (int setId, BluetoothDevice device) { } + public void onExclusiveAccessChanged (int setId, int value, int status, List devices) { + log("onLockStatusChanged: setId" + setId + devices + "status:" + status); + BassCsetManager setMgr = null; + setMgr = getOrCreateCSetManager(setId, null); + if (setMgr == null) { + return; + } + log ("sending Lock status to setId:" + setId); + Message m = setMgr.obtainMessage(BassCsetManager.LOCK_STATE_CHANGED); + m.obj = devices; + m.arg1 = value; + setMgr.sendMessage(m); + } + public void onExclusiveAccessStatusFetched (int setId, int lockStatus) { } + public void onExclusiveAccessAvailable (int setId, BluetoothDevice device) { } + }; + //_CSIP*/ + + @Override + protected boolean start() { + if (DBG) { + Log.d(TAG, "start()"); + } + mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), + "AdapterService cannot be null when BCService starts"); + mStateMachines.clear(); + mStateMachinesThread = new HandlerThread("BCService.StateMachines"); + mStateMachinesThread.start(); + + mSetManagers.clear(); + mSetManagerThread = new HandlerThread("BCService.SetManagers"); + mSetManagerThread.start(); + + setBCService(this); + bassUtils = new BassUtils(this); + //Saving PSync stuff for future addition + mSyncHandleMap = new HashMap(); + mPAResultsMap = new HashMap(); + mSyncHandleVsBaseInfo = new HashMap(); + mActiveSourceMap = new HashMap(); + + //*_CSIP + //CSET initialization + mSetCoordinator = GroupService.getGroupService(); + if (mSetCoordinator != null) { + mSetCoordinator.registerGroupClientModule(mBluetoothGroupCallback); + } + //_CSIP*/ + /*_PACS + mPacsClientService = PacsClientService.getPacsClientService(); + _PACS*/ + + ///*_GAP + //GAP registeration for Bass UUID notification + if (mAdapterService != null) { + log("register for BASS UUID notif"); + ParcelUuid bassUuid = new ParcelUuid(BassClientStateMachine.BASS_UUID); + mAdapterService.registerUuidSrvcDisc(bassUuid); + } + //_GAP*/ + return true; + } + + @Override + protected boolean stop() { + if (DBG) { + Log.d(TAG, "stop()"); + } + + synchronized (mStateMachines) { + for (BassClientStateMachine sm : mStateMachines.values()) { + sm.doQuit(); + sm.cleanup(); + } + mStateMachines.clear(); + } + + if (mStateMachinesThread != null) { + mStateMachinesThread.quitSafely(); + mStateMachinesThread = null; + } + + if (mSetManagerThread != null) { + mSetManagerThread.quitSafely(); + mSetManagerThread = null; + } + + setBCService(null); + + if (mAppCallbackMap != null) { + mAppCallbackMap.clear(); + mAppCallbackMap = null; + } + + if (mSyncHandleMap != null) { + mSyncHandleMap.clear(); + mSyncHandleMap = null; + } + + if (mActiveSourceMap != null) { + mActiveSourceMap.clear(); + mActiveSourceMap = null; + } + //*_CSIP + if (mSetCoordinator != null && mCsipAppId != -1) { + //mSetCoordinator.unregisterGroupClientModule(mCsipAppId); + } + //_CSIP*/ + return true; + } + + @Override + public boolean onUnbind(Intent intent) { + Log.d(TAG, "Need to unregister app"); + //unregisterApp(); + return super.onUnbind(intent); + } + + /** + * Get the BCService instance + * @return BCService instance + */ + public static synchronized BCService getBCService() { + if (sBCService == null) { + Log.w(TAG, "getBCService(): service is NULL"); + return null; + } + + if (!sBCService.isAvailable()) { + Log.w(TAG, "getBCService(): service is not available"); + return null; + } + return sBCService; + } + + public BassUtils getBassUtils() { + return bassUtils; + } + + public BluetoothDevice getDeviceForSyncHandle(int syncHandle) { + BluetoothDevice dev = null; + if (mSyncHandleMap != null) { + for (Map.Entry entry : mSyncHandleMap.entrySet()) { + Integer value = entry.getValue(); + if (value == syncHandle) { + dev = entry.getKey(); + } + } + } + return dev; + } + + private static synchronized void setBCService(BCService instance) { + if (DBG) { + Log.d(TAG, "setBCService(): set to: " + instance); + } + sBCService = instance; + } + + /** + * Connects the bass profile to the passed in device + * + * @param device is the device with which we will connect the Bass profile + * @return true if BAss profile successfully connected, false otherwise + */ + public boolean connect(BluetoothDevice device) { + if (DBG) { + Log.d(TAG, "connect(): " + device); + } + if (device == null) { + return false; + } + + if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_UNKNOWN) { + return false; + } + synchronized (mStateMachines) { + BassClientStateMachine stateMachine = getOrCreateStateMachine(device); + + stateMachine.sendMessage(BassClientStateMachine.CONNECT); + } + return true; + } + + /** + * Disconnects Bassclient profile for the passed in device + * + * @param device is the device with which we want to disconnected the BAss client profile + * @return true if Bass client profile successfully disconnected, false otherwise + */ + public boolean disconnect(BluetoothDevice device) { + + if (DBG) { + Log.d(TAG, "disconnect(): " + device); + } + if (device == null) { + return false; + } + + synchronized (mStateMachines) { + BassClientStateMachine stateMachine = getOrCreateStateMachine(device); + + stateMachine.sendMessage(BassClientStateMachine.DISCONNECT); + } + return true; + } + + List getConnectedDevices() { + + synchronized (mStateMachines) { + List devices = new ArrayList<>(); + for (BassClientStateMachine sm : mStateMachines.values()) { + if (sm.isConnected()) { + devices.add(sm.getDevice()); + } + } + log("getConnectedDevices: " + devices); + return devices; + } + } + + /** + * Check whether can connect to a peer device. + * The check considers a number of factors during the evaluation. + * + * @param device the peer device to connect to + * @return true if connection is allowed, otherwise false + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean okToConnect(BluetoothDevice device) { + // Check if this is an incoming connection in Quiet mode. + if (mAdapterService.isQuietModeEnabled()) { + Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled"); + return false; + } + // Check connection policy and accept or reject the connection. + int connectionPolicy = getConnectionPolicy(device); + int bondState = mAdapterService.getBondState(device); + // Allow this connection only if the device is bonded. Any attempt to connect while + // bonding would potentially lead to an unauthorized connection. + if (bondState != BluetoothDevice.BOND_BONDED) { + Log.w(TAG, "okToConnect: return false, bondState=" + bondState); + return false; + } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN + && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + // Otherwise, reject the connection if connectionPolicy is not valid. + Log.w(TAG, "okToConnect: return false, connectionPolicy=" + connectionPolicy); + return false; + } + return true; + } + + List getDevicesMatchingConnectionStates(int[] states) { + + ArrayList devices = new ArrayList<>(); + if (states == null) { + return devices; + } + final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); + if (bondedDevices == null) { + return devices; + } + synchronized (mStateMachines) { + for (BluetoothDevice device : bondedDevices) { + final ParcelUuid[] featureUuids = device.getUuids(); + if (!ArrayUtils.contains(featureUuids, new ParcelUuid(BassClientStateMachine.BASS_UUID))) { + continue; + } + int connectionState = BluetoothProfile.STATE_DISCONNECTED; + BassClientStateMachine sm = getOrCreateStateMachine(device); + if (sm != null) { + connectionState = sm.getConnectionState(); + } + for (int state : states) { + if (connectionState == state) { + devices.add(device); + break; + } + } + } + return devices; + } + } + + public int getConnectionState(BluetoothDevice device) { + synchronized (mStateMachines) { + BassClientStateMachine sm = getOrCreateStateMachine(device); + if (sm == null) { + log("getConnectionState returns STATE_DISC"); + return BluetoothProfile.STATE_DISCONNECTED; + } + return sm.getConnectionState(); + } + } + + /** + * Set the connectionPolicy of the Hearing Aid profile. + * + * @param device the remote device + * @param connectionPolicy the connection policy of the profile + * @return true on success, otherwise false + */ + public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { + + if (DBG) { + Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy); + } + boolean setSuccessfully; + setSuccessfully = mAdapterService.getDatabase() + .setProfileConnectionPolicy(device, BluetoothProfile.BC_PROFILE, connectionPolicy); + if (setSuccessfully && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + connect(device); + } else if (setSuccessfully + && connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { + disconnect(device); + } + return setSuccessfully; + } + + /** + * Get the connection policy of the profile. + * + *

The connection policy can be any of: + * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, + * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, + * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} + * + * @param device Bluetooth device + * @return connection policy of the device + * @hide + */ + public int getConnectionPolicy(BluetoothDevice device) { + + return mAdapterService.getDatabase() + .getProfileConnectionPolicy(device, BluetoothProfile.BC_PROFILE); + } + + public void sendBroadcastSourceSelectedCallback(BluetoothDevice device, List bChannels, int status){ + ArrayList cbs = mAppCallbackMap.get(device); + if (cbs == null) { + Log.e(TAG, "no App callback for this device" + device); + return; + } + for (IBleBroadcastAudioScanAssistCallback cb : cbs) { + try { + cb.onBleBroadcastAudioSourceSelected(device, status, bChannels); + } catch (RemoteException e) { + Log.e(TAG, "Exception while calling sendBroadcastSourceSelectedCallback"); + } + } + } + + public void sendAddBroadcastSourceCallback(BluetoothDevice device, byte srcId, int status){ + ArrayList cbs = mAppCallbackMap.get(device); + if (cbs == null) { + Log.e(TAG, "no App callback for this device" + device); + return; + } + for (IBleBroadcastAudioScanAssistCallback cb : cbs) { + try { + cb.onBleBroadcastAudioSourceAdded(device, srcId, status); + } catch (RemoteException e) { + Log.e(TAG, "Exception while calling onBleBroadcastAudioSourceAdded"); + } + } + } + + public void sendUpdateBroadcastSourceCallback(BluetoothDevice device, byte sourceId, int status){ + ArrayList cbs = mAppCallbackMap.get(device); + if (cbs == null) { + Log.e(TAG, "no App callback for this device" + device); + return; + } + + for (IBleBroadcastAudioScanAssistCallback cb : cbs) { + try { + cb.onBleBroadcastAudioSourceUpdated(device, sourceId, status); + } catch (RemoteException e) { + Log.e(TAG, "Exception while calling onBleBroadcastAudioSourceUpdated"); + } + } + } + public void sendRemoveBroadcastSourceCallback(BluetoothDevice device, byte sourceId, int status){ + ArrayList cbs = mAppCallbackMap.get(device); + if (cbs == null) { + Log.e(TAG, "no App callback for this device" + device); + return; + } + + for (IBleBroadcastAudioScanAssistCallback cb : cbs) { + try { + cb.onBleBroadcastAudioSourceRemoved(device, sourceId, status); + } catch (RemoteException e) { + Log.e(TAG, "Exception while calling onBleBroadcastAudioSourceRemoved"); + } + } + } + public void sendSetBroadcastPINupdatedCallback(BluetoothDevice device, byte sourceId, int status){ + ArrayList cbs = mAppCallbackMap.get(device); + if (cbs == null) { + Log.e(TAG, "no App callback for this device" + device); + return; + } + + for (IBleBroadcastAudioScanAssistCallback cb : cbs) { + try { + cb.onBleBroadcastPinUpdated(device, sourceId, status); + } catch (RemoteException e) { + Log.e(TAG, "Exception while calling onBleBroadcastPinUpdated"); + } + } + } + + public void registerAppCallback (BluetoothDevice device, IBleBroadcastAudioScanAssistCallback cb) { + + Log.i(TAG, "registerAppCallback" + device); + + ArrayList cbs = mAppCallbackMap.get(device); + if (cbs == null) { + Log.i(TAG, "registerAppCallback: entry exists"); + cbs = new ArrayList(); + } + cbs.add(cb); + mAppCallbackMap.put(device, cbs); + return; + } + + public void unregisterAppCallback (BluetoothDevice device, IBleBroadcastAudioScanAssistCallback cb) { + + Log.i(TAG, "unregisterAppCallback" + device); + + ArrayList cbs = mAppCallbackMap.get(device); + if (cbs == null) { + Log.i(TAG, "unregisterAppCallback: cb list is null"); + return; + } else { + boolean ret = cbs.remove(cb); + Log.i(TAG, "unregisterAppCallback: ret value of removal from list:" + ret); + } + if (cbs.size() != 0) { + mAppCallbackMap.replace(device, cbs); + } else { + Log.i(TAG, "unregisterAppCallback: Remove the cmplete entry"); + mAppCallbackMap.remove(device); + } + return; + } + + public boolean searchforLeAudioBroadcasters (BluetoothDevice device) { + + Log.i(TAG, "searchforLeAudioBroadcasters on behalf of" + device); + ArrayList cbs = mAppCallbackMap.get(device); + if (cbs == null) { + Log.e(TAG, "no App callback for this device" + device); + return false; + } + boolean ret = false; + if (bassUtils != null) { + ret = bassUtils.searchforLeAudioBroadcasters(device, cbs); + } else { + Log.e(TAG, "searchforLeAudioBroadcasters :Null Bass Util Handle" + device); + ret = false; + } + return ret; + } + + public boolean stopSearchforLeAudioBroadcasters (BluetoothDevice device) { + + Log.i(TAG, "stopsearchforLeAudioBroadcasters on behalf of" + device); + ArrayList cbs = mAppCallbackMap.get(device); + if (cbs == null) { + Log.e(TAG, "no App callback for this device" + device); + } + boolean ret = false; + if (bassUtils != null) { + ret = bassUtils.stopSearchforLeAudioBroadcasters(device, cbs); + } else { + Log.e(TAG, "stopsearchforLeAudioBroadcasters :Null Bass Util Handle" + device); + ret = false; + } + return ret; + } + + public boolean selectBroadcastSource (BluetoothDevice device, ScanResult scanRes, boolean isGroupOp, boolean auto) { + + Log.i(TAG, "selectBroadcastSource for " + device + "isGroupOp:" + isGroupOp); + Log.i(TAG, "ScanResult " + scanRes); + + if (scanRes == null) { + Log.e(TAG, "selectBroadcastSource: null Scan results"); + return false; + } + List listOfDevices = new ArrayList(); + listOfDevices.add(device); + if (isRoomForBroadcastSourceAddition(listOfDevices) == false) { + sendBroadcastSourceSelectedCallback(device, null, + BleBroadcastAudioScanAssistCallback.BASS_STATUS_NO_EMPTY_SLOT); + return false; + } + //dummy BleSourceInfo from scanRes + BleBroadcastSourceInfo scanResSI = new BleBroadcastSourceInfo (scanRes.getDevice(), + BassClientStateMachine.INVALID_SRC_ID, + (byte)scanRes.getAdvertisingSid(), + BleBroadcastSourceInfo.BROADCASTER_ID_INVALID, + scanRes.getAddressType(), + BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_INVALID, + BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_INVALID, + null, + (byte)0, + BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED, + null, + null); + if (isValidBroadcastSourceAddition(listOfDevices, scanResSI) == false) { + sendBroadcastSourceSelectedCallback(device, + null, BleBroadcastAudioScanAssistCallback.BASS_STATUS_DUPLICATE_ADDITION); + return false; + } + startScanOffloadInternal(device, isGroupOp); + synchronized (mStateMachines) { + BassClientStateMachine sm = getOrCreateStateMachine(device); + if (sm == null) { + return false; + } + Message m = sm.obtainMessage(BassClientStateMachine.SELECT_BCAST_SOURCE); + m.obj = scanRes; + if (auto) { + m.arg1 = sm.AUTO; + } else { + m.arg1 = sm.USER; + } + if (isGroupOp) { + m.arg2 = sm.GROUP_OP; + } else { + m.arg2 = sm.NON_GROUP_OP; + } + sm.sendMessage(m); + } + return true; + } + + public synchronized void notifyOperationCompletion(BluetoothDevice device, int pendingOperation) { + log("notifyOperationCompletion: " + device + "pendingOperation: " + + BassClientStateMachine.messageWhatToString(pendingOperation)); + //synchronized (mStateMachines) { + switch (pendingOperation) { + case BassClientStateMachine.START_SCAN_OFFLOAD: + case BassClientStateMachine.STOP_SCAN_OFFLOAD: + case BassClientStateMachine.ADD_BCAST_SOURCE: + case BassClientStateMachine.UPDATE_BCAST_SOURCE: + case BassClientStateMachine.REMOVE_BCAST_SOURCE: + case BassClientStateMachine.SET_BCAST_CODE: + if (mQueuedOps > 0) { + mQueuedOps = mQueuedOps - 1; + } else { + log("not a queued op, Internal op"); + return; + } + break; + default: + { + log("notifyOperationCompletion: unhandled case"); + return; + } + } + //} + if (mQueuedOps == 0) { + log("notifyOperationCompletion: all ops are done!"); + //trigger unlock with last device + triggerUnlockforCSet(device); + } + + } + + public synchronized boolean startScanOffload (BluetoothDevice masterDevice, List devices) { + + Log.i(TAG, "startScanOffload for " + devices); + for (BluetoothDevice dev : devices) { + BassClientStateMachine stateMachine = getOrCreateStateMachine(dev); + if (stateMachine == null) { + continue; + } + stateMachine.sendMessage(BassClientStateMachine.START_SCAN_OFFLOAD); + mQueuedOps = mQueuedOps + 1; + } + return true; + } + + public synchronized boolean stopScanOffload (BluetoothDevice masterDevice, List devices) { + + Log.i(TAG, "stopScanOffload for " + devices); + for (BluetoothDevice dev : devices) { + BassClientStateMachine stateMachine = getOrCreateStateMachine(dev); + if (stateMachine == null) { + continue; + } + stateMachine.sendMessage(BassClientStateMachine.STOP_SCAN_OFFLOAD); + mQueuedOps = mQueuedOps + 1; + } + + return true; + } + + public boolean isLocalBroadcasting() { + return bassUtils.isLocalLEAudioBroadcasting(); + } + + private boolean isValidBroadcastSourceAddition(List devices, + BleBroadcastSourceInfo srcInfo) { + boolean ret = true; + + //run through all the device, if it is not valid + //to even one device to add this source, return failure + for (BluetoothDevice dev : devices) { + List currentSourceInfos = + getAllBroadcastSourceInformation(dev); + if (currentSourceInfos == null) { + log("currentSourceInfos is null for " + dev); + continue; + } + for (int i=0; i devices) { + boolean isRoomAvail = false; + + //run through all the device, if it is not valid + //to even one device to add this source, return failure + for (BluetoothDevice dev : devices) { + isRoomAvail = false; + List currentSourceInfos = + getAllBroadcastSourceInformation(dev); + for (int i=0; i devices, BleBroadcastSourceInfo srcInfo + ) { + + Log.i(TAG, "addBroadcastSource for " + devices + + "SourceInfo " + srcInfo); + if (srcInfo == null) { + Log.e(TAG, "addBroadcastSource: null SrcInfo"); + return false; + } + if (isRoomForBroadcastSourceAddition(devices) == false) { + sendAddBroadcastSourceCallback(masterDevice, + BassClientStateMachine.INVALID_SRC_ID, BleBroadcastAudioScanAssistCallback.BASS_STATUS_NO_EMPTY_SLOT); + triggerUnlockforCSet(masterDevice); + return false; + } + + if (isValidBroadcastSourceAddition(devices, srcInfo) == false) { + sendAddBroadcastSourceCallback(masterDevice, + BassClientStateMachine.INVALID_SRC_ID, BleBroadcastAudioScanAssistCallback.BASS_STATUS_DUPLICATE_ADDITION); + triggerUnlockforCSet(masterDevice); + return false; + } + for (BluetoothDevice dev : devices) { + BassClientStateMachine stateMachine = getOrCreateStateMachine(dev); + if (stateMachine == null) { + Log.w(TAG, "addBroadcastSource: device seem to be not avaiable, proceed"); + continue; + } + Message m = stateMachine.obtainMessage(BassClientStateMachine.ADD_BCAST_SOURCE); + m.obj = srcInfo; + stateMachine.sendMessage(m); + mQueuedOps = mQueuedOps + 1; + } + return true; + } + + private byte getSrcIdForCSMember(BluetoothDevice masterDevice, BluetoothDevice memberDevice, byte masterSrcId) { + byte targetSrcId = -1; + List masterSrcInfos = getAllBroadcastSourceInformation(masterDevice); + List memberSrcInfos = getAllBroadcastSourceInformation(memberDevice); + if (masterSrcInfos == null || masterSrcInfos.size() == 0 || + memberSrcInfos == null || memberSrcInfos.size() == 0) { + Log.e(TAG, "master or member source Infos not available"); + return targetSrcId; + } + if (masterDevice.equals(memberDevice)) { + log("master: " + masterDevice + "member:memberDevice"); + return masterSrcId; + } + BluetoothDevice masterSrcDevice = null; + for (int i=0; i devices, + BleBroadcastSourceInfo srcInfo + ) { + + int status = BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_ID; + Log.i(TAG, "updateBroadcastSource for " + devices + + "masterDevice " + masterDevice + + "SourceInfo " + srcInfo); + + if (srcInfo == null) { + Log.e(TAG, "updateBroadcastSource: null SrcInfo"); + return false; + } + + for (BluetoothDevice dev : devices) { + if (getSrcIdForCSMember(masterDevice, dev, srcInfo.getSourceId()) == -1) { + if (devices.size() > 1) { + status = BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_GROUP_OP; + } else { + status = BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_ID; + } + sendRemoveBroadcastSourceCallback(masterDevice, BassClientStateMachine.INVALID_SRC_ID, + status); + triggerUnlockforCSet(masterDevice); + return false; + } + } + + for (BluetoothDevice dev : devices) { + BassClientStateMachine stateMachine = getOrCreateStateMachine(dev); + if (stateMachine == null) { + Log.w(TAG, "updateBroadcastSource: Device seem to be not avaiable"); + continue; + } + byte targetSrcId = getSrcIdForCSMember(masterDevice, dev, srcInfo.getSourceId()); + srcInfo.setSourceId(targetSrcId); + + Message m = stateMachine.obtainMessage(BassClientStateMachine.UPDATE_BCAST_SOURCE); + m.obj = srcInfo; + m.arg1 = stateMachine.USER; + stateMachine.sendMessage(m); + mQueuedOps = mQueuedOps + 1; + } + + return true; + } + + public synchronized boolean setBroadcastCode (BluetoothDevice masterDevice, List devices, + BleBroadcastSourceInfo srcInfo + ) { + + int status = BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_ID; + Log.i(TAG, "setBroadcastCode for " + devices + + "masterDevice" + masterDevice + + "Broadcast PIN" + srcInfo.getBroadcastCode()); + + if (srcInfo == null) { + Log.e(TAG, "setBroadcastCode: null SrcInfo"); + return false; + } + + for (BluetoothDevice dev : devices) { + if (getSrcIdForCSMember(masterDevice, dev, srcInfo.getSourceId()) == -1) { + if (devices.size() > 1) { + status = BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_GROUP_OP; + } else { + status = BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_ID; + } + sendRemoveBroadcastSourceCallback(masterDevice, BassClientStateMachine.INVALID_SRC_ID, + status); + triggerUnlockforCSet(masterDevice); + return false; + } + } + + for (BluetoothDevice dev : devices) { + BassClientStateMachine stateMachine = getOrCreateStateMachine(dev); + if (stateMachine == null) { + Log.w(TAG, "setBroadcastCode: Device seem to be not avaiable"); + continue; + } + + byte targetSrcId = getSrcIdForCSMember(masterDevice, dev, srcInfo.getSourceId()); + srcInfo.setSourceId(targetSrcId); + + Message m = stateMachine.obtainMessage(BassClientStateMachine.SET_BCAST_CODE); + m.obj = srcInfo; + m.arg1 = stateMachine.FRESH; + stateMachine.sendMessage(m); + mQueuedOps = mQueuedOps + 1; + } + + return true; + } + + public synchronized boolean removeBroadcastSource (BluetoothDevice masterDevice, List devices, + byte sourceId + ) { + + int status = BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_ID; + Log.i(TAG, "removeBroadcastSource for " + devices + + "masterDevice " + masterDevice + + "removeBroadcastSource: sourceId:" + sourceId); + + if (sourceId == BassClientStateMachine.INVALID_SRC_ID) { + Log.e(TAG, "removeBroadcastSource: Invalid source Id"); + return false; + } + + + for (BluetoothDevice dev : devices) { + if (getSrcIdForCSMember(masterDevice, dev, sourceId) == -1) { + if (devices.size() > 1) { + status = BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_GROUP_OP; + } else { + status = BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_ID; + } + sendRemoveBroadcastSourceCallback(masterDevice, BassClientStateMachine.INVALID_SRC_ID, + status); + triggerUnlockforCSet(masterDevice); + return false; + } + } + + for (BluetoothDevice dev : devices) { + BassClientStateMachine stateMachine = getOrCreateStateMachine(dev); + if (stateMachine == null) { + Log.w(TAG, "setBroadcastCode: Device seem to be not avaiable"); + continue; + } + + Message m = stateMachine.obtainMessage(BassClientStateMachine.REMOVE_BCAST_SOURCE); + m.arg1 = getSrcIdForCSMember(masterDevice, dev, sourceId); + log("removeBroadcastSource: send message to SM " + dev); + stateMachine.sendMessage(m); + mQueuedOps = mQueuedOps + 1; + } + + return true; + } + + void triggerUnlockforCSet (BluetoothDevice device) { + //get setId + int setId = getCsetId(device); + BassCsetManager setMgr = getOrCreateCSetManager(setId, device); + if (setMgr == null) { + Log.e(TAG, "triggerUnlockforCSet: setMgr is NULL"); + return; + } + //Sending UnLock to + log ("sending Unlock to device:" + device); + Message m = setMgr.obtainMessage(BassCsetManager.UNLOCK); + setMgr.sendMessage(m); + } + public List getAllBroadcastSourceInformation (BluetoothDevice device + ) { + Log.i(TAG, "getAllBroadcastSourceInformation for " + device); + synchronized (mStateMachines) { + BassClientStateMachine sm = getOrCreateStateMachine(device); + if (sm == null) { + return null; + } + return sm.getAllBroadcastSourceInformation(); + } + } + + private BassClientStateMachine getOrCreateStateMachine(BluetoothDevice device) { + if (device == null) { + Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null"); + return null; + } + synchronized (mStateMachines) { + BassClientStateMachine sm = mStateMachines.get(device); + if (sm != null) { + return sm; + } + // Limit the maximum number of state machines to avoid DoS attack + if (mStateMachines.size() >= MAX_BASS_CLIENT_STATE_MACHINES) { + Log.e(TAG, "Maximum number of Bassclient state machines reached: " + + MAX_BASS_CLIENT_STATE_MACHINES); + return null; + } + if (DBG) { + Log.d(TAG, "Creating a new state machine for " + device); + } + sm = BassClientStateMachine.make(device, this, + mStateMachinesThread.getLooper()); + mStateMachines.put(device, sm); + return sm; + } + } + + private BassCsetManager getOrCreateCSetManager(int setId, BluetoothDevice masterDevice) { + if (setId == -1) { + Log.e(TAG, "getOrCreateCSetManager failed: invalid setId"); + return null; + } + synchronized (mSetManagers) { + BassCsetManager sm = mSetManagers.get(setId); + log("getOrCreateCSetManager: hashmap Entry:" + sm); + if (sm != null) { + return sm; + } + // Limit the maximum number of set manager state machines + if (mStateMachines.size() >= MAX_BASS_CLIENT_CSET_MEMBERS) { + Log.e(TAG, "Maximum number of Bassclient cset members reached: " + + MAX_BASS_CLIENT_CSET_MEMBERS); + return null; + } + if (DBG) { + Log.d(TAG, "Creating a new set Manager for " + setId); + } + sm = BassCsetManager.make(setId, masterDevice, this, + mSetManagerThread.getLooper()); + mSetManagers.put(setId, sm); + return sm; + } + } + + public boolean isLockSupportAvailable(BluetoothDevice device) { + boolean isLockAvail = false; + boolean forceNoCsip = SystemProperties.getBoolean("persist.vendor.service.bt.forceNoCsip", false); + if (forceNoCsip) { + log("forceNoCsip is set"); + return isLockAvail; + } + //*_CSIP + isLockAvail = mAdapterService.isGroupExclAccessSupport(device); + //_CSIP*/ + + log("isLockSupportAvailable for:" + device + "returns " + isLockAvail); + return isLockAvail; + } + + private int getCsetId(BluetoothDevice device) { + int setId = 1; + //*_CSIP + setId = mSetCoordinator.getRemoteDeviceGroupId(device, CAS_UUID); + //_CSIP*/ + log("getCsetId return:" + setId); + return setId; + } + public boolean stopScanOffloadInternal (BluetoothDevice device, boolean isGroupOp) { + boolean ret = false; + log("stopScanOffloadInternal: device: " + device + + "isGroupOp" + isGroupOp); + /* Even If the request is for Grouoop, If Lock support not avaiable + * for that device, go ahead and treat this as single device operation + */ + if (isGroupOp && isLockSupportAvailable(device) == true) { + int setId = getCsetId(device); + synchronized (mSetManagers) { + BassCsetManager setMgr = getOrCreateCSetManager(setId, device); + if (setMgr == null) { + return false; + } + Message m = setMgr.obtainMessage(BassCsetManager.BASS_GRP_STOP_SCAN_OFFLOAD); + setMgr.sendMessage(m); + //queue req and return true + ret = true; + } + } else { + List listOfDevices = new ArrayList(); + listOfDevices.add(device); + ret = stopScanOffload(device, listOfDevices); + } + return ret; + } + + public boolean startScanOffloadInternal (BluetoothDevice device, boolean isGroupOp) { + boolean ret = false; + log("startScanOffloadInternal: device: " + device + + "isGroupOp" + isGroupOp); + /* Even If the request is for Grouoop, If Lock support not avaiable + * for that device, go ahead and treat this as single device operation + */ + if (isGroupOp&& isLockSupportAvailable(device) == true) { + int setId = getCsetId(device); + synchronized (mSetManagers) { + BassCsetManager setMgr = getOrCreateCSetManager(setId, device); + if (setMgr == null) { + return false; + } + Message m = setMgr.obtainMessage(BassCsetManager.BASS_GRP_START_SCAN_OFFLOAD); + setMgr.sendMessage(m); + //queue req and return true + ret = true; + } + } else { + List listOfDevices = new ArrayList(); + listOfDevices.add(device); + ret = startScanOffload(device, listOfDevices); + } + return ret; + } + + public boolean addBroadcastSourceInternal (BluetoothDevice device, BleBroadcastSourceInfo srcInfo, + boolean isGroupOp) { + boolean ret = false; + log("addBroadcastSourceInternal: device: " + device + + "srcInfo" + srcInfo + + "isGroupOp" + isGroupOp); + /* Even If the request is for Group, If Lock support not avaiable + * for that device, go ahead and treat this as single device operation + */ + if (isGroupOp && isLockSupportAvailable(device) == true) { + int setId = getCsetId(device); + synchronized (mSetManagers) { + BassCsetManager setMgr = getOrCreateCSetManager(setId, device); + if (setMgr == null) { + return false; + } + Message m = setMgr.obtainMessage(BassCsetManager.BASS_GRP_ADD_BCAST_SOURCE); + m.obj = srcInfo; + setMgr.sendMessage(m); + //queue req and return true + ret = true; + } + } else { + List listOfDevices = new ArrayList(); + listOfDevices.add(device); + ret = addBroadcastSource(device, listOfDevices, srcInfo); + } + return ret; + } + + public boolean updateBroadcastSourceInternal (BluetoothDevice device, BleBroadcastSourceInfo srcInfo, + boolean isGroupOp + ) { + boolean ret = false; + log("updateBroadcastSourceInternal: device: " + device + + "srcInfo" + srcInfo + + "isGroupOp" + isGroupOp); + /* Even If the request is for Grouoop, If Lock support not avaiable + * for that device, go ahead and treat this as single device operation + */ + if (isGroupOp && isLockSupportAvailable(device) == true) { + int setId = getCsetId(device); + synchronized (mSetManagers) { + BassCsetManager setMgr = getOrCreateCSetManager(setId, device); + if (setMgr == null) { + return false; + } + Message m = setMgr.obtainMessage(BassCsetManager.BASS_GRP_UPDATE_BCAST_SOURCE); + m.obj = srcInfo; + setMgr.sendMessage(m); + //queue req and return true + ret = true; + } + } else { + List listOfDevices = new ArrayList(); + listOfDevices.add(device); + ret = updateBroadcastSource(device, listOfDevices, srcInfo); + } + return ret; + } + + protected boolean setBroadcastCodeInternal (BluetoothDevice device, BleBroadcastSourceInfo srcInfo, + boolean isGroupOp + ) { + boolean ret = false; + log("setBroadcastCodeInternal: device: " + device + + "srcInfo" + srcInfo + + "isGroupOp" + isGroupOp); + /* Even If the request is for Grouoop, If Lock support not avaiable + * for that device, go ahead and treat this as single device operation + */ + if (isGroupOp && isLockSupportAvailable(device) == true) { + int setId = getCsetId(device); + synchronized (mSetManagers) { + BassCsetManager setMgr = getOrCreateCSetManager(setId, device); + if (setMgr == null) { + return false; + } + Message m = setMgr.obtainMessage(BassCsetManager.BASS_GRP_SET_BCAST_CODE); + m.obj = srcInfo; + setMgr.sendMessage(m); + //queue req and return true + ret = true; + } + } else { + List listOfDevices = new ArrayList(); + listOfDevices.add(device); + ret = setBroadcastCode(device, listOfDevices, srcInfo); + } + return ret; + } + + public boolean removeBroadcastSourceInternal (BluetoothDevice device, byte sourceId, boolean isGroupOp + ) { + boolean ret = false; + /* Even If the request is for Grouoop, If Lock support not avaiable + * for that device, go ahead and treat this as single device operation + */ + if (isGroupOp && isLockSupportAvailable(device) == true) { + int setId = getCsetId(device); + synchronized (mSetManagers) { + BassCsetManager setMgr = getOrCreateCSetManager(setId, device); + if (setMgr == null) { + return false; + } + Message m = setMgr.obtainMessage(BassCsetManager.BASS_GRP_REMOVE_BCAST_SOURCE); + m.arg1 = sourceId; + setMgr.sendMessage(m); + //queue req and return true + ret = true; + } + } else { + List listOfDevices = new ArrayList(); + listOfDevices.add(device); + ret = removeBroadcastSource(device, listOfDevices, sourceId); + } + return ret; + } + + static void log(String msg) { + if (BassClientStateMachine.BASS_DBG) { + Log.d(TAG, msg); + } + } + + /** + * Binder object: must be a static class or memory leak may occur + */ + @VisibleForTesting + static class BluetoothSyncHelperBinder extends IBluetoothSyncHelper.Stub + implements IProfileServiceBinder { + private BCService mService; + + private BCService getService() { + if (!Utils.checkCallerIsSystemOrActiveUser(TAG)) { + return null; + } + + if (mService != null && mService.isAvailable()) { + return mService; + } + return null; + } + + BluetoothSyncHelperBinder(BCService svc) { + mService = svc; + } + + @Override + public void cleanup() { + mService = null; + } + + @Override + public boolean connect(BluetoothDevice device) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.connect(device); + } + + @Override + public boolean disconnect(BluetoothDevice device) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.disconnect(device); + } + + @Override + public List getConnectedDevices() { + BCService service = getService(); + if (service == null) { + return new ArrayList<>(); + } + return service.getConnectedDevices(); + } + + @Override + public List getDevicesMatchingConnectionStates(int[] states) { + BCService service = getService(); + if (service == null) { + return new ArrayList<>(); + } + return service.getDevicesMatchingConnectionStates(states); + } + + @Override + public int getConnectionState(BluetoothDevice device) { + BCService service = getService(); + if (service == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + return service.getConnectionState(device); + } + + @Override + public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.setConnectionPolicy(device, connectionPolicy); + } + + @Override + public int getConnectionPolicy(BluetoothDevice device) { + BCService service = getService(); + if (service == null) { + return BluetoothProfile.CONNECTION_POLICY_UNKNOWN; + } + return service.getConnectionPolicy(device); + } + @Override + public boolean searchforLeAudioBroadcasters (BluetoothDevice device) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.searchforLeAudioBroadcasters(device); + } + + @Override + public boolean stopSearchforLeAudioBroadcasters (BluetoothDevice device) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.stopSearchforLeAudioBroadcasters(device); + } + + @Override + public boolean selectBroadcastSource (BluetoothDevice device, ScanResult scanRes, boolean isGroupOp) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.selectBroadcastSource(device, scanRes, isGroupOp, false); + } + + @Override + public void registerAppCallback(BluetoothDevice device, IBleBroadcastAudioScanAssistCallback cb) { + BCService service = getService(); + if (service == null) { + return; + } + service.registerAppCallback(device, cb); + } + + @Override + public void unregisterAppCallback(BluetoothDevice device, IBleBroadcastAudioScanAssistCallback cb) { + BCService service = getService(); + if (service == null) { + return; + } + service.unregisterAppCallback(device, cb); + } + + @Override + public boolean startScanOffload(BluetoothDevice device, boolean isGroupOp) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.startScanOffloadInternal(device, isGroupOp); + } + + @Override + public boolean stopScanOffload(BluetoothDevice device, boolean isGroupOp) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.stopScanOffloadInternal(device, isGroupOp); + } + + @Override + public boolean addBroadcastSource(BluetoothDevice device, BleBroadcastSourceInfo srcInfo + , boolean isGroupOp) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.addBroadcastSourceInternal(device, srcInfo, isGroupOp); + } + + @Override + public boolean updateBroadcastSource (BluetoothDevice device, + BleBroadcastSourceInfo srcInfo, + boolean isGroupOp) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.updateBroadcastSourceInternal(device, srcInfo, isGroupOp); + } + + @Override + public boolean setBroadcastCode (BluetoothDevice device, + BleBroadcastSourceInfo srcInfo, + boolean isGroupOp) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.setBroadcastCodeInternal(device, srcInfo, isGroupOp); + } + + @Override + public boolean removeBroadcastSource (BluetoothDevice device, + byte sourceId, + boolean isGroupOp) { + BCService service = getService(); + if (service == null) { + return false; + } + return service.removeBroadcastSourceInternal(device, sourceId, isGroupOp); + } + @Override + public List getAllBroadcastSourceInformation (BluetoothDevice device + ) { + BCService service = getService(); + if (service == null) { + return null; + } + return service.getAllBroadcastSourceInformation(device); + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BaseData.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BaseData.java new file mode 100644 index 00000000000..3ee9b29816b --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BaseData.java @@ -0,0 +1,847 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ +package com.android.bluetooth.bc; + +import java.util.List; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Iterator; +import android.os.Message; +import android.util.Log; +import java.util.UUID; +import java.util.Collection; +import android.os.UserHandle; + +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Scanner; +import java.util.Map; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Set; +import java.lang.String; +import java.lang.StringBuffer; +import java.lang.Integer; + +import java.nio.ByteBuffer; +import java.lang.Byte; +import java.util.stream.IntStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.io.IOException; + +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.BleBroadcastSourceChannel; +///*_BMS +import com.android.bluetooth.broadcast.BroadcastService.BisInfo; +import com.android.bluetooth.broadcast.BroadcastService.MetadataLtv; +//_BMS*/ + +/** + * Helper class to parase the Broadcast Announcement BASE data + */ +final class BaseData { + private static final String TAG = "Bassclient-BaseData"; + BaseInformation levelOne = new BaseInformation(); + ArrayList levelTwo = new ArrayList(); + ArrayList levelThree = new ArrayList(); + int mNumBISIndicies; + public static byte UNKNOWN_CODEC = (byte)0xFE; + + public class BaseInformation { + public byte[] presentationDelay = new byte[3]; //valid only if level=1 + public byte[] codecId = new byte[5]; //valid only if level=1 + public byte codecConfigLength; + public byte[] codecConfigInfo; + public byte metaDataLength; + public byte[] metaData; + public byte numSubGroups; + public byte[] bisIndicies; //valid only if level = 2 + public byte index; //valid only if level=3 and level=2 (as subgroup Id) + public int subGroupId; + public int level;//differentiate different levels of BASE data + public LinkedHashSet keyCodecCfgDiff; + public LinkedHashSet keyMetadataDiff; + public String diffText; + public String description; + + public byte[] consolidatedCodecId; + public Set consolidatedMetadata; + public Set consolidatedCodecInfo; + public HashMap consolidatedUniqueCodecInfo; + public HashMap consolidatedUniqueMetadata; + + BaseInformation() { + presentationDelay = new byte[3]; + codecId = new byte[5]; + codecConfigLength = 0; + codecConfigInfo = null; + metaDataLength = 0; + metaData = null; + numSubGroups = 0; + bisIndicies = null; + index = (byte)0xFF; + level = 0; + + keyCodecCfgDiff = new LinkedHashSet(); + keyMetadataDiff = new LinkedHashSet(); + + consolidatedMetadata = new LinkedHashSet(); + consolidatedCodecInfo = new LinkedHashSet(); + consolidatedCodecId = new byte[5]; + consolidatedUniqueMetadata = new HashMap(); + consolidatedUniqueCodecInfo = new HashMap(); + diffText = new String(""); + description = new String(""); + log("BaseInformation is Initialized"); + } + + boolean isCodecIdUnknown() { + return (codecId != null && codecId[4] == (byte)BaseData.UNKNOWN_CODEC); + } + + void printConsolidated() { + log("**BEGIN: BIS consolidated Information**"); + log("BIS index:" + index); + log("CodecId:" + Arrays.toString(consolidatedCodecId)); + + /*if (consolidatedCodecInfo != null) { + Iterator itr = consolidatedCodecInfo.iterator(); + for (int k=0; itr.hasNext(); k++) { + log("consolidatedCodecInfo:[" + k + "]:" + Arrays.toString(itr.next().getBytes())); + } + } + + if (consolidatedMetadata != null) { + Iterator itr = consolidatedMetadata.iterator(); + for (int k=0; itr.hasNext(); k++) { + log("consolidatedMetadata:[" + k + "]:" + Arrays.toString(itr.next().getBytes())); + } + }*/ + + if (consolidatedUniqueCodecInfo != null) { + for (Map.Entry entry : consolidatedUniqueCodecInfo.entrySet()) { + log("consolidatedUniqueCodecInfo:[" + entry.getKey() + "]:" + Arrays.toString(entry.getValue().getBytes())); + } + } + + if (consolidatedUniqueMetadata != null) { + for (Map.Entry entry : consolidatedUniqueMetadata.entrySet()) { + log("consolidatedUniqueMetadata:[" + entry.getKey() + "]:" + Arrays.toString(entry.getValue().getBytes())); + } + } + log("**END: BIS consolidated Information****"); + } + void print() { + log("**BEGIN: Base Information**"); + log("**Level: " + level + "***"); + if (level == 1) { + log("presentationDelay: " + Arrays.toString(presentationDelay)); + } + if (level == 2) { + log("codecId: " + Arrays.toString(codecId)); + } + if (level == 2 || level == 3) { + log("codecConfigLength: " + codecConfigLength); + log("subGroupId: " + subGroupId); + } + if (codecConfigLength != (byte)0) { + log("codecConfigInfo: " + Arrays.toString(codecConfigInfo)); + } + if (level == 2) { + log("metaDataLength: " + metaDataLength); + if (metaDataLength != (byte)0) { + log("metaData: " + Arrays.toString(metaData)); + } + if (level == 1 || level == 2) + log("numSubGroups: " + numSubGroups); + } + if (level == 2) { + log("Level2: Key Metadata differentiators"); + if (keyMetadataDiff != null) { + Iterator itr = keyMetadataDiff.iterator(); + for (int k=0; itr.hasNext(); k++) { + log("keyMetadataDiff:[" + k + "]:" + Arrays.toString(itr.next().getBytes())); + } + } + log("END: Level2: Key Metadata differentiators"); + + log("Level2: Key CodecConfig differentiators"); + if (keyCodecCfgDiff != null) { + Iterator itr = keyCodecCfgDiff.iterator(); + for (int k=0; itr.hasNext(); k++) { + log("LEVEL2: keyCodecCfgDiff:[" + k + "]:" + Arrays.toString(itr.next().getBytes())); + } + } + log("END: Level2: Key CodecConfig differentiators"); + //log("bisIndicies: " + Arrays.toString(bisIndicies)); + log("LEVEL2: diffText: " + diffText); + } + if (level == 3) { + log("Level3: Key CodecConfig differentiators"); + if (keyCodecCfgDiff != null) { + Iterator itr = keyCodecCfgDiff.iterator(); + for (int k=0; itr.hasNext(); k++) { + log("LEVEL3: keyCodecCfgDiff:[" + k + "]:" + Arrays.toString(itr.next().getBytes())); + } + } + log("END: Level3: Key CodecConfig differentiators"); + log("index: " + index); + log("LEVEL3: diffText: " + diffText); + } + log("**END: Base Information****"); + } + }; + ///*_BMS + BaseData(int numSubGroups, List colocatedBisInfo, Map metaInfo) { + if (metaInfo == null || colocatedBisInfo == null) { + + Log.e(TAG, "BaseData Contruction with Invalid parameters"); + throw new IllegalArgumentException("Basedata: Parameters can't be null"); + } + levelOne = new BaseInformation(); + levelTwo = new ArrayList(); + levelThree = new ArrayList(); + + levelOne.level = 1; + levelOne.numSubGroups = (byte)numSubGroups; + + //create the level Two and update the Metadata Info + for (int i=0; i(); + levelThree = new ArrayList(); + mNumBISIndicies = 0; + log("members initialized"); + log("BASE input" + Arrays.toString(serviceData)); + + //Parse Level 1 base + levelOne.level = 1; + int level1Idx = 0; + System.arraycopy(serviceData, level1Idx, levelOne.presentationDelay,0, 3); + level1Idx = level1Idx + 3; + + levelOne.numSubGroups = serviceData[level1Idx++]; + levelOne.print(); + log("levelOne subgroups" + levelOne.numSubGroups); + + int level2Idx = level1Idx; + for (int i =0; i<(int)levelOne.numSubGroups; i++) { + log("parsing subgroup" + i); + BaseInformation b = new BaseInformation(); + + b.level = 2; + b.subGroupId = i; + b.numSubGroups = serviceData[level2Idx++]; + if (serviceData[level2Idx] == (byte)UNKNOWN_CODEC) { + //Place It in the last byte of codecID + System.arraycopy(serviceData, level2Idx, b.codecId, 4, 1); + level2Idx = level2Idx + 1; + log("codecId is FE"); + } else { + System.arraycopy(serviceData, level2Idx, b.codecId, 0, 5); + level2Idx = level2Idx + 5; + } + + b.codecConfigLength = serviceData[level2Idx++]; + if (b.codecConfigLength != 0) { + b.codecConfigInfo = new byte[(int)b.codecConfigLength]; + System.arraycopy(serviceData, level2Idx, b.codecConfigInfo, 0, (int)b.codecConfigLength); + level2Idx = level2Idx + (int)b.codecConfigLength; + } + b.metaDataLength = serviceData[level2Idx++]; + if (b.metaDataLength != 0) { + b.metaData = new byte[(int)b.metaDataLength]; + System.arraycopy(serviceData, level2Idx, b.metaData, 0, (int)b.metaDataLength); + level2Idx = level2Idx + (int)b.metaDataLength; + } + mNumBISIndicies = mNumBISIndicies + b.numSubGroups; + levelTwo.add(b); + b.print(); + } + //Parse Level 3 Base + int level3Index = level2Idx; + for (int k=0; k uniqueMds = new HashMap (); + Map uniqueCcis = new HashMap (); + + Set Csfs = levelThree.get(i).consolidatedCodecInfo; + + if (Csfs.size() > 0) { + Iterator itr = Csfs.iterator(); + for (int j=0; itr.hasNext(); j++) { + byte[] ltvEntries = itr.next().getBytes(); + + int k = 0; + byte length = ltvEntries[k++]; + byte[] ltv = new byte[length+1]; + ltv[0] = length; + System.arraycopy(ltvEntries, k, ltv, 1, length); + + // + int type = (int)ltv[1]; + String s = uniqueCcis.get(type); + String ltvS = new String(ltv); + if (s == null) { + uniqueCcis.put(type, ltvS); + } else { + //if same type exists + //replace + uniqueCcis.replace(type, ltvS); + } + } + } + + Set Mds = levelThree.get(i).consolidatedMetadata; + if (Mds.size() > 0) { + Iterator itr = Mds.iterator(); + for (int j=0; itr.hasNext(); j++) { + byte[] ltvEntries = itr.next().getBytes(); + + int k = 0; + byte length = ltvEntries[k++]; + byte[] ltv = new byte[length+1]; + ltv[0] = length; + System.arraycopy(ltvEntries, k, ltv, 1, length); + + /*CHECK: This can be straight PUT, there wont be dups in Metadata with new BASE*/ + int type = (int)ltv[1]; + String s = uniqueCcis.get(type); + String ltvS = new String(ltv); + if (s == null) { + uniqueMds.put(type, ltvS); + } else { + //if same type exists + //replace + uniqueMds.replace(type, ltvS); + } + } + } + + levelThree.get(i).consolidatedUniqueMetadata = new HashMap(uniqueMds); + levelThree.get(i).consolidatedUniqueCodecInfo = new HashMap(uniqueCcis); + + } + } + + void consolidateBaseofLevelThree(int parentSubgroup, int startIdx, int numNodes) { + + for (int i=startIdx; i(levelTwo.get(parentSubgroup).consolidatedMetadata); + + //log("Parent Cons Info>>"); + //levelTwo.get(parentSubgroup).printConsolidated(); + //CCI clone from Parent + levelThree.get(i).consolidatedCodecInfo = new LinkedHashSet(levelTwo.get(parentSubgroup).consolidatedCodecInfo); + //log("before " + i); + //levelThree.get(i).printConsolidated(); + //Append Level 2 Codec Config + if (levelThree.get(i).codecConfigLength != 0) { + log("append level 3 cci to level 3 cons:" + i); + String s = new String(levelThree.get(i).codecConfigInfo); + levelThree.get(i).consolidatedCodecInfo.add(s); + } + //log("after " + i); + //levelThree.get(i).printConsolidated(); + //log("Parent Cons Info>>"); + //levelTwo.get(parentSubgroup).printConsolidated(); + } + + } + + public int getNumberOfIndicies() { + return mNumBISIndicies; + } + + public byte getNumberOfSubgroupsofBIG() { + byte ret = 0; + if (levelOne != null) { + ret = levelOne.numSubGroups; + } + return ret; + } + + public ArrayList getBISIndexInfos() { + return levelThree; + } + List getBroadcastChannels() { + List bChannels = new ArrayList(); + for (int k=0; k pickAllBroadcastChannels() { + List bChannels = new ArrayList(); + for (int k=0; k itr = levelTwo.get(i).keyMetadataDiff.iterator(); + for (int k=0; itr.hasNext(); k++) { + levelTwo.get(i).diffText = levelTwo.get(i).diffText.concat(getMetadataString(itr.next().getBytes())); + levelTwo.get(i).diffText = levelTwo.get(i).diffText.concat("_"); + } + } + if (levelTwo.get(i).keyCodecCfgDiff != null) { + Iterator itr = levelTwo.get(i).keyCodecCfgDiff.iterator(); + for (int k=0; itr.hasNext(); k++) { + levelTwo.get(i).diffText = levelTwo.get(i).diffText.concat(getCodecParamString(itr.next().getBytes())); + levelTwo.get(i).diffText = levelTwo.get(i).diffText.concat("_"); + } + } + } + + for (int i=0; i itr = levelThree.get(i).keyCodecCfgDiff.iterator(); + for (int k=0; itr.hasNext(); k++) { + levelThree.get(i).diffText = levelThree.get(i).diffText.concat(getCodecParamString(itr.next().getBytes())); + levelThree.get(i).diffText = levelThree.get(i).diffText.concat("_"); + } + } + } + + //Concat and update the Description + int startIdx = 0; + int children = 0; + for (int i=0; i uniqueCodecIds = new LinkedHashSet(); + Set uniqueCsfs = new LinkedHashSet(); + Set uniqueMetadatas = new LinkedHashSet(); + + log("updateUniquenessForLevelTwo"); + + int startIdx = 0; + int children = 0; + for (int i=0; i uniqueCodecParams = new LinkedHashSet(); + Set uniqueMetadataParams = new LinkedHashSet(); + + if (uniqueCodecIds.size() > 0) log("LevelTwo: UniqueCodecIds"); + + if (uniqueCsfs.size() > 0) { + log("LevelTwo: uniqueCsfs"); + //uniqueCodecParams = + Iterator itr = uniqueCsfs.iterator(); + for (int i=0; itr.hasNext(); i++) { + byte[] ltvEntries = itr.next().getBytes(); + + int k = 0; + byte length = ltvEntries[k++]; + byte[] ltv = new byte[length+1]; + ltv[0] = length; + System.arraycopy(ltvEntries, k, ltv, 1, length); + //This should ensure Duplicate entries at this level + String s = new String(ltvEntries); + uniqueCodecParams.add(s); + } + } + if (uniqueMetadatas.size() > 0) { + log("LevelTwo: uniqueMetadatas"); + //uniqueMetadataParams = new LinkedHashSet(); + Iterator itr = uniqueMetadatas.iterator(); + for (int i=0; itr.hasNext(); i++) { + byte[] ltvEntries = itr.next().getBytes(); + + int k = 0; + byte length = ltvEntries[k++]; + byte[] ltv = new byte[length+1]; + ltv[0] = length; + System.arraycopy(ltvEntries, k, ltv, 1, length); + //This should ensure Duplicate entries at this level + String s = new String(ltvEntries); + uniqueMetadataParams.add(s); + } + } + + //run though the nodes and update KEY differentiating factors + if (uniqueCodecParams != null) { + Iterator itr = uniqueCodecParams.iterator(); + int i = 0; + for (int k=0; itr.hasNext(); k++) { + levelTwo.get(i).keyCodecCfgDiff.add(itr.next()); + i = (i+1)%(numNodes); + } + } + + //run though the nodes and update KEY differentiating factors + if (uniqueMetadataParams != null) { + Iterator itr = uniqueMetadataParams.iterator(); + int i = 0; + for (int k=0; itr.hasNext(); k++) { + levelTwo.get(i).keyMetadataDiff.add(itr.next()); + i = (i+1)%(numNodes); + } + } + + /*log("Level2: Uniqueness among subgroups"); + if (uniqueCodecParams != null) { + Iterator itr = uniqueCodecParams.iterator(); + for (int k=0; itr.hasNext(); k++) { + log("UniqueCodecParams:[" + k + "]" + Arrays.toString(itr.next().getBytes())); + } + } + if (uniqueMetadataParams != null) { + Iterator itr = uniqueMetadataParams.iterator(); + for (int k=0; itr.hasNext(); k++) { + log("uniqueMetadataParams:["+ k + "]" + Arrays.toString(itr.next().getBytes())); + } + } + log("END: Level2: Uniqueness among subgroups"); + */ + } + void updateUniquenessForLevelThree(int parentSubgroup, int startIdx, int numNodes) { + //Set uniqueCodecIds = new LinkedHashSet(); + Set uniqueCsfs = new LinkedHashSet(); + //Set uniqueMetadatas = new LinkedHashSet(); + + log("updateUniquenessForLevelThree: startIdx" + startIdx + "numNodes" + numNodes); + for (int i=startIdx; i uniqueCodecParams = new LinkedHashSet(); + if (uniqueCsfs.size() > 0) { + log("LevelThree: uniqueCsfs"); + //uniqueCodecParams = + Iterator itr = uniqueCsfs.iterator(); + for (int i=0; itr.hasNext(); i++) { + byte[] ltvEntries = itr.next().getBytes(); + + int k = 0; + byte length = ltvEntries[k++]; + byte[] ltv = new byte[length+1]; + ltv[0] = length; + System.arraycopy(ltvEntries, k, ltv, 1, length); + //This should ensure Duplicate entries at this level + String s = new String(ltvEntries); + uniqueCodecParams.add(s); + } + } + //run though the nodes and update KEY differentiating factors + if (uniqueCodecParams != null) { + Iterator itr = uniqueCodecParams.iterator(); + int i = startIdx; + for (int k=0; itr.hasNext(); k++) { + levelThree.get(i).keyCodecCfgDiff.add(itr.next()); + i = (i+1)%(startIdx+numNodes); + } + } + /* + log("Level3: Uniqueness among children of " + parentSubgroup + "th Subgroup"); + if (uniqueCodecParams != null) { + Iterator itr = uniqueCodecParams.iterator(); + for (int k=0; itr.hasNext(); k++) { + log("UniqueCodecParams:[" + k + "]" + Arrays.toString(itr.next().getBytes())); + + } + } + log("END: Level3: Uniqueness among children of " + parentSubgroup + "th Subgroup"); + */ + } + + void print() { + levelOne.print(); + log("----- Level TWO BASE ----"); + for (int i=0; i(Disconnecting) + * | ^ + * CONNECTED | | DISCONNECT + * V | + * (Connected) + * | ^ + * GATT_TXN | | GATT_TXN_DONE/GATT_TXN_TIMEOUT + * V | + * (ConnectedProcessing) + * NOTES: + * - If state machine is in "Connecting" state and the remote device sends + * DISCONNECT request, the state machine transitions to "Disconnecting" state. + * - Similarly, if the state machine is in "Disconnecting" state and the remote device + * sends CONNECT request, the state machine transitions to "Connecting" state. + * - Whenever there is any Gatt Write/read, State machine will moved "ConnectedProcessing" and + * all other requests (add, update, remove source) operations will be deferred in "ConnectedProcessing" state + * - Once the gatt transaction is done (or after a specified timeout of no response), State machine will + * move back to "Connected" and try to process the deferred requests as needed + * + * DISCONNECT + * (Connecting) ---------------> (Disconnecting) + * <--------------- + * CONNECT + * + */ + +package com.android.bluetooth.bc; + +import static android.Manifest.permission.BLUETOOTH_CONNECT; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothUuid; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.BluetoothGattService; +import android.bluetooth.BluetoothSyncHelper; +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.BleBroadcastSourceChannel; +import android.bluetooth.BleBroadcastAudioScanAssistManager; +import android.bluetooth.IBleBroadcastAudioScanAssistCallback; +import android.bluetooth.BleBroadcastAudioScanAssistCallback; +import com.android.bluetooth.Utils; + +//CSIP related imports +///*_CSIP +import com.android.bluetooth.groupclient.GroupService; +import android.bluetooth.BluetoothGroupCallback; +import android.bluetooth.DeviceGroup; +//_CSIP*/ + +///*_VCP +import android.bluetooth.BluetoothVcp; +import com.android.bluetooth.vcp.VcpController; +//_VCP*/ + +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.PeriodicAdvertisingCallback; +import android.bluetooth.le.PeriodicAdvertisingManager; +import android.bluetooth.le.PeriodicAdvertisingReport; + +import android.bluetooth.IBluetoothManager; +import android.os.ServiceManager; +import android.os.IBinder; + +import java.util.List; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Iterator; +import android.content.Intent; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import java.util.UUID; +import java.util.Collection; +import android.os.UserHandle; +import java.lang.IllegalArgumentException; + +import com.android.bluetooth.btservice.ProfileService; +/*_PACS +import com.android.bluetooth.pacsclient.PacsClientService; +_PACS*/ + +import com.android.bluetooth.btservice.ServiceFactory; +///*_BMS +import com.android.bluetooth.broadcast.BroadcastService; +//_BMS*/ + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Scanner; +import java.util.Map; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Set; +import java.lang.String; +import java.lang.StringBuffer; +import java.lang.Integer; + +import java.nio.ByteBuffer; +import java.lang.Byte; +import java.util.stream.IntStream; +import android.os.SystemProperties; +import android.os.ParcelUuid; + + +final class BassClientStateMachine extends StateMachine { + private static final String TAG = "BassClientStateMachine"; + public static final boolean BASS_DBG = true; + //public static final boolean BASS_DBG = Log.isLoggable(TAG, Log.DEBUG); + + private boolean mIsWhitelist = false; + + static final int BCAST_RECEIVER_STATE_LENGTH = 15; + static final int CONNECT = 1; + static final int DISCONNECT = 2; + static final int CONNECTION_STATE_CHANGED = 3; + static final int GATT_TXN_PROCESSED = 4; + static final int READ_BASS_CHARACTERISTICS= 5; + static final int START_SCAN_OFFLOAD = 6; + static final int STOP_SCAN_OFFLOAD = 7; + static final int SELECT_BCAST_SOURCE = 8; + static final int ADD_BCAST_SOURCE = 9; + static final int UPDATE_BCAST_SOURCE = 10; + static final int SET_BCAST_CODE = 11; + static final int REMOVE_BCAST_SOURCE = 12; + static final int GATT_TXN_TIMEOUT = 13; + static final int PSYNC_ACTIVE_TIMEOUT = 14; + public static final int CSIP_CONNECTION_STATE_CHANGED = 15; + static final int CONNECT_TIMEOUT = 16; + + //30 secs time out for all gatt writes + static final int GATT_TXN_TIMEOUT_MS = 30000; + + //3 min time out for keeping PSYNC active + static final int PSYNC_ACTIVE_TIMEOUT_MS = 3*60000; + //2 secs time out achieving psync + static final int PSYNC_TIMEOUT = 200; + + int NUM_OF_BROADCAST_RECEIVER_STATES = 0; + + private final Disconnected mDisconnected; + private final Connected mConnected; + private final Connecting mConnecting; + private final Disconnecting mDisconnecting; + private final ConnectedProcessing mConnectedProcessing; + private int mLastConnectionState = -1; + private static int mConnectTimeoutMs = 30000; + private boolean mMTUChangeRequested = false; + private boolean mDiscoveryInitiated = false; + + private BCService mService; + private final BluetoothDevice mDevice; + private BluetoothGatt mBluetoothGatt = null; + + //BASS Characteristics UUID + public static final UUID BASS_UUID = UUID.fromString("0000184F-0000-1000-8000-00805F9B34FB"); + private static final UUID BASS_BCAST_AUDIO_SCAN_CTRL_POINT = UUID.fromString("00002BC7-0000-1000-8000-00805F9B34FB"); + private static final UUID BASS_BCAST_RECEIVER_STATE = UUID.fromString("00002BC8-0000-1000-8000-00805F9B34FB"); + private static final UUID CLIENT_CHARACTERISTIC_CONFIG = UUID.fromString( + "00002902-0000-1000-8000-00805f9b34fb"); + private List mBroadcastReceiverStates; + private BluetoothGattCharacteristic mBroadcastScanControlPoint; + /*key is combination of sourceId, Address and advSid for this hashmap*/ + private final Map mBleBroadcastSourceInfos; + private boolean mFirstTimeBisDiscovery = false; + private int mPASyncRetryCounter = 0; + private ScanResult mScanRes = null; + + BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + private ServiceFactory mFactory = new ServiceFactory(); + ///*_BMS + private BroadcastService mBAService = null; + //_BMS*/ + + private final byte[] REMOTE_SCAN_STOP = {00}; + private final byte[] REMOTE_SCAN_START = {01}; + private byte BASS_ADD_SOURCE_OPCODE = 0x02; + private byte BASS_UPDATE_SOURCE_OPCODE = 0x03; + private byte BASS_SET_BCAST_PIN_OPCODE = 0x04; + private byte BASS_REMOVE_SOURCE_OPCODE = 0x05; + + private static int num_of_recever_states = 0; + private static int PIN_CODE_CMD_LEN = 18; + private final int BASS_MAX_BYTES = 100; + private int mPendingOperation = -1; + private byte mPendingSourceId = -1; + public static byte INVALID_SRC_ID = -1; + private int GATT_TXN_TOUT_ERROR = -1; + private BleBroadcastSourceInfo mSetBroadcastPINSrcInfo = null; + private boolean mSetBroadcastCodePending = false; + + //types of command for set broadcast PIN operation + public int FRESH = 1; + private int QUEUED = 2; + + //types of command for select and add Broadcast source operations + public int AUTO = 1; + public int USER = 2; + + //types of operation for Select source to determine + //if psync achieved on behalf of single device or multiple devices + public int GROUP_OP = 1; + public int NON_GROUP_OP = 0; + + public static int BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC = 0; + + //Service data Octet0 + private static int BROADCAST_ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS = 0x00000001; + private static int BROADCAST_ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS = 0x00000002; + + //Psync and PAST interfaces + private PeriodicAdvertisingManager mPeriodicAdvManager; + private static UUID BASIC_AUDIO_UUID = UUID.fromString("00001851-0000-1000-8000-00805F9B34FB"); + private boolean mAutoAssist = false; + private boolean mNoReverse = false; + private boolean mAutoTriggerred = false; + private boolean mSyncingOnBehalfOfGroup = false; + private boolean mNoStopScanOffload = false; + + //CSET interfaces + ///*_CSIP + private GroupService mSetCoordinator = GroupService.getGroupService(); + private boolean mCsipConnected = false; + //_CSIP*/ + + private boolean mPacsAvail = false; + private boolean mDefNoPAS = false; + private boolean mNoPast = false; + private boolean mNoCSIPReconn = false; + private boolean mPublicAddrForcSrc = false; + private boolean mForceSB = false; + private boolean mVcpForBroadcast = false; + + private int BROADCAST_SOURCE_ID_LENGTH = 3; + private byte mTempSourceId = 0; + //broadcast receiver state indicies + private static final int BCAST_RCVR_STATE_SRC_ID_IDX = 0; + private static final int BCAST_RCVR_STATE_SRC_ADDR_TYPE_IDX = 1; + private static final int BCAST_RCVR_STATE_SRC_ADDR_START_IDX = 2; + private static final int BCAST_RCVR_STATE_SRC_BCAST_ID_START_IDX = 9; + private static final int BCAST_RCVR_STATE_SRC_ADDR_SIZE = 6; + private static final int BCAST_RCVR_STATE_SRC_ADV_SID_IDX = 8; + private static final int BCAST_RCVR_STATE_PA_SYNC_IDX = 12; + private static final int BCAST_RCVR_STATE_ENC_STATUS_IDX = 13; + private static final int BCAST_RCVR_STATE_BADCODE_START_IDX = 14; + private static final int BCAST_RCVR_STATE_BADCODE_SIZE = 16; + + + private static final int BCAST_RCVR_STATE_BIS_SYNC_START_IDX = 10; + private static final int BCAST_RCVR_STATE_BIS_SYNC_SIZE = 4; + private static final int BCAST_RCVR_STATE_METADATA_LENGTH_IDX = 15; + private static final int BCAST_RCVR_STATE_METADATA_START_IDX = 16; + BassClientStateMachine(BluetoothDevice device, BCService svc, + Looper looper) { + super(TAG + "(" + device.toString() + ")", looper); + mDevice = device; + mService = svc; + + mDisconnected = new Disconnected(); + mDisconnecting = new Disconnecting(); + mConnected = new Connected(); + mConnecting = new Connecting(); + mConnectedProcessing = new ConnectedProcessing(); + + addState(mDisconnected); + addState(mDisconnecting); + addState(mConnected); + addState(mConnecting); + addState(mConnectedProcessing); + + setInitialState(mDisconnected); + mBroadcastReceiverStates = new ArrayList(); + mBleBroadcastSourceInfos = new HashMap(); + + //PSYNC and PAST instances + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mBluetoothAdapter != null) { + mPeriodicAdvManager = mBluetoothAdapter.getPeriodicAdvertisingManager(); + } + ///*_BMS + mBAService = BroadcastService.getBroadcastService(); + //_BMS*/ + + mNoReverse = SystemProperties.getBoolean("persist.vendor.service.bt.nReverse", false); + mAutoAssist = SystemProperties.getBoolean("persist.vendor.service.bt.autoassist", false); + mIsWhitelist = SystemProperties.getBoolean("persist.vendor.service.bt.wl", true); + mDefNoPAS = SystemProperties.getBoolean("persist.vendor.service.bt.defNoPAS", false); + mNoPast = SystemProperties.getBoolean("persist.vendor.service.bt.noPast", false); + mNoCSIPReconn = SystemProperties.getBoolean("persist.vendor.service.bt.noCsipRec", false); + mPublicAddrForcSrc = SystemProperties.getBoolean("persist.vendor.service.bt.pAddrForcSource", true); + mForceSB = SystemProperties.getBoolean("persist.vendor.service.bt.forceSB", false); + mVcpForBroadcast = SystemProperties.getBoolean("persist.vendor.service.bt.vcpForBroadcast", true); + } + + static BassClientStateMachine make(BluetoothDevice device, BCService svc, + Looper looper) { + Log.d(TAG, "make for device " + device); + BassClientStateMachine BassclientSm = new BassClientStateMachine(device, svc, + looper); + BassclientSm.start(); + return BassclientSm; + } + + public void doQuit() { + log("doQuit for device " + mDevice); + quitNow(); + } + + public void cleanup() { + log("cleanup for device " + mDevice); + clearCharsCache(); + + if (mBluetoothGatt != null) { + log("disconnect gatt"); + mBluetoothGatt.disconnect(); + mBluetoothGatt.close(); + mBluetoothGatt = null; + } + mPendingOperation = -1; + mPendingSourceId = -1; + } + + BleBroadcastSourceInfo getBroadcastSourceInfoForSourceDevice (BluetoothDevice srcDevice) { + List currentSourceInfos = + getAllBroadcastSourceInformation(); + BleBroadcastSourceInfo srcInfo = null; + for (int i=0; i currentSourceInfos = + getAllBroadcastSourceInformation(); + BleBroadcastSourceInfo srcInfo = null; + for (int i=0; i bmsAdvDataMap = record.getServiceData(); + if (bmsAdvDataMap != null) { + for (Map.Entry entry : bmsAdvDataMap.entrySet()) { + log("ParcelUUid = " + entry.getKey() + + ", Value = " + entry.getValue()); + } + } else { + log("bmsAdvDataMap is null"); + if (mAutoTriggerred == false) { + mService.sendBroadcastSourceSelectedCallback(mDevice, null, + BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_SELECTED); + cancelActiveSync(srcDevice); + } else { + mAutoTriggerred = false; + } + } + ParcelUuid basicAudioUuid = new ParcelUuid(BASIC_AUDIO_UUID); + byte[] bmsAdvData = record.getServiceData(basicAudioUuid); + if (bmsAdvData != null) { + //ByteBuffer bb = ByteBuffer.wrap(bmsAdvData); + parseBaseData(mDevice, syncHandle, bmsAdvData); + + } else { + Log.e(TAG, "No service data in Scan record"); + if (mAutoTriggerred == false) { + mService.sendBroadcastSourceSelectedCallback(mDevice, null, + BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_SELECTED); + cancelActiveSync(srcDevice); + } else { + mAutoTriggerred = false; + } + } + } + + /*Local Public address based check + Use this prior to addition of Broadcast source*/ + boolean isLocalBroadcastSource (BluetoothDevice device) + { + BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); + boolean ret = btAdapter.getAddress().equals(device.getAddress()); + + log("isLocalBroadcastSource returns" +ret); + return ret; + } + + private boolean isValidBroadcastSourceInfo(BleBroadcastSourceInfo srcInfo) { + boolean ret = true; + List currentSourceInfos = + getAllBroadcastSourceInformation(); + Log.i(TAG, "input srcInfo: " + srcInfo); + for (int i=0; i listOfUuids = scanRecord.getServiceData(); + if (listOfUuids != null) { + if(listOfUuids.containsKey(ParcelUuid.fromString(BassUtils.BAAS_UUID))) { + byte[] bId = listOfUuids.get(ParcelUuid.fromString(BassUtils.BAAS_UUID)); + broadcastId = (0x00FF0000 & (bId[0] << 16)); + broadcastId |= (0x0000FF00 & (bId[1] << 8)); + broadcastId |= (0x000000FF & bId[2]); + } + } + mService.updatePAResultsMap(scanRes.getDevice(), scanRes.getAddressType(), + BCService.INVALID_SYNC_HANDLE, BCService.INVALID_ADV_SID, + scanRes.getPeriodicAdvertisingInterval(), + broadcastId); + } + } + else { + log("colocated case"); + if (autoTriggerred) { + log("should never happen!!!"); + //ignore + mAutoTriggerred = false; + } + ///*_BMS + if (mBAService == null || mBAService.isBroadcastActive() != true) { + Log.e(TAG, "colocated source handle unavailable OR not in streaming"); + mService.sendBroadcastSourceSelectedCallback(mDevice, null, + BleBroadcastAudioScanAssistCallback.BASS_STATUS_COLOCATED_SRC_UNAVAILABLE); + mService.stopScanOffloadInternal(mDevice, false); + return false; + } + String colocatedAddress = null; + int colocatedAddressType; + if (mPublicAddrForcSrc == true) { + colocatedAddress = BluetoothAdapter.getDefaultAdapter().getAddress(); + colocatedAddressType = BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC; + } else { + colocatedAddress = mBAService.BroadcastGetAdvAddress(); + colocatedAddressType = mBAService.BroadcastGetAdvAddrType(); + } + int paInterval = 0x0000FFFF; + paInterval = mBAService.BroadcastGetAdvInterval(); + + if (colocatedAddress == null) { + log("colocatedAddress is null"); + mService.sendBroadcastSourceSelectedCallback(mDevice, null, + BleBroadcastAudioScanAssistCallback.BASS_STATUS_COLOCATED_SRC_UNAVAILABLE); + mService.stopScanOffloadInternal(mDevice, false); + return false; + } + BluetoothDevice colocatedSrcDevice = + BluetoothAdapter.getDefaultAdapter().getRemoteDevice(colocatedAddress); + log("caching local Broacast details: " + colocatedSrcDevice); + + //advSid is same as advHandle for collocated case + byte[] broadcast_id = mBAService.getBroadcastId(); + broadcastId = (0x00FF0000 & (broadcast_id[2] << 16)); + broadcastId |= (0x0000FF00 & (broadcast_id[1] << 8)); + broadcastId |= (0x000000FF & broadcast_id[0]); + + mService.updatePAResultsMap(colocatedSrcDevice, colocatedAddressType, + mBAService.BroadcatGetAdvHandle(), + mBAService.BroadcatGetAdvHandle(), + paInterval, + broadcastId); + BaseData localBase = new BaseData(mBAService.getNumSubGroups(), + mBAService.BroadcastGetBisInfo(), + mBAService.BroadcastGetMetaInfo()); + localBase.printConsolidated(); + //Use advHandle to cahce Base + mService.updateBASE(mBAService.BroadcatGetAdvHandle(), localBase); + mService.sendBroadcastSourceSelectedCallback(mDevice, localBase.getBroadcastChannels(), + BleBroadcastAudioScanAssistCallback.BASS_STATUS_SUCCESS); + //_BMS*/ + } + return true; + } + + private void cancelActiveSync(BluetoothDevice sourceDev) { + log("cancelActiveSync"); + boolean isCancelSyncNeeded = false; + BluetoothDevice activeSyncedSrc = mService.getActiveSyncedSource(mDevice); + if (activeSyncedSrc != null ) { + if (sourceDev == null) { + isCancelSyncNeeded = true; + } else if(activeSyncedSrc.equals(sourceDev)) { + isCancelSyncNeeded = true; + } + } + if (isCancelSyncNeeded) { + removeMessages(PSYNC_ACTIVE_TIMEOUT); + try { + log("calling unregisterSync"); + mPeriodicAdvManager.unregisterSync(mPeriodicAdvCallback); + } catch (IllegalArgumentException ex) { + Log.w(TAG, "unregisterSync:IllegalArguementException"); + //ignore + } + mService.clearPAResults(activeSyncedSrc); + mService.setActiveSyncedSource(mDevice, null); + if (mNoStopScanOffload != true) { + //trigger scan stop here + mService.stopScanOffloadInternal(mDevice, false); + } + } + mNoStopScanOffload = false; + } + + /* Internal periodc Advertising manager callback + * + */ + private PeriodicAdvertisingCallback mPeriodicAdvCallback = new PeriodicAdvertisingCallback() { + @Override + public void onSyncEstablished(int syncHandle, BluetoothDevice device, + int advertisingSid, int skip, int timeout, + int status) { + log ("onSyncEstablished" + "syncHandle" + syncHandle + + "device" + device + "advertisingSid" + advertisingSid + + "skip" + skip + "timeout" + timeout + "status" + + status); + //turn off the LeScan once sync estd + mService.getBassUtils().leScanControl(false); + if (status == BluetoothGatt.GATT_SUCCESS) { + //upates syncHandle, advSid + mService.updatePAResultsMap(device, + BCService.INVALID_ADV_ADDRESS_TYPE, + syncHandle, advertisingSid, + BCService.INVALID_ADV_INTERVAL, + BCService.INVALID_BROADCAST_ID); + sendMessageDelayed(PSYNC_ACTIVE_TIMEOUT, PSYNC_ACTIVE_TIMEOUT_MS); + mService.setActiveSyncedSource(mDevice, device); + } else { + log("failed to sync to PA" + mPASyncRetryCounter); + mScanRes = null; + if (mAutoTriggerred == false) { + mService.sendBroadcastSourceSelectedCallback(mDevice, null, + BleBroadcastAudioScanAssistCallback.BASS_STATUS_SOURCE_UNAVAILABLE); + mService.stopScanOffloadInternal(mDevice, false); + } + mAutoTriggerred = false; + } + } + @Override + public void onPeriodicAdvertisingReport(PeriodicAdvertisingReport report) { + log( "onPeriodicAdvertisingReport"); + //Parse the BIS indicies from report's service data + if (mFirstTimeBisDiscovery) { + parseScanRecord(report.getSyncHandle(),report.getData()); + mFirstTimeBisDiscovery = false; + } + } + @Override + public void onSyncLost(int syncHandle) { + log( "OnSyncLost" + syncHandle); + BluetoothDevice srcDevice = mService.getDeviceForSyncHandle(syncHandle); + cancelActiveSync(srcDevice); + } + + public void onSyncTransfered(BluetoothDevice device, int status) { + log("sync transferred:" + device + " : " + status); + } + }; + + private void broadcastReceiverState(BleBroadcastSourceInfo state, int index, int max_num_srcInfos) { + log("broadcastReceiverState: " + mDevice); + + Intent intent = new Intent(BleBroadcastAudioScanAssistManager.ACTION_BROADCAST_SOURCE_INFO); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); + intent.putExtra(BleBroadcastSourceInfo.EXTRA_SOURCE_INFO, state); + intent.putExtra(BleBroadcastSourceInfo.EXTRA_SOURCE_INFO_INDEX, index); + intent.putExtra(BleBroadcastSourceInfo.EXTRA_MAX_NUM_SOURCE_INFOS, max_num_srcInfos); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mService.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_CONNECT, + Utils.getTempAllowlistBroadcastOptions()); + } + + private static boolean isEmpty(final byte[] data){ + return IntStream.range(0, data.length).parallel().allMatch(i -> data[i] == 0); + } + + private void processPASyncState(BleBroadcastSourceInfo srcInfo) { + log("processPASyncState" + srcInfo); + int serviceData = 0; + if (srcInfo == null) { + Log.e(TAG, "processPASyncState: srcInfo is null"); + return; + } + if (srcInfo.getMetadataSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_SYNCINFO_REQ) { + log("Initiate PAST procedure"); + BCService.PAResults res = mService.getPAResults(srcInfo.getSourceDevice()); + if (isAddedBroadcastSourceIsLocal(srcInfo.getSourceDevice()) && + mService.isLocalBroadcasting()) { + if (res == null) { + log("Populate colocated PA and initiate PAST"); + + int colocatedAddressType; + if (mPublicAddrForcSrc == true) { + colocatedAddressType = BROADCAST_ASSIST_ADDRESS_TYPE_PUBLIC; + } else { + colocatedAddressType = mBAService.BroadcastGetAdvAddrType(); + } + int broadcastId; + byte[] broadcast_id = mBAService.getBroadcastId(); + broadcastId = (0x00FF0000 & (broadcast_id[2] << 16)); + broadcastId |= (0x0000FF00 & (broadcast_id[1] << 8)); + broadcastId |= (0x000000FF & broadcast_id[0]); + mService.updatePAResultsMap(srcInfo.getSourceDevice(), colocatedAddressType, + mBAService.BroadcatGetAdvHandle(), + mBAService.BroadcatGetAdvHandle(), + mBAService.BroadcastGetAdvInterval(), + broadcastId); + } + res = mService.getPAResults(srcInfo.getSourceDevice()); + } + if (res != null) { + int syncHandle = res.mSyncHandle; + log("processPASyncState: syncHandle" + res.mSyncHandle); + if (mNoPast == false && syncHandle != BCService.INVALID_SYNC_HANDLE) { + if (isAddedBroadcastSourceIsLocal(srcInfo.getSourceDevice())) { + log("Collocated Case Initiate PAST for :" + mDevice + "syncHandle:" + syncHandle + + "serviceData" + serviceData); + serviceData = 0x000000FF & srcInfo.getSourceId(); + serviceData = serviceData << 8; + //advA matches EXT_ADV_ADDRESS + //but not matches source address (as we would have written pAddr) + serviceData = serviceData & (~BROADCAST_ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS); + serviceData = serviceData | (BROADCAST_ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS); + try { + mPeriodicAdvManager.transferSetInfo(mDevice, serviceData, syncHandle,mPeriodicAdvCallback); + } catch (IllegalArgumentException ex) { + Log.w(TAG, "transferSetInfo: IllegalArgumentException : PAST failure"); + //ignore + } + } else { + serviceData = 0x000000FF & srcInfo.getSourceId(); + serviceData = serviceData << 8; + //advA matches EXT_ADV_ADDRESS + //also matches source address (as we would have written) + serviceData = serviceData & (~BROADCAST_ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS); + serviceData = serviceData & (~BROADCAST_ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS); + log("Initiate PAST for :" + mDevice + "syncHandle:" + syncHandle + + "serviceData" + serviceData); + mPeriodicAdvManager.transferSync(mDevice, serviceData, syncHandle); + } + } + } else { + Log.e(TAG, "There is no valid sync handle for this Source"); + if (mAutoAssist) { + //initiate Auto Assist procedure for this device + mService.getBassUtils().triggerAutoAssist (srcInfo); + } + } + } + else if (srcInfo.getAudioSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED || + srcInfo.getMetadataSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_NO_PAST) { + //Cancel the existing sync and Invalidate the sync handle + if (isAddedBroadcastSourceIsLocal(srcInfo.getSourceDevice()) == false) { + if (mSyncingOnBehalfOfGroup == false) { + //Cancel the Sync only If it is NOT syced on behalf of group. + //group based sync will be kept active PSYNC_ACTIVE_TIMEOUT seconds so that + //all group members can get back in sync + log("Unregister sync as It is non colocated"); + cancelActiveSync(srcInfo.getSourceDevice()); + } + } else { + //trigger scan stop here + mService.stopScanOffloadInternal(mDevice, false); + } + } + } + + /*Actual OTA advertising address based check + Use this after the addition of Broadcast source*/ + private boolean isAddedBroadcastSourceIsLocal (BluetoothDevice device) + { + if (device == null) { + Log.e(TAG, "device handle is null"); + return false; + } + String localBroadcasterAddr = null; + ///*_BMS + if (mPublicAddrForcSrc) { + localBroadcasterAddr = BluetoothAdapter.getDefaultAdapter().getAddress(); + } else { + if (mBAService == null) { + mBAService = BroadcastService.getBroadcastService(); + } + if (mBAService == null || mBAService.isBroadcastActive() != true) { + Log.e(TAG, "isAddedBroadcastSourceIsLocal: colocated source handle is unavailable"); + return false; + } + localBroadcasterAddr = mBAService.BroadcastGetAdvAddress(); + } + //_BMS*/ + boolean ret = false; + if (localBroadcasterAddr == null) { + Log.e(TAG, "isAddedBroadcastSourceIsLocal: localBroadcasterAddr is null"); + ret = false; + } else { + ret = localBroadcasterAddr.equals(device.getAddress()); + } + log("isAddedBroadcastSourceIsLocal returns" +ret); + return ret; + } + + private void checkAndUpdateBroadcastCode(BleBroadcastSourceInfo srcInfo) { + if (isAddedBroadcastSourceIsLocal(srcInfo.getSourceDevice())) { + if (mForceSB == true || + srcInfo.getEncryptionStatus() == BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED) { + //query the Encryption Key from BMS and update + ///*_BMS + byte[] colocatedBcastCode = mBAService.GetEncryptionKey(null); + if (mBAService.isBroadcastStreamingEncrypted() == false) { + Log.e(TAG, "seem to be Unencrypted colocated broadcast"); + //do nothing + return; + } + log("colocatedBcastCode is " + colocatedBcastCode); + //queue a fresh command to update the + Message m = obtainMessage(BassClientStateMachine.SET_BCAST_CODE); + m.obj = srcInfo; + m.arg1 = FRESH; + log("checkAndUpdateBroadcastCode: src device: " + srcInfo.getSourceDevice()); + sendMessage(m); + //_BMS*/ + } + } else { + log("checkAndUpdateBroadcastCode"); + //non colocated case, Broadcast PIN should have been updated from lyaer + //If there is pending one process it Now + if (srcInfo.getEncryptionStatus() == BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED && + mSetBroadcastCodePending == true) { + //Make a QUEUED command + log("Update the Broadcast now"); + Message m = obtainMessage(BassClientStateMachine.SET_BCAST_CODE); + m.obj = mSetBroadcastPINSrcInfo; + m.arg1 = QUEUED; + + sendMessage(m); + mSetBroadcastCodePending = false; + mSetBroadcastPINSrcInfo = null; + } + } + } + + private List getListOfBisIndicies(int bisIndicies, int subGroupId, byte[] metaData) { + List bcastIndicies = new ArrayList(); + int index =0; + log("getListOfBisIndicies:" + bisIndicies); + while (bisIndicies != 0) { + if ((bisIndicies & 0x00000001) == 0x00000001) { + BleBroadcastSourceChannel bI = + new BleBroadcastSourceChannel(index, Integer.toString(index), true, subGroupId, metaData); + bcastIndicies.add(bI); + log("Adding BIS index for :" + index); + } + bisIndicies = bisIndicies>>1; + index++; + } + return bcastIndicies; + + } + + private void processBroadcastReceiverState (byte[] receiverState, BluetoothGattCharacteristic characteristic) { + int index = -1; + boolean isEmptyEntry = false; + BleBroadcastSourceInfo srcInfo = null; + + log("processBroadcastReceiverState:: characteristic:" + characteristic); + + byte sourceId = 0; + if (receiverState.length > 0) + sourceId = receiverState[BCAST_RCVR_STATE_SRC_ID_IDX]; + log("processBroadcastReceiverState: receiverState length: " + receiverState.length); + if (receiverState.length == 0 || + isEmpty(Arrays.copyOfRange(receiverState, 1, receiverState.length-1))) { + log("This is an Empty Entry"); + if (mPendingOperation == REMOVE_BCAST_SOURCE) { + srcInfo = new BleBroadcastSourceInfo(mPendingSourceId); + } else if (receiverState.length == 0) { + if (mBleBroadcastSourceInfos != null) { + mTempSourceId = (byte)mBleBroadcastSourceInfos.size(); + } + if (mTempSourceId < NUM_OF_BROADCAST_RECEIVER_STATES) { + mTempSourceId++; + srcInfo = new BleBroadcastSourceInfo(mTempSourceId); + } else { + Log.e(TAG, "reached the remote supported max SourceInfos"); + return; + } + } + isEmptyEntry = true; + //just create an Empty entry + if (srcInfo.isEmptyEntry()) { + log("An empty entry has been created"); + } + } + else { + byte sourceAddressType = receiverState[BCAST_RCVR_STATE_SRC_ADDR_TYPE_IDX]; + byte[] sourceAddress = new byte[BCAST_RCVR_STATE_SRC_ADDR_SIZE]; + System.arraycopy(receiverState, BCAST_RCVR_STATE_SRC_ADDR_START_IDX, sourceAddress, 0, BCAST_RCVR_STATE_SRC_ADDR_SIZE); + byte sourceAdvSid = receiverState[BCAST_RCVR_STATE_SRC_ADV_SID_IDX]; + + byte[] broadcastIdBytes = new byte[BROADCAST_SOURCE_ID_LENGTH]; + System.arraycopy(receiverState, BCAST_RCVR_STATE_SRC_BCAST_ID_START_IDX, broadcastIdBytes, 0, BROADCAST_SOURCE_ID_LENGTH); + int broadcastId = (0x00FF0000 & (broadcastIdBytes[2] << 16)); + broadcastId |= (0x0000FF00 & (broadcastIdBytes[1] << 8)); + broadcastId |= (0x000000FF & broadcastIdBytes[0]); + + BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); + BluetoothDevice device = btAdapter.getRemoteDevice(reverseBytes(sourceAddress)); + byte metaDataSyncState = receiverState[BCAST_RCVR_STATE_PA_SYNC_IDX]; + + + byte encyptionStatus = receiverState[BCAST_RCVR_STATE_ENC_STATUS_IDX]; + byte[] badBroadcastCode = null; + byte badBroadcastCodeLen = 0; + byte numSubGroups = 0; + byte[] metadataLength = null; + byte[] metaData = null; + if (encyptionStatus == BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_BADCODE) { + badBroadcastCode = new byte[BCAST_RCVR_STATE_BADCODE_SIZE]; + System.arraycopy(receiverState, BCAST_RCVR_STATE_BADCODE_START_IDX, badBroadcastCode, 0, BCAST_RCVR_STATE_BADCODE_SIZE); + badBroadcastCode = reverseBytes(badBroadcastCode); + badBroadcastCodeLen = BCAST_RCVR_STATE_BADCODE_SIZE; + } + numSubGroups = receiverState[BCAST_RCVR_STATE_BADCODE_START_IDX + badBroadcastCodeLen]; + int offset = BCAST_RCVR_STATE_BADCODE_START_IDX + badBroadcastCodeLen + 1; + //Map of Bis Status + Map> bisIndexList = new HashMap>(); + //Map for Metada + Map metadataList = new HashMap(); + metadataLength = new byte[numSubGroups]; + byte audioSyncState = 0; + for (int i = 0; i < numSubGroups; i++) { + byte[] audioSyncIndex = new byte[BCAST_RCVR_STATE_BIS_SYNC_SIZE]; + System.arraycopy(receiverState, offset, audioSyncIndex, 0, BCAST_RCVR_STATE_BIS_SYNC_SIZE); + offset = offset + BCAST_RCVR_STATE_BIS_SYNC_SIZE; + log("BIS index byte array: "); + BassUtils.printByteArray(audioSyncIndex); + ByteBuffer wrapped = ByteBuffer.wrap(reverseBytes(audioSyncIndex)); + int audioBisIndex = wrapped.getInt(); + if (audioBisIndex == 0xFFFFFFFF) { + log("Remote failed to sync to BIS"); + audioSyncState = 0x00; + audioBisIndex = 0; + } else { + //Bits (0-30)=> (1-31) + audioBisIndex = audioBisIndex << 1; + log("BIS index converted: " + audioBisIndex); + if (audioBisIndex != 0){ + //If any BIS is in sync, Set Audio state as ON + audioSyncState = 0x01; + } + } + + metadataLength[i] = receiverState[offset++]; + if (metadataLength[i] != 0) { + log("metadata of length: " + metadataLength[i] + "is avaialble"); + metaData = new byte[metadataLength[i]]; + System.arraycopy(receiverState, offset, metaData, 0, metadataLength[i]); + offset = offset + metadataLength[i]; + metaData = reverseBytes(metaData); + metadataList.put(i, metaData); + } + bisIndexList.put(i, getListOfBisIndicies(audioBisIndex, i, metaData)); + } + srcInfo = new BleBroadcastSourceInfo(device, + sourceId, + sourceAdvSid, + broadcastId, + (int)sourceAddressType, + (int)metaDataSyncState, + (int)encyptionStatus, + badBroadcastCode, + numSubGroups, + (int)audioSyncState, + bisIndexList, + metadataList + ); + } + BleBroadcastSourceInfo oldSourceInfo = mBleBroadcastSourceInfos.get(characteristic.getInstanceId()); + if (oldSourceInfo == null) { + log("Initial Read and Populating values"); + if (mBleBroadcastSourceInfos.size() == NUM_OF_BROADCAST_RECEIVER_STATES) { + Log.e(TAG, "reached the Max SourceInfos"); + return; + } + mBleBroadcastSourceInfos.put(characteristic.getInstanceId(), srcInfo); + checkAndUpdateBroadcastCode(srcInfo); + processPASyncState(srcInfo); + } else { + log("old sourceInfo: " + oldSourceInfo); + log("new sourceInfo: " + srcInfo); + mBleBroadcastSourceInfos.replace(characteristic.getInstanceId(), srcInfo); + if (oldSourceInfo.isEmptyEntry() == true) { + log("New Source Addition"); + sendPendingCallbacks(ADD_BCAST_SOURCE, + srcInfo.getSourceId(), BluetoothGatt.GATT_SUCCESS); + checkAndUpdateBroadcastCode(srcInfo); + processPASyncState(srcInfo); + } else { + if (isEmptyEntry) { + BluetoothDevice removedDevice = oldSourceInfo.getSourceDevice(); + log("sourceInfo removal" + removedDevice); + cancelActiveSync(removedDevice); + sendPendingCallbacks(REMOVE_BCAST_SOURCE, + srcInfo.getSourceId(), BluetoothGatt.GATT_SUCCESS); + } else { + log("update to an existing srcInfo"); + sendPendingCallbacks(UPDATE_BCAST_SOURCE, + srcInfo.getSourceId(),BluetoothGatt.GATT_SUCCESS); + processPASyncState(srcInfo); + checkAndUpdateBroadcastCode(srcInfo); + } + } + } + index = srcInfo.getSourceId(); + if (index == -1) { + log("processBroadcastReceiverState: invalid index"); + return; + } + broadcastReceiverState(srcInfo, index, NUM_OF_BROADCAST_RECEIVER_STATES); + } + // Implements callback methods for GATT events that the app cares about. For example, + // connection change and services discovered. + private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { + @Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + boolean isStateChanged = false; + log( "onConnectionStateChange : Status=" + status + "newState" + newState); + if (newState == BluetoothProfile.STATE_CONNECTED && getConnectionState() != BluetoothProfile.STATE_CONNECTED) { + isStateChanged = true; + Log.w(TAG, "Bassclient Connected from Disconnected state: " + mDevice); + if (mService.okToConnect(mDevice)) { + log("Bassclient Connected to: " + mDevice); + if (mBluetoothGatt != null) { + log( "Attempting to start service discovery:" + + mBluetoothGatt.discoverServices()); + mDiscoveryInitiated = true; + } + } else { + if (mBluetoothGatt != null) { + // Reject the connection + Log.w(TAG, "Bassclient Connect request rejected: " + mDevice); + mBluetoothGatt.disconnect(); + mBluetoothGatt.close(); + mBluetoothGatt = null; + //force move to disconnected + newState = BluetoothProfile.STATE_DISCONNECTED; + } + } + } else if (newState == BluetoothProfile.STATE_DISCONNECTED && + getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { + isStateChanged = true; + log( "Disconnected from Bass GATT server."); + } + if (isStateChanged) { + Message m = obtainMessage(CONNECTION_STATE_CHANGED); + m.obj = newState; + sendMessage(m); + } + } + @Override + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + log("onServicesDiscovered:" + status); + if (mDiscoveryInitiated == true) { + mDiscoveryInitiated = false; + if (status == BluetoothGatt.GATT_SUCCESS && mBluetoothGatt != null) { + mBluetoothGatt.requestMtu(BASS_MAX_BYTES); + mMTUChangeRequested = true; + } else { + Log.w(TAG, "onServicesDiscovered received: " + status + + "mBluetoothGatt" + mBluetoothGatt); + } + } else { + log("remote initiated callback"); + //do nothing + } + } + @Override + public void onCharacteristicRead(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic, + int status) { + log( "onCharacteristicRead:: status: " + status + "char:" + characteristic); + + if (status == BluetoothGatt.GATT_SUCCESS && + characteristic.getUuid().equals(BASS_BCAST_RECEIVER_STATE)) { + log( "onCharacteristicRead: BASS_BCAST_RECEIVER_STATE: status" + status); + logByteArray("Received ", characteristic.getValue(), 0, + characteristic.getValue().length); + if (characteristic.getValue() == null) { + Log.e(TAG, "Remote receiver state is NULL"); + return; + } + processBroadcastReceiverState(characteristic.getValue(), characteristic); + } + // switch to receiving notifications after initial characteristic read + BluetoothGattDescriptor desc = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG); + if (mBluetoothGatt != null && desc != null) { + log("Setting the value for Desc"); + mBluetoothGatt.setCharacteristicNotification(characteristic, true); + desc.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + mBluetoothGatt.writeDescriptor(desc); + } else { + Log.w(TAG, "CCC for " + characteristic + "seem to be not present"); + //atleast move the SM to stable state + Message m = obtainMessage(GATT_TXN_PROCESSED); + m.arg1 = status; + sendMessage(m); + } + } + + @Override + public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, + int status) { + log("onDescriptorWrite"); + if (status == BluetoothGatt.GATT_SUCCESS && + descriptor.getUuid().equals(CLIENT_CHARACTERISTIC_CONFIG)) { + log("CCC write resp"); + } + + //Move the SM to connected so further reads happens + Message m = obtainMessage(GATT_TXN_PROCESSED); + m.arg1 = status; + sendMessage(m); + } + + @Override + public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) + { + log("onMtuChanged: mtu:" + mtu); + if (mMTUChangeRequested == true && mBluetoothGatt != null) { + acquireAllBassChars(); + mMTUChangeRequested = false; + } else { + log("onMtuChanged is remote initiated trigger, mBluetoothGatt:" + mBluetoothGatt); + //Do nothing + } + } + + @Override + public void onCharacteristicChanged(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic) { + log( "onCharacteristicChanged :: " + + characteristic.getUuid().toString()); + if (characteristic.getUuid().equals(BASS_BCAST_RECEIVER_STATE)) { + log( "onCharacteristicChanged is rcvr State :: " + + characteristic.getUuid().toString()); + if (characteristic.getValue() == null) { + Log.e(TAG, "Remote receiver state is NULL"); + return; + } + logByteArray("onCharacteristicChanged: Received ", characteristic.getValue(), 0, + characteristic.getValue().length); + processBroadcastReceiverState(characteristic.getValue(), characteristic); + } + } + + @Override + public void onCharacteristicWrite(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic, int status) { + log( "onCharacteristicWrite: " + + characteristic.getUuid().toString() + + "status:" + status); + if (status == 0 && + characteristic.getUuid().equals(BASS_BCAST_AUDIO_SCAN_CTRL_POINT)) { + log( "BASS_BCAST_AUDIO_SCAN_CTRL_POINT is written successfully"); + } + Message m = obtainMessage(GATT_TXN_PROCESSED); + m.arg1 = status; + sendMessage(m); + } + }; + + public List getAllBroadcastSourceInformation() { + log( "getAllBroadcastSourceInformation"); + List list = new ArrayList(mBleBroadcastSourceInfos.values()); + return list; + } + + void acquireAllBassChars() { + clearCharsCache(); + BluetoothGattService service = null; + if (mBluetoothGatt != null) { + log("getting Bass Service handle"); + service = mBluetoothGatt.getService(BASS_UUID); + } + if (service != null) { + log( "found BASS_SERVICE"); + List allChars = service.getCharacteristics(); + int numOfChars = allChars.size(); + NUM_OF_BROADCAST_RECEIVER_STATES = numOfChars-1; + log( "Total number of chars" + numOfChars); + //static var to keep track of read callbacks + num_of_recever_states = NUM_OF_BROADCAST_RECEIVER_STATES; + for (int i = 0; i < allChars.size(); i++) { + if (allChars.get(i).getUuid().equals(BASS_BCAST_AUDIO_SCAN_CTRL_POINT)) { + mBroadcastScanControlPoint = allChars.get(i); + log( "Index of ScanCtrlPoint:" + i); + } else { + log( "Reading " + i + "th ReceiverState"); + mBroadcastReceiverStates.add(allChars.get(i)); + Message m = obtainMessage(READ_BASS_CHARACTERISTICS); + m.obj = allChars.get(i); + sendMessage(m); + } + } + } else { + Log.e(TAG, "acquireAllBassChars: BASS service not found"); + } + } + + void clearCharsCache() { + if (mBroadcastReceiverStates != null) { + mBroadcastReceiverStates.clear(); + } + if (mBroadcastScanControlPoint != null) { + mBroadcastScanControlPoint = null; + } + num_of_recever_states = 0; + if (mBleBroadcastSourceInfos != null) { + mBleBroadcastSourceInfos.clear(); + } + mPendingOperation = -1; + } + + @VisibleForTesting + class Disconnected extends State { + @Override + public void enter() { + log( "Enter Disconnected(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + clearCharsCache(); + mTempSourceId = 0; + removeDeferredMessages(DISCONNECT); + + if (mLastConnectionState == -1) { + log( "no Broadcast of initial profile state "); + } else { + if (mPacsAvail == true) { + /*_PACS + PacsClientService mPacsClientService = PacsClientService.getPacsClientService(); + if (mPacsClientService != null) { + log("trigger disconnect to Pacs"); + mPacsClientService.disconnect(mDevice); + } else { + Log.e(TAG, "PACs interface is null"); + } + _PACS*/ + } + + ///*_VCP + if (mVcpForBroadcast) { + VcpController vcpController = VcpController.getVcpController(); + if (vcpController != null) { + log("trigger disconnect to Vcp Renderer"); + if (!vcpController.disconnect(mDevice, BluetoothVcp.MODE_BROADCAST)) { + log("Disconnect Vcp failed"); + } + } else { + Log.e(TAG, "VcpController interface is null"); + } + } + //_VCP*/ + broadcastConnectionState(mDevice, mLastConnectionState, + BluetoothProfile.STATE_DISCONNECTED); + } + } + + @Override + public void exit() { + log("Exit Disconnected(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_DISCONNECTED; + } + + @Override + public boolean processMessage(Message message) { + log("Disconnected process message(" + mDevice + "): " + messageWhatToString( + message.what)); + + switch (message.what) { + case CONNECT: + log("Connecting to " + mDevice); + if (mBluetoothGatt != null) { + Log.d(TAG, "clear off, pending wl connection"); + mBluetoothGatt.disconnect(); + mBluetoothGatt.close(); + mBluetoothGatt = null; + } + + if ((mBluetoothGatt = mDevice.connectGatt(mService, mIsWhitelist, mGattCallback, + BluetoothDevice.TRANSPORT_LE, false, (BluetoothDevice.PHY_LE_1M_MASK | + BluetoothDevice.PHY_LE_2M_MASK | BluetoothDevice.PHY_LE_CODED_MASK), + null, true)) == null) { + Log.e(TAG, "Disconnected: error connecting to " + mDevice); + break; + } else { + transitionTo(mConnecting); + } + break; + case DISCONNECT: + Log.w(TAG, "Disconnected: DISCONNECT ignored: " + mDevice); + break; + case CONNECTION_STATE_CHANGED: + int state = (int)message.obj; + Log.w(TAG, "connection state changed:" + state); + if (state == BluetoothProfile.STATE_CONNECTED) { + log("remote/wl connection, ensure csip is up as well"); + if (mNoCSIPReconn == false && mService != null && + mService.isLockSupportAvailable(mDevice)) { + /////*_CSIP + mCsipConnected = false; + mSetCoordinator.connect(mService.mCsipAppId, mDevice); + transitionTo(mConnecting); + break; + ////_CSIP*/ + } else { + transitionTo(mConnected); + } + } else { + Log.w(TAG, "Disconected: Connection failed to " + mDevice); + } + break; + case PSYNC_ACTIVE_TIMEOUT: + cancelActiveSync(null); + break; + default: + log("DISCONNECTED: not handled message:" + message.what); + return NOT_HANDLED; + } + return HANDLED; + } + } + + @VisibleForTesting + class Connecting extends State { + @Override + public void enter() { + log( "Enter Connecting(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + sendMessageDelayed(CONNECT_TIMEOUT, mDevice, mConnectTimeoutMs); + broadcastConnectionState(mDevice, mLastConnectionState, + BluetoothProfile.STATE_CONNECTING); + } + + @Override + public void exit() { + log("Exit Connecting(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_CONNECTING; + removeMessages(CONNECT_TIMEOUT); + } + + @Override + public boolean processMessage(Message message) { + log("Connecting process message(" + mDevice + "): " + messageWhatToString( + message.what)); + + switch (message.what) { + case CONNECT: + log("Already Connecting to " + mDevice); + log("Ignore this connection request " + mDevice); + break; + case DISCONNECT: + Log.w(TAG, "Connecting: DISCONNECT deferred: " + mDevice); + deferMessage(message); + break; + case READ_BASS_CHARACTERISTICS: + Log.w(TAG, "defer READ_BASS_CHARACTERISTICS requested!: " + mDevice); + deferMessage(message); + break; + case CSIP_CONNECTION_STATE_CHANGED: + int state = (int)message.obj; + if (state == BluetoothProfile.STATE_CONNECTED) { + ///*_CSIP + if (mCsipConnected == true) { + Log.e(TAG, "CSIP is already up, ignore this DUP event"); + break; + } + mCsipConnected = true; + Log.d(TAG, "Csip connected"); + transitionTo(mConnected); + } else { + Log.w(TAG, "CSIP Connection failed to " + mDevice); + if (mBluetoothGatt != null) { + //disc bass + mBluetoothGatt.disconnect(); + mBluetoothGatt.close(); + mBluetoothGatt = null; + } + transitionTo(mDisconnected); + } + + break; + case CONNECTION_STATE_CHANGED: + state = (int)message.obj; + Log.w(TAG, "Connecting: connection state changed:" + state); + if (state == BluetoothProfile.STATE_CONNECTED) { + if (mService != null && + mService.isLockSupportAvailable(mDevice)) { + ///*_CSIP + //If Lock support available & connect to csip + mCsipConnected = false; + mSetCoordinator.connect(mService.mCsipAppId, mDevice); + break; + //_CSIP*/ + } else { + transitionTo(mConnected); + } + } else { + Log.w(TAG, "Connection failed to " + mDevice); + transitionTo(mDisconnected); + } + break; + case CONNECT_TIMEOUT: + Log.w(TAG, "CONNECT_TIMEOUT"); + BluetoothDevice device = (BluetoothDevice) message.obj; + if (!mDevice.equals(device)) { + Log.e(TAG, "Unknown device timeout " + device); + break; + } + transitionTo(mDisconnected); + break; + case PSYNC_ACTIVE_TIMEOUT: + deferMessage(message); + break; + default: + log("CONNECTING: not handled message:" + message.what); + return NOT_HANDLED; + } + return HANDLED; + } + } + + private byte[] reverseBytes(byte[] a) { + if (mNoReverse) { + log("no reverse is enabled>"); + return a; + } + for(int i=0; i bisIndicies, int subGroupId) { + int audioBisIndex = 0; + if (bisIndicies != null) { + for (int i=0; i>> 8); + res[11] = (byte)((paRes.mBroadcastId & 0x0000000000FF0000) >>> 16); + if (mDefNoPAS == false && + srcInfo.getMetadataSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC) { + res[12] = (byte)(0x01); + } else { + log("setting PA sync to ZERO"); + res[12] = (byte)0x00; + } + + res[13] = (byte)(paRes.mPAInterval & 0x00000000000000FF); + res[14] = (byte)((paRes.mPAInterval & 0x000000000000FF00)>>>8); + + res[15] = base_.getNumberOfSubgroupsofBIG(); + + int offset = 16; + for (int i=0; i>>8); + res[offset++] = (byte)((bisIndexValue & 0x0000000000FF0000)>>>16); + res[offset++] = (byte)((bisIndexValue & 0x00000000FF000000)>>>24); + + res[offset++] = metaDataLength[i]; + if (metaDataLength[i] != 0) { + if (isLocalBroadcastSource(broadcastSource) == false) { + byte[] revMetadata = reverseBytes(base_.getMetadata(i)); + System.arraycopy(revMetadata, 0, res, offset, metaDataLength[i]); + } else { + System.arraycopy(base_.getMetadata(i), 0, res, offset, metaDataLength[i]); + } + } + offset = offset + metaDataLength[i]; + } + + log("ADD_BCAST_SOURCE in Bytes"); + BassUtils.printByteArray(res); + return res; + } + + private byte[] convertSourceInfoToUpdateSourceByteArray(BleBroadcastSourceInfo srcInfo) { + byte[] res; + int updateSourceFixedLength = 6; + BCService.PAResults paRes = null; + BleBroadcastSourceInfo existingSI = getBroadcastSourceInfoForSourceId(srcInfo.getSourceId()); + if (existingSI == null) { + log("no existing SI for update source op"); + return null; + } + + byte numSubGroups = existingSI.getNumberOfSubGroups(); + //on Modify source, dont update any Metadata + byte metaDataLength = 0; + res = new byte [updateSourceFixedLength + numSubGroups*5]; + + res[0] = BASS_UPDATE_SOURCE_OPCODE; + res[1] = srcInfo.getSourceId(); + + if (srcInfo.getMetadataSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC) { + res[2] = (byte)(0x01); + } else { + res[2] = (byte)0x00; + } + //update these from existing SI + BluetoothDevice existingSrcDevice = existingSI.getSourceDevice(); + if (isAddedBroadcastSourceIsLocal(existingSrcDevice)) { + int paInterval = 0x0000FFFF; + paInterval = mBAService.BroadcastGetAdvInterval(); + res[4] = (byte)((paInterval & 0x000000000000FF00)>>>8); + res[3] = (byte)(paInterval & 0x00000000000000FF); + } else { + //for non-c mmodify op, set PA Interval as UNKNOWN + res[4] = (byte)0xFF; + res[3] = (byte)0xFF; + } + //For modify op, just set number of Subgroups as UNKNOWN + //ZERO is treated as UNKNOWN + res[5] = numSubGroups; + + int offset = 6; + int bisIndexValue = 0; + Map bisIndexList = existingSI.getBisIndexList(); + if (srcInfo.getAudioSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED) { + //Force BIS index value to NO_PREF for modify SRC + bisIndexValue = 0xFFFFFFFF; + } else { + bisIndexValue = 0x00000000; + } + log("UPDATE_BCAST_SOURCE b4: bisIndexValue : " + bisIndexValue); + //If there is an empty List, set NO pref all subgroups + if (bisIndexList == null || bisIndexList.size() == 0) { + bisIndexValue = 0xFFFFFFFF; + } + for (int i=0; i>>8); + res[offset++] = (byte)((bisIndexValue & 0x0000000000FF0000)>>>16); + res[offset++] = (byte)((bisIndexValue & 0x00000000FF000000)>>>24); + + res[offset++] = metaDataLength; + } + log("UPDATE_BCAST_SOURCE in Bytes"); + BassUtils.printByteArray(res); + return res; + } + + private byte[] convertAsciitoValues (byte[] val) { + byte[] ret = new byte[val.length]; + for (int i=0; i< val.length; i++) { + ret[i] = (byte)(val[i] - (byte)'0'); + } + log("convertAsciitoValues: returns:" + Arrays.toString(val)); + return ret; + } + + private byte[] convertSourceInfoToSetBroadcastCodeByteArray(BleBroadcastSourceInfo srcInfo) { + + byte[] res = new byte[PIN_CODE_CMD_LEN]; + res[0] = BASS_SET_BCAST_PIN_OPCODE; + res[1] = srcInfo.getSourceId(); + log("convertSourceInfoToSetBroadcastCodeByteArray: Source device : " + srcInfo.getSourceDevice()); + byte[] actualPIN = null; + //srcInfo.getSourceDevice() will be NULL if this request coming from SDK + // srcInfo.getSourceDevice() will have valid Source device only If this is + //collocated device + if (srcInfo.getSourceDevice() != null && + isAddedBroadcastSourceIsLocal(srcInfo.getSourceDevice())) { + //colocated Source addition + //query the Encryption Key from BMS and update + ///*_BMS + actualPIN = mBAService.GetEncryptionKey(null); + //_BMS*/ + log("colocatedBcastCode is " + Arrays.toString(actualPIN)); + } else { + //Can Keep as ASCII as is + String reversePIN = new StringBuffer(srcInfo.getBroadcastCode()).reverse().toString(); + actualPIN = reversePIN.getBytes(); + } + if (actualPIN == null) { + Log.e(TAG, "actual PIN is null"); + return null; + } else { + log( "byte array broadcast Code:" + Arrays.toString(actualPIN)); + log( "pinLength:" + actualPIN.length); + + //Fill the PIN code in the Last Position + System.arraycopy(actualPIN, 0, res, ((PIN_CODE_CMD_LEN)-actualPIN.length), actualPIN.length); + + log("SET_BCAST_PIN in Bytes"); + BassUtils.printByteArray(res); + } + return res; + } + + private boolean IsItRightTimeToUpdateBroadcastPIN(byte srcId) { + Collection srcInfos = mBleBroadcastSourceInfos.values(); + Iterator iterator = srcInfos.iterator(); + boolean ret = false; + if (mForceSB) { + log("force SB is set"); + return true; + } + while (iterator.hasNext()) { + BleBroadcastSourceInfo sI = iterator.next(); + if (sI == null) { + log("src Info is null"); + continue; + } + if (srcId == sI.getSourceId() && + sI.getEncryptionStatus() == BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED) { + ret = true; + break; + } + } + log("IsItRightTimeToUpdateBroadcastPIN returning:" + ret); + return ret; + } + + @VisibleForTesting + class Connected extends State { + @Override + public void enter() { + log( "Enter Connected(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + + removeDeferredMessages(CONNECT); + if (mLastConnectionState == BluetoothProfile.STATE_CONNECTED) { + log("CONNECTED->CONNTECTED: Ignore"); + } else { + broadcastConnectionState(mDevice, mLastConnectionState, + BluetoothProfile.STATE_CONNECTED); + //initialize PACs for this devices + if (mPacsAvail == true) { + /* + PacsClientService mPacsClientService = PacsClientService.getPacsClientService(); + if (mPacsClientService != null) { + log("trigger connect to Pacs"); + mPacsClientService.connect(mDevice); + } else { + Log.e(TAG, "PACs interface is null"); + } + */ + } + + ///*_VCP + if (mVcpForBroadcast) { + VcpController vcpController = VcpController.getVcpController(); + if (vcpController != null) { + log("trigger connect to Vcp Renderer"); + if (!vcpController.connect(mDevice, BluetoothVcp.MODE_BROADCAST)) { + log("Connect vcp failed"); + } + } else { + Log.e(TAG, "VcpController interface is null"); + } + } + //_VCP*/ + } + } + + @Override + public void exit() { + log("Exit Connected(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_CONNECTED; + } + + @Override + public boolean processMessage(Message message) { + log("Connected process message(" + mDevice + "): " + + messageWhatToString(message.what)); + BleBroadcastSourceInfo srcInfo; + switch (message.what) { + case CONNECT: + Log.w(TAG, "Connected: CONNECT ignored: " + mDevice); + break; + case DISCONNECT: + log("Disconnecting from " + mDevice); + if (mBluetoothGatt != null) { + mBluetoothGatt.disconnect(); + mBluetoothGatt.close(); + mBluetoothGatt = null; + //transitionTo(mDisconnecting); + cancelActiveSync(null); + //Trigger the CSip disconnection, dont worry about pass/failure + if (mCsipConnected && mSetCoordinator != null) { + mSetCoordinator.disconnect(mService.mCsipAppId, mDevice); + mCsipConnected = false; + } + transitionTo(mDisconnected); + } else { + log("mBluetoothGatt is null"); + } + break; + case CONNECTION_STATE_CHANGED: + int state = (int)message.obj; + Log.w(TAG, "Connected:connection state changed:" + state); + if (state == BluetoothProfile.STATE_CONNECTED) { + Log.w(TAG, "device is already connected to Bass" + mDevice); + } else { + Log.w(TAG, "unexpected disconnected from " + mDevice); + cancelActiveSync(null); + ///*_CSIP + //Trigger the CSip disconnection, dont worry about pass/failure + if (mCsipConnected) { + mSetCoordinator.disconnect(mService.mCsipAppId, mDevice); + mCsipConnected = false; + } + //_CSIP*/ + transitionTo(mDisconnected); + } + break; + case READ_BASS_CHARACTERISTICS: + BluetoothGattCharacteristic characteristic = (BluetoothGattCharacteristic)message.obj; + if (mBluetoothGatt != null) { + mBluetoothGatt.readCharacteristic(characteristic); + transitionTo(mConnectedProcessing); + } else { + Log.e(TAG, "READ_BASS_CHARACTERISTICS is ignored, Gatt handle is null"); + } + break; + case START_SCAN_OFFLOAD: + if (mBluetoothGatt != null && + mBroadcastScanControlPoint != null) { + mBroadcastScanControlPoint.setValue(REMOTE_SCAN_START); + mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint); + mPendingOperation = message.what; + transitionTo(mConnectedProcessing); + } else { + log("no Bluetooth Gatt handle, may need to fetch write"); + } + break; + case STOP_SCAN_OFFLOAD: + if (mBluetoothGatt != null && + mBroadcastScanControlPoint != null) { + mBroadcastScanControlPoint.setValue(REMOTE_SCAN_STOP); + mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint); + mPendingOperation = message.what; + transitionTo(mConnectedProcessing); + } else { + log("no Bluetooth Gatt handle, may need to fetch write"); + } + break; + case SELECT_BCAST_SOURCE: + ScanResult scanRes = (ScanResult)message.obj; + boolean auto = ((int) message.arg1) == AUTO; + boolean isGroupOp = ((int) message.arg2) == GROUP_OP; + selectBroadcastSource(scanRes, isGroupOp, auto); + break; + case ADD_BCAST_SOURCE: + srcInfo = (BleBroadcastSourceInfo)message.obj; + log("Adding Broadcast source" + srcInfo); + byte[] addSourceInfo = convertSourceInfoToAddSourceByteArray(srcInfo); + if (addSourceInfo == null) { + Log.e(TAG, "add source: source Info is NULL"); + break; + } + if (mBluetoothGatt != null && + mBroadcastScanControlPoint != null) { + mBroadcastScanControlPoint.setValue(addSourceInfo); + mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint); + mPendingOperation = message.what; + transitionTo(mConnectedProcessing); + sendMessageDelayed(GATT_TXN_TIMEOUT, GATT_TXN_TIMEOUT_MS); + } else { + Log.e(TAG, "ADD_BCAST_SOURCE: no Bluetooth Gatt handle, Fatal"); + sendPendingCallbacks(ADD_BCAST_SOURCE,INVALID_SRC_ID, + BleBroadcastAudioScanAssistCallback.BASS_STATUS_FATAL); + } + break; + case UPDATE_BCAST_SOURCE: + srcInfo = (BleBroadcastSourceInfo)message.obj; + mAutoTriggerred = ((int) message.arg1) == AUTO; + log("Updating Broadcast source" + srcInfo); + byte[] updateSourceInfo = convertSourceInfoToUpdateSourceByteArray(srcInfo); + if (updateSourceInfo == null) { + Log.e(TAG, "update source: source Info is NULL"); + break; + } + if (mBluetoothGatt != null && + mBroadcastScanControlPoint != null) { + mBroadcastScanControlPoint.setValue(updateSourceInfo); + mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint); + mPendingOperation = message.what; + mPendingSourceId = srcInfo.getSourceId(); + transitionTo(mConnectedProcessing); + sendMessageDelayed(GATT_TXN_TIMEOUT, GATT_TXN_TIMEOUT_MS); + } else { + Log.e(TAG, "UPDATE_BCAST_SOURCE: no Bluetooth Gatt handle, Fatal"); + sendPendingCallbacks(UPDATE_BCAST_SOURCE,INVALID_SRC_ID, + BleBroadcastAudioScanAssistCallback.BASS_STATUS_FATAL); + } + break; + case SET_BCAST_CODE: + srcInfo = (BleBroadcastSourceInfo)message.obj; + int cmdType = message.arg1; + log("SET_BCAST_CODE srcInfo: " + srcInfo); + + if (cmdType != QUEUED && + IsItRightTimeToUpdateBroadcastPIN(srcInfo.getSourceId()) != true) { + mSetBroadcastCodePending = true; + mSetBroadcastPINSrcInfo = srcInfo; + log("Ignore SET_BCAST now, but store it for later"); + //notify so that lock release happens as SET_BCAST_CODE + //queued for future + mService.notifyOperationCompletion(mDevice,SET_BCAST_CODE); + } else { + byte[] setBroadcastPINcmd = convertSourceInfoToSetBroadcastCodeByteArray(srcInfo); + if (setBroadcastPINcmd == null) { + Log.e(TAG, "SET_BCAST_CODE: Broadcast code is NULL"); + break; + } + if (mBluetoothGatt != null && + mBroadcastScanControlPoint != null) { + mBroadcastScanControlPoint.setValue(setBroadcastPINcmd); + mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint); + mPendingOperation = message.what; + mPendingSourceId = srcInfo.getSourceId(); + transitionTo(mConnectedProcessing); + sendMessageDelayed(GATT_TXN_TIMEOUT, GATT_TXN_TIMEOUT_MS); + } else { + Log.e(TAG, "SET_BCAST_CODE: no Bluetooth Gatt handle, Fatal"); + sendPendingCallbacks(SET_BCAST_CODE,INVALID_SRC_ID, + BleBroadcastAudioScanAssistCallback.BASS_STATUS_FATAL); + + } + } + break; + case REMOVE_BCAST_SOURCE: + byte sourceId = (byte)message.arg1; + BluetoothDevice audioSrc = (BluetoothDevice)message.obj; + log("Removing Broadcast source: audioSource:" + audioSrc + "sourceId:" + sourceId); + byte[] removeSourceInfo = new byte [2]; + removeSourceInfo[0] = BASS_REMOVE_SOURCE_OPCODE; + removeSourceInfo[1] = sourceId; + if (mBluetoothGatt != null && + mBroadcastScanControlPoint != null) { + mBroadcastScanControlPoint.setValue(removeSourceInfo); + mBluetoothGatt.writeCharacteristic(mBroadcastScanControlPoint); + mPendingOperation = message.what; + mPendingSourceId = sourceId; + transitionTo(mConnectedProcessing); + sendMessageDelayed(GATT_TXN_TIMEOUT, GATT_TXN_TIMEOUT_MS); + } else { + Log.e(TAG, "REMOVE_BCAST_SOURCE: no Bluetooth Gatt handle, Fatal"); + sendPendingCallbacks(REMOVE_BCAST_SOURCE,INVALID_SRC_ID, + BleBroadcastAudioScanAssistCallback.BASS_STATUS_FATAL); + + } + break; + case PSYNC_ACTIVE_TIMEOUT: + cancelActiveSync(null); + break; + default: + log("CONNECTED: not handled message:" + message.what); + return NOT_HANDLED; + } + return HANDLED; + } + } + + + void sendPendingCallbacks(int pendingOp, byte sourceId, int status) { + if (status != 0) { + //Notify service only In case of failure cases + //Success case would have been notified through State machine anyways + mService.notifyOperationCompletion(mDevice, pendingOp); + } + switch (pendingOp) { + case START_SCAN_OFFLOAD: + if (status != 0) { + if (mAutoTriggerred == false) { + log("notify the app only if start Scan offload fails"); + //shouldnt happen in general + mService.sendBroadcastSourceSelectedCallback(mDevice, null, status); + cancelActiveSync(null); + } else { + mAutoTriggerred = false; + } + } + break; + case ADD_BCAST_SOURCE: + if (status != 0) { + sourceId = INVALID_SRC_ID; + cancelActiveSync(null); + //stop Scan offload for colocated case + mService.stopScanOffloadInternal(mDevice, false); + } + mService.sendAddBroadcastSourceCallback(mDevice, sourceId, status); + break; + case UPDATE_BCAST_SOURCE: + if (mAutoTriggerred == false) { + mService.sendUpdateBroadcastSourceCallback(mDevice, sourceId, status); + } else { + mAutoTriggerred = false; + } + break; + case REMOVE_BCAST_SOURCE: + mService.sendRemoveBroadcastSourceCallback(mDevice, sourceId, status); + break; + case SET_BCAST_CODE: + mService.sendSetBroadcastPINupdatedCallback(mDevice, sourceId, status); + break; + default: + { + log("sendPendingCallbacks: unhandled case"); + } + } + } + @VisibleForTesting + class ConnectedProcessing extends State { + @Override + public void enter() { + log( "Enter ConnectedProcessing(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + } + + @Override + public void exit() { + log("Exit ConnectedProcessing(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + } + @Override + public boolean processMessage(Message message) { + log("ConnectedProcessing process message(" + mDevice + "): " + messageWhatToString( + message.what)); + switch (message.what) { + case CONNECT: + Log.w(TAG, "CONNECT request is ignored" + mDevice); + break; + case DISCONNECT: + Log.w(TAG, "DISCONNECT requested!: " + mDevice); + if (mBluetoothGatt != null) { + mBluetoothGatt.disconnect(); + mBluetoothGatt.close(); + mBluetoothGatt = null; + cancelActiveSync(null); + //Trigger the CSIP disconnection, dont worry about pass/failure + if (mCsipConnected && mSetCoordinator != null) { + mSetCoordinator.disconnect(mService.mCsipAppId, mDevice); + mCsipConnected = false; + } + transitionTo(mDisconnected); + } else { + log("mBluetoothGatt is null"); + } + break; + case READ_BASS_CHARACTERISTICS: + Log.w(TAG, "defer READ_BASS_CHARACTERISTICS requested!: " + mDevice); + deferMessage(message); + break; + case CONNECTION_STATE_CHANGED: + int state = (int)message.obj; + Log.w(TAG, "ConnectedProcessing: connection state changed:" + state); + if (state == BluetoothProfile.STATE_CONNECTED) { + Log.w(TAG, "should never happen from this state"); + } else { + Log.w(TAG, "Unexpected disconnection " + mDevice); + transitionTo(mDisconnected); + } + break; + case GATT_TXN_PROCESSED: + removeMessages(GATT_TXN_TIMEOUT); + int status = (int)message.arg1; + log( "GATT transaction processed for" + mDevice); + mService.notifyOperationCompletion(mDevice, mPendingOperation); + if (status == BluetoothGatt.GATT_SUCCESS) { + if (mPendingOperation == SET_BCAST_CODE) { + //If Pending operation is SET_BCAST_CODE + //send callback to notify BCAST is updated + //This is needed only for SET_BCAST operation + sendPendingCallbacks(mPendingOperation, + mPendingSourceId, BleBroadcastAudioScanAssistCallback.BASS_STATUS_SUCCESS); + } + } else { + //any failure to write operation + //will be converted to corresponding + //callback with failure status + sendPendingCallbacks(mPendingOperation, + mPendingSourceId, BleBroadcastAudioScanAssistCallback.BASS_STATUS_FAILURE); + } + transitionTo(mConnected); + break; + case GATT_TXN_TIMEOUT: + log( "GATT transaction timedout for" + mDevice); + mService.notifyOperationCompletion(mDevice, mPendingOperation); + sendPendingCallbacks(mPendingOperation, + mPendingSourceId, BleBroadcastAudioScanAssistCallback.BASS_STATUS_TXN_TIMEOUT); + mPendingOperation = -1; + transitionTo(mConnected); + mPendingSourceId = -1; + break; + case START_SCAN_OFFLOAD: + case STOP_SCAN_OFFLOAD: + case SELECT_BCAST_SOURCE: + case ADD_BCAST_SOURCE: + case SET_BCAST_CODE: + case REMOVE_BCAST_SOURCE: + case PSYNC_ACTIVE_TIMEOUT: + log("defer the message:" + message.what + "so that it will be processed later"); + deferMessage(message); + break; + default: + log("CONNECTEDPROCESSING: not handled message:" + message.what); + return NOT_HANDLED; + } + return HANDLED; + } + } + + + @VisibleForTesting + class Disconnecting extends State { + @Override + public void enter() { + log( "Enter Disconnecting(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + sendMessageDelayed(CONNECT_TIMEOUT, mDevice, mConnectTimeoutMs); + broadcastConnectionState(mDevice, mLastConnectionState, + BluetoothProfile.STATE_DISCONNECTING); + } + @Override + public void exit() { + log("Exit Disconnecting(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + removeMessages(CONNECT_TIMEOUT); + mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING; + } + @Override + public boolean processMessage(Message message) { + log("Disconnecting process message(" + mDevice + "): " + messageWhatToString( + message.what)); + switch (message.what) { + case CONNECT: + log("Disconnecting to " + mDevice); + log("deferring this connection request " + mDevice); + deferMessage(message); + break; + case DISCONNECT: + Log.w(TAG, "Already disconnecting: DISCONNECT ignored: " + mDevice); + break; + case CONNECTION_STATE_CHANGED: + int state = (int)message.obj; + Log.w(TAG, "Disconnecting: connection state changed:" + state); + if (state == BluetoothProfile.STATE_CONNECTED) { + Log.e(TAG, "should never happen from this state"); + transitionTo(mConnected); + } else { + Log.w(TAG, "disconnection successfull to " + mDevice); + cancelActiveSync(null); + transitionTo(mDisconnected); + ///*_CSIP + //Trigger the CSip disconnection, dont worry about pass/failure + if (mCsipConnected) { + mSetCoordinator.disconnect(mService.mCsipAppId, mDevice); + mCsipConnected = false; + } + //_CSIP*/ + } + break; + case CONNECT_TIMEOUT: + Log.w(TAG, "CONNECT_TIMEOUT"); + + BluetoothDevice device = (BluetoothDevice) message.obj; + if (!mDevice.equals(device)) { + Log.e(TAG, "Unknown device timeout " + device); + break; + } + transitionTo(mDisconnected); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + + void broadcastConnectionState(BluetoothDevice device, int fromState, int toState) { + log( "broadcastConnectionState " + device + ": " + fromState + "->" + toState); + if (fromState == BluetoothProfile.STATE_CONNECTED && + toState == BluetoothProfile.STATE_CONNECTED) { + log("CONNECTED->CONNTECTED: Ignore"); + return; + } + Intent intent = new Intent(BluetoothSyncHelper.ACTION_CONNECTION_STATE_CHANGED); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState); + intent.putExtra(BluetoothProfile.EXTRA_STATE, toState); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mService.sendBroadcastAsUser(intent, UserHandle.ALL, + BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); + } + + int getConnectionState() { + String currentState = "Unknown"; + if (getCurrentState() != null) { + currentState = getCurrentState().getName(); + } + switch (currentState) { + case "Disconnected": + log("Disconnected"); + return BluetoothProfile.STATE_DISCONNECTED; + case "Disconnecting": + log("Disconnecting"); + return BluetoothProfile.STATE_DISCONNECTING; + case "Connecting": + log("Connecting"); + return BluetoothProfile.STATE_CONNECTING; + case "Connected": + case "ConnectedProcessing": + log("connected"); + return BluetoothProfile.STATE_CONNECTED; + default: + Log.e(TAG, "Bad currentState: " + currentState); + return BluetoothProfile.STATE_DISCONNECTED; + } + } + + BluetoothDevice getDevice() { + return mDevice; + } + + synchronized boolean isConnected() { + return getCurrentState() == mConnected; + } + + public static String messageWhatToString(int what) { + switch (what) { + case CONNECT: + return "CONNECT"; + case DISCONNECT: + return "DISCONNECT"; + case CONNECTION_STATE_CHANGED: + return "CONNECTION_STATE_CHANGED"; + case GATT_TXN_PROCESSED: + return "GATT_TXN_PROCESSED"; + case READ_BASS_CHARACTERISTICS: + return "READ_BASS_CHARACTERISTICS"; + case START_SCAN_OFFLOAD: + return "START_SCAN_OFFLOAD"; + case STOP_SCAN_OFFLOAD: + return "STOP_SCAN_OFFLOAD"; + case ADD_BCAST_SOURCE: + return "ADD_BCAST_SOURCE"; + case SELECT_BCAST_SOURCE: + return "SELECT_BCAST_SOURCE"; + case UPDATE_BCAST_SOURCE: + return "UPDATE_BCAST_SOURCE"; + case SET_BCAST_CODE: + return "SET_BCAST_CODE"; + case REMOVE_BCAST_SOURCE: + return "REMOVE_BCAST_SOURCE"; + case PSYNC_ACTIVE_TIMEOUT: + return "PSYNC_ACTIVE_TIMEOUT"; + case CSIP_CONNECTION_STATE_CHANGED: + return "CSIP_CONNECTION_STATE_CHANGED"; + case CONNECT_TIMEOUT: + return "CONNECT_TIMEOUT"; + default: + break; + } + return Integer.toString(what); + } + + private static String profileStateToString(int state) { + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return "DISCONNECTED"; + case BluetoothProfile.STATE_CONNECTING: + return "CONNECTING"; + case BluetoothProfile.STATE_CONNECTED: + return "CONNECTED"; + case BluetoothProfile.STATE_DISCONNECTING: + return "DISCONNECTING"; + default: + break; + } + return Integer.toString(state); + } + + public void dump(StringBuilder sb) { + ProfileService.println(sb, "mDevice: " + mDevice); + ProfileService.println(sb, " StateMachine: " + this); + // Dump the state machine logs + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + super.dump(new FileDescriptor(), printWriter, new String[]{}); + printWriter.flush(); + stringWriter.flush(); + ProfileService.println(sb, " StateMachineLog:"); + Scanner scanner = new Scanner(stringWriter.toString()); + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + ProfileService.println(sb, " " + line); + } + scanner.close(); + } + + @Override + protected void log( String msg) { + if (BASS_DBG) { + super.log(msg); + } + } + + private static void logByteArray(String prefix, byte[] value, int offset, int count) { + StringBuilder builder = new StringBuilder(prefix); + for (int i = offset; i < count; i++) { + builder.append(String.format("0x%02X", value[i])); + if (i != value.length - 1) { + builder.append(", "); + } + } + Log.d(TAG, builder.toString()); + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassCsetManager.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassCsetManager.java new file mode 100644 index 00000000000..fd2be7c6623 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassCsetManager.java @@ -0,0 +1,592 @@ +/* + * Copyright (c) 2020 The Linux Foundation. All rights reserved. + * + * Copyright 2018 The Android Open Source Project + * + * 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. + */ + +/** + * Bass CSET managet StateMachine. There is one instance per Coordinated "set Id". + * - "Idle" and "Locked" are steady states. + * - "Locking" is a transient states until the + * Locking confirmation comes from upper layers. + * - Once lock is acquired, profile dont try to unlock + * + * (Idle) + * | ^ + * LOCK | | UNLOCK + * V | + * (Locking)<->(Unlocking) + * | ^ + * ON_LOCK | | ON_UNLOCK + * V | + * (Locked) + * + * + */ + +package com.android.bluetooth.bc; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothUuid; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.BluetoothGattService; +import android.bluetooth.BluetoothSyncHelper; +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.BleBroadcastSourceChannel; +import android.bluetooth.BleBroadcastAudioScanAssistManager; +import android.bluetooth.IBleBroadcastAudioScanAssistCallback; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.PeriodicAdvertisingCallback; +import android.bluetooth.le.PeriodicAdvertisingManager; +import android.bluetooth.le.PeriodicAdvertisingReport; + +///*_CSIP +//CSET +import android.bluetooth.BluetoothDeviceGroup; +import com.android.bluetooth.groupclient.GroupService; +//_CSIP*/ + +import android.bluetooth.IBluetoothManager; +import android.os.ServiceManager; +import android.os.IBinder; + +import java.util.List; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Iterator; +import android.content.Intent; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import java.util.UUID; +import java.util.Collection; +import android.os.UserHandle; + +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.btservice.ServiceFactory; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Scanner; +import java.util.Map; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Set; +import java.lang.String; +import java.lang.StringBuffer; +import java.lang.Integer; + +import java.nio.ByteBuffer; +import java.lang.Byte; +import java.util.stream.IntStream; +import android.os.SystemProperties; +import android.os.ParcelUuid; + +final class BassCsetManager extends StateMachine { + private static final String TAG = "BassCsetManager"; + + //Considered as Coordinated ops + static final int BASS_GRP_START_SCAN_OFFLOAD = 6; + static final int BASS_GRP_STOP_SCAN_OFFLOAD = 7; + static final int BASS_GRP_ADD_BCAST_SOURCE = 9; + static final int BASS_GRP_UPDATE_BCAST_SOURCE = 10; + static final int BASS_GRP_SET_BCAST_CODE = 11; + static final int BASS_GRP_REMOVE_BCAST_SOURCE = 12; + + static final int LOCK = 17; + static final int UNLOCK = 18; + static final int LOCK_STATE_CHANGED = 3; + static final int LOCK_TIMEOUT = 4; + static final int ON_CSIP_CONNECTED = 5; + + //10 secs time out for all gatt writes + static final int LOCK_TIMEOUT_MS = 10000; + + + @VisibleForTesting + private static final int CONNECT_TIMEOUT = 201; + + private final Idle mIdle; + private final Locking mLocking; + private final Locked mLocked; + private final LockedProcessing mLockedProcessing; + private final Unlocking mUnlocking; + + private BCService mBCService; + private final BluetoothDevice mDevice; + private final int mSetId; + private List mMemberDevices = null; + + ///*_CSIP + //CSIP Locking Interfaces + private GroupService mSetCoordinator = GroupService.getGroupService(); + //_CSIP*/ + + BassCsetManager(int setId, BluetoothDevice masterDevice, BCService svc, + Looper looper) { + super(TAG, looper); + mSetId = setId; + mBCService = svc; + + mIdle = new Idle(); + mLocked = new Locked(); + mLockedProcessing = new LockedProcessing(); + mLocking = new Locking(); + mUnlocking = new Unlocking(); + + addState(mIdle); + addState(mLocking); + addState(mLocked); + addState(mLockedProcessing); + addState(mUnlocking); + + setInitialState(mIdle); + mDevice = masterDevice; + mMemberDevices = new ArrayList(); + + } + + static BassCsetManager make(int setId, BluetoothDevice masterDevice, BCService svc, + Looper looper) { + Log.d(TAG, "make for setId, setId " + setId + ": masterDevice" + masterDevice); + BassCsetManager BassclientSm = new BassCsetManager(setId, masterDevice, svc, + looper); + BassclientSm.start(); + return BassclientSm; + } + + public void doQuit() { + log("doQuit for device " + mDevice); + quitNow(); + } + + public void cleanup() { + log("cleanup for device " + mDevice); + } + + @VisibleForTesting + class Idle extends State { + @Override + public void enter() { + log( "Enter Idle(" + mSetId + "): " + messageWhatToString( + getCurrentMessage().what)); + mMemberDevices = null; + + } + + @Override + public void exit() { + log("Exit Idle(" + mSetId + "): " + messageWhatToString( + getCurrentMessage().what)); + } + + @Override + public boolean processMessage(Message message) { + log("Idle process message(" + mSetId + "): " + messageWhatToString( + message.what)); + + switch (message.what) { + case BASS_GRP_START_SCAN_OFFLOAD: + case BASS_GRP_STOP_SCAN_OFFLOAD: + case BASS_GRP_ADD_BCAST_SOURCE: + case BASS_GRP_UPDATE_BCAST_SOURCE: + case BASS_GRP_SET_BCAST_CODE: + case BASS_GRP_REMOVE_BCAST_SOURCE: + //defer the meesage and move to Locked + deferMessage(message); + //Intentional miss of break + case LOCK: + //treat Connect & Lock as same request + log("Locking to " + mSetId); + //get CSIP connection status for BluetoothDevice + //if CSIP disconnected: start Connect Procedure (mostly hpns only at first time) + //if CSIP connected: start Lock Procedure + ///*_CSIP + mSetCoordinator.setLockValue(mBCService.mCsipAppId, mSetId, null, BluetoothDeviceGroup.ACCESS_GRANTED); + //_CSIP*/ + transitionTo(mLocking); + + //transitionTo(mLocked); + break; + case UNLOCK: + Log.w(TAG, "Idle: UNLOCK ignored: " + mSetId); + break; + case LOCK_STATE_CHANGED: + //This most likely not happen + ///*_CSIP + int value = (int)message.arg1; + List devices = (List)message.obj; + Log.w(TAG, "Lock state changed:" + value); + if (value == BluetoothDeviceGroup.ACCESS_GRANTED) { + transitionTo(mLocked); + } else { + Log.w(TAG, "Idle: Lock failed to " + mSetId); + } + //_CSIP*/ + break; + case ON_CSIP_CONNECTED: + //starts the Lock procedure + //Only reason why we Connect is to Lock + // + //Dont transition the state + default: + log("Idle: not handled message:" + message.what); + return NOT_HANDLED; + } + return HANDLED; + } + } + + @VisibleForTesting + class Locking extends State { + @Override + public void enter() { + log( "Enter Locking(" + mSetId + "): " + messageWhatToString( + getCurrentMessage().what)); + } + + @Override + public void exit() { + log("Exit Locking(" + mSetId + "): " + messageWhatToString( + getCurrentMessage().what)); + } + + @Override + public boolean processMessage(Message message) { + log("Locking process message(" + mSetId + "): " + messageWhatToString( + message.what)); + + switch (message.what) { + + case BASS_GRP_START_SCAN_OFFLOAD: + case BASS_GRP_STOP_SCAN_OFFLOAD: + case BASS_GRP_ADD_BCAST_SOURCE: + case BASS_GRP_UPDATE_BCAST_SOURCE: + case BASS_GRP_SET_BCAST_CODE: + case BASS_GRP_REMOVE_BCAST_SOURCE: + //defer the meesage and move to Locked + deferMessage(message); + break; + case LOCK: + log("Already Locking to " + mSetId); + log("Ignore this Lock request " + mSetId); + break; + case UNLOCK: + Log.w(TAG, "Locking: UNLOCK deferred: " + mSetId); + deferMessage(message); + break; + case LOCK_STATE_CHANGED: + ///*_CSIP + int value = (int)message.arg1; + Log.w(TAG, "Lock state changed:" + value); + if (value == BluetoothDeviceGroup.ACCESS_GRANTED) { + List devices = (List)message.obj; + mMemberDevices = devices; + transitionTo(mLocked); + } else { + Log.w(TAG, "Locking: Unlocked to " + mSetId); + transitionTo(mIdle); + } + //_CSIP*/ + break; + case ON_CSIP_CONNECTED: + //starts the Lock procedure + //Only reason why we Connect is to Lock + // + //Dont transition the state + break; + default: + log("LOCKING: not handled message:" + message.what); + return NOT_HANDLED; + } + return HANDLED; + } + } + + @VisibleForTesting + class Locked extends State { + @Override + public void enter() { + log( "Enter Locked(" + mSetId + "): " + + messageWhatToString(getCurrentMessage().what)); + + removeDeferredMessages(LOCK); + + } + + @Override + public void exit() { + log("Exit Locked(" + mSetId + "): " + + messageWhatToString(getCurrentMessage().what)); + } + + @Override + public boolean processMessage(Message message) { + log("Locked process message(" + mSetId + "): " + + messageWhatToString(message.what)); + BleBroadcastSourceInfo srcInfo; + switch (message.what) { + case LOCK: + Log.w(TAG, "Locked: Lock ignored: " + mSetId); + break; + case UNLOCK: + log("Unlocking from " + mDevice); + //trigger unlock procedure + ///*_CSIP + mSetCoordinator.setLockValue(mBCService.mCsipAppId, mSetId, null, BluetoothDeviceGroup.ACCESS_RELEASED); + transitionTo(mUnlocking); + //_CSIP*/ + + //transitionTo(mIdle); + break; + case LOCK_STATE_CHANGED: + ///*_CSIP + int value = (int)message.arg1; + List devices = (List)message.obj; + Log.w(TAG, "Lock state changed:" + value); + if (value == BluetoothDeviceGroup.ACCESS_GRANTED) { + transitionTo(mLocked); + } else { + Log.w(TAG, "Locking: Unlocked to " + mSetId); + transitionTo(mIdle); + } + //_CSIP*/ + break; + case BASS_GRP_START_SCAN_OFFLOAD: + if (mBCService != null) { + log("START_SCAN_OFFLOAD: " + mMemberDevices); + mBCService.startScanOffload(mDevice, mMemberDevices); + transitionTo(mLockedProcessing); + } else { + log("no Bassclient service handle"); + } + break; + case BASS_GRP_STOP_SCAN_OFFLOAD: + if (mBCService != null) { + log("STOP_SCAN_OFFLOAD: " + mMemberDevices); + mBCService.stopScanOffload(mDevice, mMemberDevices); + transitionTo(mLockedProcessing); + } else { + log("no Bassclient service handle"); + } + break; + case BASS_GRP_ADD_BCAST_SOURCE: + srcInfo = (BleBroadcastSourceInfo)message.obj; + if (mBCService != null) { + mBCService.addBroadcastSource(mDevice, mMemberDevices, srcInfo); + transitionTo(mLockedProcessing); + } else { + log("no Bassclient service handle"); + } + break; + case BASS_GRP_UPDATE_BCAST_SOURCE: + srcInfo = (BleBroadcastSourceInfo)message.obj; + if (mBCService != null) { + mBCService.updateBroadcastSource(mDevice, mMemberDevices, srcInfo); + transitionTo(mLockedProcessing); + } else { + log("no Bassclient service handle"); + } + break; + case BASS_GRP_SET_BCAST_CODE: + srcInfo = (BleBroadcastSourceInfo)message.obj; + if (mBCService != null) { + mBCService.setBroadcastCode(mDevice, mMemberDevices, srcInfo); + transitionTo(mLockedProcessing); + } else { + log("no Bassclient service handle"); + } + break; + case BASS_GRP_REMOVE_BCAST_SOURCE: + byte sourceId = (byte)message.arg1; + if (mBCService != null) { + mBCService.removeBroadcastSource(mDevice, mMemberDevices, sourceId); + transitionTo(mLockedProcessing); + } else { + log("no Bassclient service handle"); + } + break; + default: + log("Locked: not handled message:" + message.what); + return NOT_HANDLED; + } + return HANDLED; + } + } + + @VisibleForTesting + class LockedProcessing extends State { + @Override + public void enter() { + log( "Enter LockedProcessing(" + mSetId + "): " + + messageWhatToString(getCurrentMessage().what)); + } + + @Override + public void exit() { + log("Exit LockedProcessing(" + mSetId + "): " + + messageWhatToString(getCurrentMessage().what)); + } + + @Override + public boolean processMessage(Message message) { + log("LockedProcessing process message(" + mSetId + "): " + + messageWhatToString(message.what)); + BleBroadcastSourceInfo srcInfo; + switch (message.what) { + case UNLOCK: + log("LockedProcessing: UNLOCK defer " + mDevice); + deferMessage(message); + transitionTo(mLocked); + break; + case LOCK_STATE_CHANGED: + int value = (int)message.arg1; + Log.w(TAG, "Locking state changed:" + value); + //Should never happen + break; + case LOCK: + log("LockedProcessing: LOCK ignore " + mDevice); + break; + case BASS_GRP_START_SCAN_OFFLOAD: + case BASS_GRP_STOP_SCAN_OFFLOAD: + case BASS_GRP_ADD_BCAST_SOURCE: + case BASS_GRP_UPDATE_BCAST_SOURCE: + case BASS_GRP_SET_BCAST_CODE: + case BASS_GRP_REMOVE_BCAST_SOURCE: + //defer the meesage and move to Locked + if (hasDeferredMessages(UNLOCK)) { + //If Unlock is in pending list, remove it + //Override the UNLOCK with this new operation + log("removing the unlock message, as there is another req"); + removeDeferredMessages(UNLOCK); + } + deferMessage(message); + break; + default: + log("LockedProcessing: not handled message:" + message.what); + return NOT_HANDLED; + } + return HANDLED; + } + } + + + @VisibleForTesting + class Unlocking extends State { + @Override + public void enter() { + log( "Enter Unlocking(" + mSetId + "): " + + messageWhatToString(getCurrentMessage().what)); + + //removeDeferredMessages(LOCK); + + } + + @Override + public void exit() { + log("Exit Unlocking(" + mSetId + "): " + + messageWhatToString(getCurrentMessage().what)); + } + + @Override + public boolean processMessage(Message message) { + log("Locked process message(" + mSetId + "): " + + messageWhatToString(message.what)); + BleBroadcastSourceInfo srcInfo; + switch (message.what) { + case UNLOCK: + log("Unlocking: UNLOCK ignored from " + mDevice); + break; + case LOCK_STATE_CHANGED: + ///*_CSIP + int value = (int)message.arg1; + Log.w(TAG, "Locking state changed:" + value); + if (value == BluetoothDeviceGroup.ACCESS_RELEASED) { + mMemberDevices = null; + transitionTo(mIdle); + } else { + Log.w(TAG, "UnLocking: failed to " + mSetId); + //keep that back in Locked? + transitionTo(mLocked); + // + } + //_CSIP*/ + break; + case LOCK: + case BASS_GRP_START_SCAN_OFFLOAD: + case BASS_GRP_STOP_SCAN_OFFLOAD: + case BASS_GRP_ADD_BCAST_SOURCE: + case BASS_GRP_UPDATE_BCAST_SOURCE: + case BASS_GRP_SET_BCAST_CODE: + case BASS_GRP_REMOVE_BCAST_SOURCE: + //defer the meesage and move to Locked + deferMessage(message); + break; + default: + log("Locked: not handled message:" + message.what); + return NOT_HANDLED; + } + return HANDLED; + } + } + + + private static String messageWhatToString(int what) { + switch (what) { + case LOCK: + return "LOCK"; + case UNLOCK: + return "UNLOCK"; + case LOCK_STATE_CHANGED: + return "LOCK_STATE_CHANGED"; + case BASS_GRP_START_SCAN_OFFLOAD: + return "BASS_GRP_START_SCAN_OFFLOAD"; + case BASS_GRP_STOP_SCAN_OFFLOAD: + return "BASS_GRP_STOP_SCAN_OFFLOAD"; + case BASS_GRP_ADD_BCAST_SOURCE: + return "BASS_GRP_ADD_BCAST_SOURCE"; + case BASS_GRP_UPDATE_BCAST_SOURCE: + return "BASS_GRP_UPDATE_BCAST_SOURCE"; + case BASS_GRP_SET_BCAST_CODE: + return "BASS_GRP_SET_BCAST_CODE"; + case BASS_GRP_REMOVE_BCAST_SOURCE: + return "BASS_GRP_REMOVE_BCAST_SOURCE"; + default: + break; + } + return Integer.toString(what); + } + + @Override + protected void log( String msg) { + if (BassClientStateMachine.BASS_DBG) { + super.log(msg); + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassUtils.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassUtils.java new file mode 100644 index 00000000000..50aea48a39b --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassUtils.java @@ -0,0 +1,492 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ +package com.android.bluetooth.bc; + +import java.util.List; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Iterator; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import java.util.UUID; +import java.util.Collection; +import android.os.UserHandle; + +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import java.nio.charset.StandardCharsets; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Scanner; +import java.util.Map; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Set; +import java.lang.String; +import java.lang.StringBuffer; +import java.lang.Integer; + +import java.nio.ByteBuffer; +import java.lang.Byte; +import java.util.stream.IntStream; +import java.util.NoSuchElementException; + +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.BluetoothLeScanner; +import java.util.UUID; +import android.os.Handler; +import android.os.ParcelUuid; +import android.os.SystemProperties; +import android.os.RemoteException; + +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.BleBroadcastSourceChannel; +//import android.bluetooth.BluetoothBroadcast; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import com.android.bluetooth.btservice.ServiceFactory; +///*_BMS +import com.android.bluetooth.broadcast.BroadcastService; +//_BMS*/ +import android.bluetooth.BluetoothCodecConfig; +/*_PACS +import com.android.bluetooth.pacsclient.PacsClientService; +_PACS*/ +import android.bluetooth.IBleBroadcastAudioScanAssistCallback; + +/** + * Bass Utility functions + */ + +final class BassUtils { + private static final String TAG = "BassUtils"; + /*LE Scan related members*/ + private boolean mBroadcastersAround = false; + private BluetoothAdapter mBluetoothAdapter = null; + private BluetoothLeScanner mLeScanner = null; + private BCService mBCService = null; + + ///*_BMS + private BroadcastService mBAService = null; + //_BMS*/ + public static final String BAAS_UUID = "00001852-0000-1000-8000-00805F9B34FB"; + private boolean mIsLocalBMSNotified = false; + private ServiceFactory mFactory = new ServiceFactory(); + //Using ArrayList as KEY to hashmap. May be not risk + //in this case as It is used to track the callback to cancel Scanning later + private final Map, ScanCallback> mLeAudioSourceScanCallbacks; + private final Map mBassAutoAssist; + + private static final int AA_START_SCAN = 1; + private static final int AA_SCAN_SUCCESS = 2; + private static final int AA_SCAN_FAILURE = 3; + private static final int AA_SCAN_TIMEOUT = 4; + //timeout for internal scan + private static final int AA_SCAN_TIMEOUT_MS = 1000; + + /** + * Stanadard Codec param types + */ + static final int LOCATION = 3; + //sample rate + static final int SAMPLE_RATE = 1; + //frame duration + static final int FRAME_DURATION = 2; + //Octets per frame + static final int OCTETS_PER_FRAME = 8; + /*_PACS + private PacsClientService mPacsClientService = PacsClientService.getPacsClientService(); + _PACS*/ + BassUtils (BCService service) { + mBCService = service; + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mLeScanner = mBluetoothAdapter.getBluetoothLeScanner(); + mLeAudioSourceScanCallbacks = new HashMap, ScanCallback>(); + mBassAutoAssist = new HashMap(); + ///*_BMS + mBAService = BroadcastService.getBroadcastService(); + //_BMS*/ + } + + private ScanCallback mPaSyncScanCallback = new ScanCallback() { + @Override + public void onScanResult(int callbackType, ScanResult result) { + log( "onScanResult:" + result); + } + }; + + void cleanUp () { + + if (mLeAudioSourceScanCallbacks != null) { + mLeAudioSourceScanCallbacks.clear(); + } + + if (mBassAutoAssist != null) { + mBassAutoAssist.clear(); + } + } + + boolean leScanControl(boolean on) { + log("leScanControl:" + on); + mLeScanner = mBluetoothAdapter.getBluetoothLeScanner(); + if (mLeScanner == null) { + Log.e(TAG, "LeScan handle not available"); + return false; + } + + if (on) { + mLeScanner.startScan(mPaSyncScanCallback); + } else { + mLeScanner.stopScan(mPaSyncScanCallback); + } + + return true; + } + + /* private helper to check if the Local BLE Broadcast happening Or not */ + public boolean isLocalLEAudioBroadcasting() { + boolean ret = false; + /*String localLeABroadcast = SystemProperties.get("persist.vendor.btstack.isLocalLeAB"); + if (!localLeABroadcast.isEmpty() && "true".equals(localLeABroadcast)) { + ret = true; + } + log("property isLocalLEAudioBroadcasting returning " + ret);*/ + ///*_Broadcast + if (mBAService == null) { + mBAService = BroadcastService.getBroadcastService(); + } + if (mBAService != null) { + ret = mBAService.isBroadcastActive(); + //ret = mBAService.isBroadcastStreaming(); + log("local broadcast streaming:" + ret); + } else { + log("BroadcastService is Null"); + } + //_Broadcast*/ + log("isLocalLEAudioBroadcasting returning " + ret); + return ret; + } + + Handler mAutoAssistScanHandler = new Handler() { + public void handleMessage(Message msg) { + super.handleMessage(msg); + switch (msg.what) { + case AA_START_SCAN: + BluetoothDevice dev = (BluetoothDevice) msg.obj; + Message m = obtainMessage(AA_SCAN_TIMEOUT); + m.obj = dev; + sendMessageDelayed(m, AA_SCAN_TIMEOUT_MS); + searchforLeAudioBroadcasters(dev, null); + break; + case AA_SCAN_SUCCESS: + //Able to find to desired desired Source Device + ScanResult scanRes = (ScanResult) msg.obj; + dev = scanRes.getDevice(); + stopSearchforLeAudioBroadcasters(dev,null); + mBCService.selectBroadcastSource(dev, scanRes, false, true); + break; + case AA_SCAN_FAILURE: + //Not able to find the given source + //ignore + break; + case AA_SCAN_TIMEOUT: + dev = (BluetoothDevice)msg.obj; + stopSearchforLeAudioBroadcasters(dev, null); + break; + } + } + }; + private void notifyLocalBroadcastSourceFound(ArrayList cbs) { + BluetoothDevice localDev = + BluetoothAdapter.getDefaultAdapter().getRemoteDevice(mBluetoothAdapter.getAddress()); + String localName = BluetoothAdapter.getDefaultAdapter().getName(); + ScanRecord record = null; + if (localName != null) { + byte name_len = (byte)localName.length(); + byte[] bd_name = localName.getBytes(StandardCharsets.US_ASCII); + byte[] name_key = new byte[] {++name_len, 0x09 }; //0x09 TYPE:Name + byte[] scan_r = new byte[name_key.length + bd_name.length]; + System.arraycopy(name_key, 0, scan_r, 0, name_key.length); + System.arraycopy(bd_name, 0, scan_r, name_key.length, bd_name.length); + record = ScanRecord.parseFromBytes(scan_r); + log ("Local name populated in fake Scan res:" + record.getDeviceName()); + } + ScanResult scanRes = new ScanResult(localDev, + 1, 1, 1,2, 0, 0, 0, record, 0); + if (cbs != null) { + for (IBleBroadcastAudioScanAssistCallback cb : cbs) { + try { + cb.onBleBroadcastSourceFound(scanRes); + } catch (RemoteException e) { + Log.e(TAG, "Exception while calling onBleBroadcastSourceFound"); + } + } + } + } + public boolean searchforLeAudioBroadcasters (BluetoothDevice srcDevice, + ArrayList cbs + ) { + log( "searchforLeAudioBroadcasters: "); + BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner(); + mIsLocalBMSNotified = false; + if (scanner == null) { + Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner"); + return false; + } + synchronized (mLeAudioSourceScanCallbacks) { + if (mLeAudioSourceScanCallbacks.containsKey(cbs)) { + Log.e(TAG, "LE Scan has already started"); + return false; + } + ScanCallback scanCallback = new ScanCallback() { + @Override + public void onScanResult(int callbackType, ScanResult result) { + log( "onScanResult:" + result); + if (callbackType != ScanSettings.CALLBACK_TYPE_ALL_MATCHES) { + // Should not happen. + Log.e(TAG, "LE Scan has already started"); + return; + } + ScanRecord scanRecord = result.getScanRecord(); + //int pInterval = result.getPeriodicAdvertisingInterval(); + if (scanRecord != null) { + Map listOfUuids = scanRecord.getServiceData(); + if (listOfUuids != null) { + //ParcelUuid bmsUuid = new ParcelUuid(BroadcastService.BAAS_UUID); + //boolean isBroadcastSource = listOfUuids.containsKey(bmsUuid); + boolean isBroadcastSource = listOfUuids.containsKey(ParcelUuid.fromString(BAAS_UUID)); + log( "isBroadcastSource:" + isBroadcastSource); + if (isBroadcastSource) { + log( "Broadcast Source Found:" + result.getDevice()); + if (cbs != null) { + for (IBleBroadcastAudioScanAssistCallback cb : cbs) { + try { + cb.onBleBroadcastSourceFound(result); + } catch (RemoteException e) { + Log.e(TAG, "Exception while calling onBleBroadcastSourceFound"); + } + } + } else { + if (srcDevice.equals(result.getDevice())) { + log("matching src Device found"); + Message msg = mAutoAssistScanHandler.obtainMessage(AA_SCAN_SUCCESS); + msg.obj = result; + mAutoAssistScanHandler.sendMessage(msg); + } + } + } else { + log( "Broadcast Source UUID not preset, ignore"); + } + } else { + Log.e(TAG, "Ignore no UUID"); + return; + } + } else { + Log.e(TAG, "Scan record is null, ignoring this Scan res"); + return; + } + //Before starting LE Scan, Call local APIs to find out if the local device + //is Broadcaster, then generate callback for Local device + if (!mIsLocalBMSNotified && isLocalLEAudioBroadcasting()) { + //Create a DUMMY scan result for colocated case + notifyLocalBroadcastSourceFound(cbs); + mIsLocalBMSNotified = true; + } + } + + public void onScanFailed(int errorCode) { + Log.e(TAG, "Scan Failure:" + errorCode); + } + }; + if (mBluetoothAdapter != null) { + if (cbs != null) { + mLeAudioSourceScanCallbacks.put(cbs, scanCallback); + } else { + //internal auto assist trigger remember it + //based on device + mBassAutoAssist.put(srcDevice, scanCallback); + } + + ScanSettings settings = new ScanSettings.Builder().setCallbackType( + ScanSettings.CALLBACK_TYPE_ALL_MATCHES) + .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) + .setLegacy(false) + .build(); + ScanFilter.Builder filterBuilder = new ScanFilter.Builder(); + ScanFilter srcFilter = filterBuilder.setServiceUuid( + ParcelUuid.fromString(BAAS_UUID)).build(); + List filters = new ArrayList(); + if (!mIsLocalBMSNotified && isLocalLEAudioBroadcasting()) { + //Create a DUMMY scan result for colocated case + notifyLocalBroadcastSourceFound(cbs); + mIsLocalBMSNotified = true; + } + scanner.startScan(filters, settings, scanCallback); + return true; + } else { + Log.e(TAG, "searchforLeAudioBroadcasters: Adapter is NULL"); + return false; + } + } + } + public boolean stopSearchforLeAudioBroadcasters(BluetoothDevice srcDev, + ArrayList cbs) { + log( "stopSearchforLeAudioBroadcasters()"); + BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner(); + if (scanner == null) { + return false; + } + ScanCallback scanCallback = null; + if (cbs != null) { + scanCallback = mLeAudioSourceScanCallbacks.remove(cbs); + } else { + scanCallback = mLeAudioSourceScanCallbacks.remove(srcDev); + } + + if (scanCallback == null) { + log( "scan not started yet"); + return false; + } + scanner.stopScan(scanCallback); + return true; + } + + private int convertConfigurationSRToCapabilitySR(byte sampleRate) { + int ret = BluetoothCodecConfig.SAMPLE_RATE_NONE; + switch (sampleRate) { + case 1: + ret = BluetoothCodecConfig.SAMPLE_RATE_NONE; break; + case 2: + ret = BluetoothCodecConfig.SAMPLE_RATE_NONE; break; + case 3: + ret = BluetoothCodecConfig.SAMPLE_RATE_NONE; break; + case 4: + //ret = BluetoothCodecConfig.SAMPLE_RATE_32000; break; + case 5: + ret = BluetoothCodecConfig.SAMPLE_RATE_44100; break; + case 6: + ret = BluetoothCodecConfig.SAMPLE_RATE_48000; break; + } + log("convertConfigurationSRToCapabilitySR returns:" + ret); + return ret; + } + + private boolean isSampleRateSupported(BluetoothDevice device, byte sampleRate) { + boolean ret = false; + /*_PACS + BluetoothCodecConfig[] supportedConfigs = mPacsClientService.getSinkPacs(device); + int actualSampleRate = convertConfigurationSRToCapabilitySR(sampleRate); + + if (actualSampleRate == BluetoothCodecConfig.SAMPLE_RATE_NONE) { + return false; + } + + for (int i=0; i selectBises(BluetoothDevice device, + BleBroadcastSourceInfo srcInfo, BaseData base) { + boolean noPref = SystemProperties.getBoolean("persist.vendor.service.bt.bass_no_pref", false); + if (noPref) { + log("No pref selected"); + return null; + } else { + /*_PACS + mPacsClientService = PacsClientService.getPacsClientService(); + List bChannels = new ArrayList(); + //if (mPacsClientService == null) { + log("selectBises: Pacs Service is null, pick BISes apropriately"); + //Pacs not available + if (base != null) { + bChannels = base.pickAllBroadcastChannels(); + } else { + bChannels = null; + } + return bChannels; + //} + if (mPacsClientService != null) { + int supportedLocations = 1/*mPacsClientService.getSinkLocations(device); + ArrayList broadcastedCodecInfo = base.getBISIndexInfos(); + if (broadcastedCodecInfo != null) { + for (int i=0; i consolidatedUniqueCodecInfo = broadcastedCodecInfo.get(i).consolidatedUniqueCodecInfo; + byte index = broadcastedCodecInfo.get(i).index; + if (consolidatedUniqueCodecInfo != null) { + + + byte[] bisChannelLocation = consolidatedUniqueCodecInfo.get(LOCATION).getBytes(); + byte[] locationValue = new byte[4]; + System.arraycopy(bisChannelLocation, 2, locationValue, 0, 4); + log ("locationValue>>> "); + printByteArray(locationValue); + ByteBuffer wrapped = ByteBuffer.wrap(locationValue); + int bisLocation = wrapped.getInt(); + log("bisLocation: " + bisLocation); + int reversebisLoc = Integer.reverseBytes(bisLocation); + log("reversebisLoc: " + reversebisLoc); + + byte[] bisSampleRate = consolidatedUniqueCodecInfo.get(SAMPLE_RATE).getBytes(); + byte bisSR = bisSampleRate[2]; + + //using bitwise operand as Location can be bitmask + if (isSampleRateSupported(device, bisSR) && (reversebisLoc & supportedLocations) == supportedLocations) { + log("matching location: bisLocation " + reversebisLoc + ":: " + supportedLocations); + BleBroadcastSourceChannel bc = new BleBroadcastSourceChannel(index, String.valueOf(index), true); + bChannels.add(bc); + } + } + } + } + } + + if (bChannels != null && bChannels.size() == 0) { + log("selectBises: no channel are selected"); + bChannels = null; + + } + return bChannels; + _PACS*/ + } + return null; + } + + public void triggerAutoAssist (BleBroadcastSourceInfo srcInfo) { + //searchforLeAudioBroadcasters (srcInfo.getSourceDevice(), null, AUTO_ASSIST_SCAN_TIMEOUT); + BluetoothDevice dev = srcInfo.getSourceDevice(); + + Message msg = mAutoAssistScanHandler.obtainMessage(AA_START_SCAN); + msg.obj = srcInfo.getSourceDevice(); + mAutoAssistScanHandler.sendMessage(msg); + } + + static void log(String msg) { + if (BassClientStateMachine.BASS_DBG) { + Log.d(TAG, msg); + } + } + + static void printByteArray(byte[] array) { + log("Entire byte Array as string: " + Arrays.toString(array)); + log("printitng byte by bte"); + for (int i=0; i mBisInfo; + MapmMetaInfo = Collections.synchronizedMap(new HashMap<>());; + private String mAdvAddress; + private int mAdvAddressType; + private BluetoothLeAdvertiser mAdvertiser; + private BluetoothCodecStatus mCodecStatus; + private BluetoothCodecConfig mCodecConfig; + private BluetoothCodecConfig mHapCodecConfig; + private BroadcastCodecConfig mBroadcastCodecConfig; + private BroadcastAdvertiser mBroadcastAdvertiser; + private int mBroadcastConfigSettings; + private BluetoothAdapter mBluetoothAdapter; + private BluetoothDevice mBroadcastDevice = null; + private boolean mBroadcastDeviceIsActive = false; + TrackMetadata mTrackMetadata; + private String mBroadcastAddress = "FA:CE:FA:CE:FA:CE"; + ActiveDeviceManagerService mActiveDeviceManager; + public static UUID BROADCAST_AUDIO_UUID = UUID.fromString("00001852-0000-1000-8000-00805F9B34FB"); + public static UUID BASIC_AUDIO_UUID = UUID.fromString("00001851-0000-1000-8000-00805F9B34FB"); + private BroadcastBase mBroadcastBase; + private MediaAudio mMediaAudio; + private boolean new_codec_id = false; + private static int mSecPhy = 1; + private static int mTxPowerLevel = 1; + private static int mPaInt; + private boolean mNewVersion = false; + List broadcast_supported_config = new ArrayList(List.of("16_2", "24_2", "48_1", "48_2", "48_3", "48_4", "48_5", "48_6")); + private static final int MSG_ENABLE_BROADCAST = 1; + private static final int MSG_DISABLE_BROADCAST = 2; + private static final int MSG_SET_ENCRYPTION_KEY = 3; + private static final int MSG_GET_ENCRYPTION_KEY = 4; + private static final int MSG_SET_BROADCAST_ACTIVE = 5; + private static final int MSG_UPDATE_BROADCAST_ADV_SET = 6; + private static final int MSG_ADV_DATA_SET = 7; + private static final int MSG_SET_AUDIO_PATH = 8; + private static final int MSG_RESET_ENCRYPTION_FLAG_TIMEOUT = 9; + private static final int MSG_FROM_NATIVE_CODEC_STATE = 10; + private static final int MSG_FROM_NATIVE_BROADCAST_STATE = 11; + private static final int MSG_FROM_NATIVE_ENCRYPTION_KEY = 12; + private static final int MSG_FROM_NATIVE_BROADCAST_AUDIO_STATE = 13; + private static final int MSG_FROM_NATIVE_SETUP_BIG = 14; + private static final int MSG_UPDATE_BROADCAST_STATE = 15; + private static final int MSG_FROM_NATIVE_BROADCAST_ID = 16; + @Override + protected IProfileServiceBinder initBinder() { + return new BluetoothBroadcastBinder(this); + } + + @Override + protected void create() { + Log.i(TAG, "create()"); + } + + @Override + protected boolean start() { + Log.i(TAG, "start()"); + if (sBroadcastService != null) { + Log.w(TAG, "Broadcastervice is already running"); + return true; + } + if (mHandler != null) + mHandler = null; + mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), + "AdapterService cannot be null when A2dpService starts"); + + mBroadcastNativeInterface = Objects.requireNonNull(mBroadcastNativeInterface.getInstance(), + "BroadcastNativeInterface cannot be null when BroadcastService starts"); + mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + Objects.requireNonNull(mAudioManager, + "AudioManager cannot be null when A2dpService starts"); + HandlerThread thread = new HandlerThread("BroadcastHandler"); + mBroadcastConfigSettings = SystemProperties.getInt("persist.vendor.btstack.bap_ba_setting", 4); + mBroadcastCodecConfig = new BroadcastCodecConfig(); + String PartialSimulcast = SystemProperties.get("persist.vendor.btstack.partial_simulcast"); + if (!PartialSimulcast.isEmpty() && "true".equals(PartialSimulcast)) { + mPartialSimulcast = true; + mNumSubGrps = 2; + mNumBises = 4; + //mHapCodecConfig = new BroadcastCodecConfig(mPartialSimulcast); + } + String mNewCodecId = SystemProperties.get("persist.vendor.btstack.new_lc3_id"); + if (mNewCodecId.isEmpty() || "true".equals(mNewCodecId) || + "6".equals(mNewCodecId)) { + new_codec_id = true; + } + /* Property to set seconday advertising phy to 1M or 2M. 2M is selected by default + * if propety is not set + */ + mSecPhy = SystemProperties.getInt("persist.vendor.btstack.secphy", 2); + mTxPowerLevel = SystemProperties.getInt("persist.vendor.service.bt.txpower", 9); + mPD = SystemProperties.getInt("persist.vendor.service.bt.presentation_delay", 40); + mPaInt = SystemProperties.getInt("persist.vendor.btstack.pa_interval", 360); + mNewVersion = SystemProperties.getBoolean("persist.vendor.service.bt.new_ba_version", true); + int offload_mode = 1; //offload + mBroadcastNativeInterface.init(1, mCodecConfig,offload_mode); + thread.start(); + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); + mAdapterService.registerReceiver(mBroadcastReceiver, filter); + Looper looper = thread.getLooper(); + mHandler = new BroadcastMessageHandler(looper); + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mBroadcastBase = new BroadcastBase(); + mBisInfo = new ArrayList<>(); + //mBroadcastAdvertiser = new BroadcastAdvertiser(); + setBroadcastService(this); + mBroadcastDevice = mAdapter.getRemoteDevice(mBroadcastAddress); + mTrackMetadata = new TrackMetadata(null); + + mActiveDeviceManager = ActiveDeviceManagerService.get(this); + DeviceProfileMap dpm = DeviceProfileMap.getDeviceProfileMapInstance(); + dpm.profileConnectionUpdate(mBroadcastDevice, ApmConst.AudioFeatures.BROADCAST_AUDIO, ApmConst.AudioProfiles.BROADCAST_LE, true); + + //Get current codec and call native init + return true; + } + private void initialize_advertiser() { + Log.d(TAG,"initalize_advertiser"); + mBroadcastAdvertiser = new BroadcastAdvertiser(); + GetEncryptionKeyFromNative(); + } + private void startAdvTest() { + //Log.d(TAG,"startAdvTest!!!"); + boolean ba_test = SystemProperties.getBoolean("persist.vendor.btstack.batest",false); + if (ba_test) { + Log.d(TAG,"startAdvTest!!!"); + EnableBroadcast(null); + } + } + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,BluetoothAdapter.ERROR); + Log.d(TAG,"action: " + action + " state: " + state); + if (state == BluetoothAdapter.STATE_ON) { + initialize_advertiser(); + startAdvTest(); + } else if (state == BluetoothAdapter.STATE_TURNING_OFF) { + if (sBroadcastService != null) + cleanup_broadcast(); + } + } + } + }; + @Override + protected boolean stop() { + Log.i(TAG, "stop()"); + if (sBroadcastService == null) { + Log.w(TAG, "stop() called before start()"); + return true; + } + notifyBroadcastEnabled(false); + if (mIsAdvertising) { + mBroadcastAdvertiser.stopBroadcastAdvertising(); + } + mAdapterService = null; + mBroadcastNativeInterface = null; + mAudioManager = null; + mIsAdvertising = false; + Looper looper = mHandler.getLooper(); + if (looper != null) { + looper.quit(); + } + setBroadcastService(null); + return true; + } + + @Override + protected void cleanup() { + Log.i(TAG, "cleanup()"); + } + public static synchronized BroadcastService getBroadcastService() { + if (sBroadcastService == null) { + Log.w(TAG, "getBroadcastService(): service is null"); + return null; + } + if (!sBroadcastService.isAvailable()) { + Log.w(TAG, "getBroadcastService(): service is not available"); + return null; + } + return sBroadcastService; + } + + /** Handles Broadcast messages. */ + private final class BroadcastMessageHandler extends Handler { + private BroadcastMessageHandler(Looper looper) { + super(looper); + } + @Override + public void handleMessage(Message msg) { + Log.v(TAG, "BroadcastMessageHandler: received message=" + msg.what); + int prev_state; + switch (msg.what) { + case MSG_ENABLE_BROADCAST: + //int prev_state; + synchronized (mBroadcastLock) { + if (VDBG) { + Log.i(TAG, "Setting broadcast state to ENABLING"); + } + prev_state = mBroadcastState; + mBroadcastState = BluetoothBroadcast.STATE_ENABLING; + } + broadcastState(mBroadcastState, prev_state); + mBroadcastNativeInterface.enableBroadcast(mCodecConfig); + break; + case MSG_DISABLE_BROADCAST: + //int prev_state; + goingDown = true; + if (!mIsAdvertising) { + Log.e(TAG, "Broadcast is not advertising"); + break; + } + synchronized(mBroadcastLock) { + if (VDBG) { + Log.i(TAG,"Disabling broadcast, setting state to DISABLING"); + } + prev_state = mBroadcastState; + mBroadcastState = BluetoothBroadcast.STATE_DISABLING; + } + broadcastState(mBroadcastState, prev_state); + mBroadcastNativeInterface.disableBroadcast(mAdvertisingSet.getAdvertiserId()); + //mBroadcastAdvertiser.stopBroadcastAdvertising(); + break; + case MSG_SET_ENCRYPTION_KEY: + //int length = msg.arg1; + mBroadcastNativeInterface.SetEncryptionKey(mEncryptionEnabled, mEncryptionLength); + if (mEncryptionLength == 0) { + for(int i = 0; i < mDefaultEncryptionLength; i++) { + BigBroadcastCode[i] = 0x00; + } + broadcastEncryptionkeySet(); + } + break; + case MSG_GET_ENCRYPTION_KEY: { + mEncryptionString = mBroadcastNativeInterface.GetEncryptionKey(); + if (mEncryptionString == null) { + Log.e(TAG,"MSG_GET_ENCRYPTION_KEY: mEncryptionString null"); + for (int i = 0; i < mDefaultEncryptionLength; i++) { + BigBroadcastCode[i] = 0x00; + } + break; + } + mEncKey= mEncryptionString.getBytes(); + Log.i(TAG, "mEncryptionString: " + mEncryptionString); + System.arraycopy(mEncKey, 0, BigBroadcastCode, 0, mEncKey.length); + if (mEncKey.length < mDefaultEncryptionLength) { + for (int i = mEncKey.length; i < mDefaultEncryptionLength; i++) { + BigBroadcastCode[i] = 0x00; + } + } + for (int i = 0;i < mDefaultEncryptionLength/2; i++) { + byte temp = BigBroadcastCode[i]; + BigBroadcastCode[i] = BigBroadcastCode[(mDefaultEncryptionLength -1) - i]; + BigBroadcastCode[(mDefaultEncryptionLength -1) - i] = temp; + } + for (int i = 0; i < 16; i++) { + Log.i(TAG,"BigBroadcastCode["+ i + "] = " + BigBroadcastCode[i]); + } + //TODO: Stub to test encryption key creation, to be removed + //Log.i(TAG,"calling setencryptionkey"); + //mBroadcastNativeInterface.SetEncryptionKey(4); + broadcastEncryptionkeySet(); + } + break; + case MSG_UPDATE_BROADCAST_ADV_SET: + break; + case MSG_SET_BROADCAST_ACTIVE: + // Call native layer to set broadcast active + //mBroadcastNativeInterface.setActiveDevice(true, mAdvertisingSet.getAdvertiserId()); + //setActiveDevice(mBroadcastDevice); + notifyBroadcastEnabled(true); + break; + case MSG_RESET_ENCRYPTION_FLAG_TIMEOUT: + Log.i(TAG,"Setting mEncKeyRefreshed to false"); + mEncKeyRefreshed = false; + break; + case MSG_FROM_NATIVE_BROADCAST_STATE: + synchronized(mBroadcastLock) { + prev_state = mBroadcastState; + mBroadcastState = msg.arg1; + if (VDBG) { + Log.i(TAG,"New broadcast state: " + mBroadcastState); + } + } + if (mBroadcastState == BluetoothBroadcast.STATE_DISABLED) { + if (goingDown) { + notifyBroadcastEnabled(false); + } + mBIGHandle = -1; + mBroadcastAdvertiser.stopBroadcastAdvertising(); + break; + } + if (prev_state != mBroadcastState) + broadcastState(mBroadcastState, prev_state); + break; + case MSG_ADV_DATA_SET: + synchronized (mBroadcastLock) { + if (VDBG) { + Log.i(TAG, "Setting broadcast state to ENABLING"); + } + prev_state = mBroadcastState; + mBroadcastState = BluetoothBroadcast.STATE_ENABLED; + } + broadcastState(mBroadcastState, prev_state); + break; + case MSG_SET_AUDIO_PATH: + //mBroadcastNativeInterface.SetupAudioPath(true,mAdvertisingSet.getAdvertiserId(),mBIGHandle,mNumBises,bis_handles); + break; + case MSG_FROM_NATIVE_CODEC_STATE: + mCodecStatus = (BluetoothCodecStatus)msg.obj; + if (IsCodecConfigChanged(mCodecStatus.getCodecConfig())) { + mBroadcastCodecConfig.updateBroadcastCodecConfig(mCodecStatus.getCodecConfig()); + mBroadcastBase.populateBase(); + mBroadcastAdvertiser.updatePAwithBase(); + } + broadcastCodecConfig(mCodecStatus); + mMediaAudio = MediaAudio.get(); + mMediaAudio.onCodecConfigChange(mBroadcastDevice, mCodecStatus, ApmConst.AudioProfiles.BROADCAST_LE); + break; + case MSG_FROM_NATIVE_ENCRYPTION_KEY: { + mEncryptionString = (String)msg.obj; + Log.d(TAG,"mEncryptionString: " + mEncryptionString); + mEncKey= mEncryptionString.getBytes(); + System.arraycopy(mEncKey, 0, BigBroadcastCode, 0, mEncKey.length); + if (mEncKey.length < mDefaultEncryptionLength) { + for (int i = mEncKey.length; i < mDefaultEncryptionLength; i++) { + BigBroadcastCode[i] = 0x00; + } + } + for (int i = 0; i < mEncKey.length; i++) { + Log.d(TAG,"mEnc[" + i +"] = " + mEncKey[i]); + } + for (int i = 0;i < mDefaultEncryptionLength/2; i++) { + byte temp = BigBroadcastCode[i]; + BigBroadcastCode[i] = BigBroadcastCode[(mDefaultEncryptionLength - 1) - i]; + BigBroadcastCode[(mDefaultEncryptionLength - 1) - i] = temp; + } + //Broadcast encyption key set + broadcastEncryptionkeySet(); + } + break; + case MSG_FROM_NATIVE_SETUP_BIG: + int setup = msg.arg1; + boolean set = (setup == 1); + if (set) { + Log.d(TAG, "BIG created: " + mBIGHandle + "with no of bises: " + mNumBises); + mNumBises = mNumBises * mNumSubGrps; + mBroadcastBase.populateBase(); + mBroadcastAdvertiser.updatePAwithBase(); + } else { + Log.d(TAG, "BIG terminated"); + mBIGHandle = -1; + //Clean up mBisInfo List + mBisInfo.clear(); + mMetaInfo.clear(); + } + break; + case MSG_FROM_NATIVE_BROADCAST_AUDIO_STATE: + int prevState = mBroadcastAudioState; + mBroadcastAudioState = msg.arg1; + if (prevState != mBroadcastAudioState) + broadcastAudioState(mBroadcastAudioState, prevState); + break; + case MSG_FROM_NATIVE_BROADCAST_ID: + if (mBroadcastAdvertiser != null) { + mBroadcastAdvertiser.startBroadcastAdvertising(); + } else { + Log.e(TAG,"Did not receive adatper state change intent, turning off Broadcast"); + prev_state = mBroadcastState; + mBroadcastState = BluetoothBroadcast.STATE_DISABLED; + broadcastState(mBroadcastState, prev_state); + } + break; + case MSG_UPDATE_BROADCAST_STATE: + prev_state = msg.arg1; + mBroadcastState = BluetoothBroadcast.STATE_DISABLED; + Log.d(TAG,"MSG_UPDATE_BROADCAST_STATE"); + broadcastState(mBroadcastState, prev_state); + break; + default: + Log.e(TAG,"unknown message msg.what = " + msg.what); + break; + } + Log.d(TAG,"Exit handleMessage"); + } + } + + private void updateBroadcastStateToHfp(int state) { + if (DBG) { + Log.d(TAG,"updateBroadcastStateToHfp"); + } + HeadsetService hfpService = HeadsetService.getHeadsetService(); + if (hfpService != null) { + hfpService.updateBroadcastState(state); + } + } + private void broadcastState(int state, int prev_state) { + if (DBG) { + Log.d(TAG, "Broadcasting broadcastState: " + state); + } + Intent intent = new Intent(BluetoothBroadcast.ACTION_BROADCAST_STATE_CHANGED); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prev_state); + intent.putExtra(BluetoothProfile.EXTRA_STATE, state); + sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); + updateBroadcastStateToHfp(state); + } + private void broadcastCodecConfig(BluetoothCodecStatus codecStatus) { + if (DBG) { + Log.d(TAG, "Broacasting broadcastCodecConfig" + codecStatus); + } + Intent intent = new Intent(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED); + intent.putExtra(BluetoothCodecStatus.EXTRA_CODEC_STATUS, codecStatus); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBroadcastDevice); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + //sendBroadcast(intent, BLUETOOTH_CONNECT); + } + + private void broadcastEncryptionkeySet() { + if (DBG) { + Log.d(TAG, "broadcastEncryptionkeySet"); + } + Intent intent = new Intent(BluetoothBroadcast.ACTION_BROADCAST_ENCRYPTION_KEY_GENERATED); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); + } + + private void broadcastAudioState(int newState, int prevState) { + Log.d(TAG, "broadcastAudioState: State:" + audioStateToString(prevState) + + "->" + audioStateToString(newState)); + Intent intent = new Intent(BluetoothBroadcast.ACTION_BROADCAST_AUDIO_STATE_CHANGED); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); + intent.putExtra(BluetoothProfile.EXTRA_STATE, newState); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); + } + + private static String audioStateToString(int state) { + switch (state) { + case BluetoothBroadcast.STATE_PLAYING: + return "PLAYING"; + case BluetoothBroadcast.STATE_NOT_PLAYING: + return "NOT_PLAYING"; + default: + break; + } + return Integer.toString(state); + } + private boolean IsCodecConfigChanged(BluetoothCodecConfig config) { + return (mCodecConfig.getSampleRate() != config.getSampleRate() || + mCodecConfig.getChannelMode() != config.getChannelMode() || + mCodecConfig.getCodecSpecific1() != config.getCodecSpecific1() || + mCodecConfig.getCodecSpecific2() != config.getCodecSpecific2()); + } + private boolean isCodecValid(BluetoothCodecConfig mCodecConfig) { + if (mCodecConfig.getCodecType() != BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3) { + return false; + } + return true; + } + + private boolean isCodecConfigValid(String config_id) { + if (broadcast_supported_config.contains(config_id)) { + Log.d(TAG,"isCodecConfigValid: config supported"); + return true; + } + Log.d(TAG,"isCodecConfigValid: config not supported"); + return false; + } + + private boolean isEncrytionLengthValid(int enc_length) { + if (enc_length == 4 || enc_length == 16) { + return true; + } + return false; + } + + private BluetoothCodecConfig buildCodecConfig(String config_id, int channel) { + //BluetoothCodecConfig cc; + int index = broadcast_supported_config.indexOf(config_id); + int sr; + long codecspecific1, codecspecific2; + String isMono = SystemProperties.get("persist.vendor.btstack.enable.broadcast_mono"); + Log.d(TAG,"buildCodecConfig:" + config_id + " index: " + index); + switch(index) { + case 0: //16_2 + sr = BluetoothCodecConfig.SAMPLE_RATE_16000; + //ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1001;//32kbps + codecspecific2 = 1; + break; + case 1: //24_2 + sr = BluetoothCodecConfig.SAMPLE_RATE_24000; + //ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1002;//48kbps + codecspecific2 = 1; + break; + case 2: //48_1 + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + //ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1004;//80kbps + codecspecific2 = 0; + break; + case 3: //48_2 + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + //ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1004;//80kbps + codecspecific2 = 1; + break; + case 4: //48_3 + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + //ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1006;//96kbps + codecspecific2 = 0; + break; + case 5: //48_4 + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + //ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1006;//96kbps + codecspecific2 = 1; + break; + case 6: //48_5 + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + //ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1007;//124kbps + codecspecific2 = 0; + break; + case 7: //48_6 + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + //ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1007;//124kbps + codecspecific2 = 1; + break; + + default: + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + //ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1007;//80kbps + codecspecific2 = 1; + break; + } + //if (isMono.isEmpty() || isMono.equals("mono")) { + // ch_mode = BluetoothCodecConfig.CHANNEL_MODE_MONO; + //} + BluetoothCodecConfig cc = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + sr, BluetoothCodecConfig.BITS_PER_SAMPLE_24, + channel, codecspecific1, codecspecific2, 0, 0); + return cc; + } + private static synchronized void setBroadcastService(BroadcastService instance) { + if (DBG) { + Log.d(TAG, "setBroadcastService(): set to: " + instance); + } + sBroadcastService = instance; + } + + private void cleanup_broadcast() { + if (DBG) Log.d (TAG, "cleanup_broadcast"); + synchronized (mBroadcastLock) { + if (mIsAdvertising) { + if (mBroadcastNativeInterface != null) + mBroadcastNativeInterface.disableBroadcast(mAdvertisingSet.getAdvertiserId()); + mBroadcastAdvertiser.stopBroadcastAdvertising(); + int prev_state = mBroadcastState; + mBroadcastState = BluetoothBroadcast.STATE_DISABLED; + broadcastState(mBroadcastState, prev_state); + } + } + } + public boolean EnableBroadcast(String packageName) { + if (DBG) Log.d (TAG, "EnableBroadcast"); + + if (mBroadcastState != BluetoothBroadcast.STATE_DISABLED) { + return false; + } + Message msg = mHandler.obtainMessage(MSG_ENABLE_BROADCAST); + mHandler.sendMessage(msg); + return true; + } + public boolean DisableBroadcast(String packageName) { + if (DBG) Log.d (TAG, "DisableBroadcast: state " + mBroadcastState); + + if (mBroadcastState == BluetoothBroadcast.STATE_DISABLING || + mBroadcastState == BluetoothBroadcast.STATE_DISABLED) { + return true; + } else if (mBroadcastState != BluetoothBroadcast.STATE_ENABLED && + mBroadcastState != BluetoothBroadcast.STATE_STREAMING) { + Log.d(TAG,"Broadcast is not enabled yet"); + return false; + } + Message msg = mHandler.obtainMessage(MSG_DISABLE_BROADCAST); + mHandler.sendMessage(msg); + return true; + } + public boolean SetEncryption(boolean enable, int enc_len, + boolean use_existing, String packageName) { + if (DBG) Log.d (TAG,"SetEncryption"); + + mEncryptionEnabled = enable; + if (enable) { + if (!isEncrytionLengthValid(enc_len)) { + if (DBG) Log.d (TAG,"SetEncryption: invalid encrytion length requested"); + return false; + } + } else { + Log.d(TAG,"Selected unencrypted"); + enc_len = 0; + } + if (!use_existing) { + Log.d (TAG,"Generate new ecrytpion key of lenght = " + enc_len); + mEncryptionLength = enc_len; + if (mBroadcastState == BluetoothBroadcast.STATE_ENABLED || + mBroadcastState == BluetoothBroadcast.STATE_STREAMING) { + mEncKeyRefreshed = true; + Message msg = mHandler.obtainMessage(MSG_RESET_ENCRYPTION_FLAG_TIMEOUT); + mHandler.sendMessageDelayed(msg, 1000); + } + Message msg = mHandler.obtainMessage(MSG_SET_ENCRYPTION_KEY); + mHandler.sendMessage(msg); + } + return true; + } + + public byte[] GetEncryptionKey(String packageName) { + if (DBG) Log.d (TAG,"GetBroadcastEncryptionKey: package name = " + packageName); + + return BigBroadcastCode; + } + + public int GetBroadcastStatus(String packageName) { + if (DBG) Log.d (TAG,"GetBroadcastStatus: state = " + mBroadcastState + " package name = " + packageName); + return mBroadcastState; + } + + public boolean isBroadcastActive() { + if (mBroadcastDeviceIsActive == false) { + Log.d (TAG,"isBroadcastActive: Broadcast is turned to off"); + return false; + } + if (DBG) Log.d (TAG,"isBroadcastActive"); + return ((mBroadcastState == BluetoothBroadcast.STATE_ENABLED) || + (mBroadcastState == BluetoothBroadcast.STATE_STREAMING)); + } + + public BluetoothDevice getBroadcastDevice() { + if (DBG) Log.d (TAG,"getBroadcastDevice"); + return mBroadcastDevice; + } + + public String getBroadcastAddress() { + if (DBG) Log.d (TAG,"getBroadcastAddress"); + return mBroadcastAddress; + } + + public byte[] getBroadcastId() { + Log.d(TAG,"getBroadcastId: " + mBroadcastID); + return mBroadcastID; + } + + public boolean isBroadcastStreamingEncrypted() { + return mEncryptionEnabled; + } + + public boolean isBroadcastStreaming() { + return (mBroadcastState == BluetoothBroadcast.STATE_STREAMING); + } + + public String BroadcastGetAdvAddress() { + if (DBG) Log.d (TAG,"BroadcastGetAdvAddress: " + mAdvAddress); + return mAdvAddress; + } + + public int getNumSubGroups() { + if (DBG) Log.d (TAG,"getNumSubGroups: " + mNumSubGrps); + return mNumSubGrps; + } + + public int BroadcastGetAdvAddrType() { + return mAdvAddressType; + } + + public int BroadcatGetAdvHandle() { + //check if advertising + return mAdvertisingSet.getAdvertiserId(); + } + + public int BroadcastGetAdvInterval() { + return mPaInt; + } + public List BroadcastGetBisInfo() { + if (isBroadcastStreaming()) { + return mBisInfo; + } + Log.d(TAG,"BroadcastGetBisInfo: Broadcast is not active"); + return mBisInfo; + } + + public Map BroadcastGetMetaInfo() { + if (isBroadcastStreaming()) { + return mMetaInfo; + } + Log.d(TAG,"BroadcastGetMetaInfo: Broadcast is not active"); + return mMetaInfo; + } + public byte[] BroadcastGetMetadata() { + if (isBroadcastStreaming()) { + return mBroadcastBase.getMetadataContext(); + } + Log.d(TAG,"BroadcastGetMetadata: Broadcast is not active"); + return mBroadcastBase.getMetadataContext(); + } + public void setCodecPreference(String config_id, int ch_mode) { + if (isCodecConfigValid(config_id)) { + setCodecPreference(buildCodecConfig(config_id, ch_mode)); + } + } + public void setCodecPreference(BluetoothCodecConfig newConfig) { + if (DBG) Log.d (TAG, "setCodecPreference"); + if (newConfig.getCodecType() != BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3) { + Log.e(TAG, "setCodecPreference: Invalid codec for broadcast mode: " + newConfig.getCodecType()); + return; + } + //mBroadcastCodecConfig.updateCodecConfig(newConfig); + if (mBroadcastState != BluetoothBroadcast.STATE_DISABLED) + mBroadcastNativeInterface.setCodecConfigPreference(mAdvertisingSet.getAdvertiserId(),newConfig); + } + + public void GetEncryptionKeyFromNative() { + Log.e(TAG,"GetEncryptionKeyFromNative"); + Message msg = mHandler.obtainMessage(MSG_GET_ENCRYPTION_KEY); + mHandler.sendMessage(msg); + } + private void setup_isodatapath(int adv_id, int big_handle,int num_bises, int[] bises) { + } + /* LE HAP broadcast hooks */ + public boolean startHAPBroadcast() { + if (isBroadcastActive()) { + //TODO: update codec config with HAP HQ mode + //Terminate BIG if created + //Notify codec config change to stack + //Create BIG and update BASE + } else { + //TODO: update codec config with HAP HQ mode + //Start Adv + //Existing encryption key will be used for HAP as only music streaming is supported + //Announcement content type will not be covered + } + return true; + } + public boolean stopHAPBroadcast() { + //TODO: DisableAudioPath + //Terminate BIG + //update state to disabling + //stop Adv + //reset codec config to default config + return true; + } + public void removeActiveDevice() { + if (DBG) Log.d (TAG,"removeActiveDevice"); + //int [] bis_handles = {-1, -1}; + if (mBroadcastDeviceIsActive == false) { + Log.d (TAG,"removeActiveDevice: mBADeviceIsActive is false, already removed"); + return; + } + mBroadcastDeviceIsActive = false; + synchronized (mBroadcastLock) { + if (mIsAdvertising && + (mBroadcastState == BluetoothBroadcast.STATE_ENABLED || + mBroadcastState == BluetoothBroadcast.STATE_STREAMING)) { + mBroadcastNativeInterface.disableBroadcast(mAdvertisingSet.getAdvertiserId()); + //mBroadcastAdvertiser.stopBroadcastAdvertising(); + } + if (!mBroadcastNativeInterface.setActiveDevice(false, mAdvertisingSet.getAdvertiserId())) { + Log.d(TAG,"SetActiveNative failed"); + } + } + //notifyBroadcastEnabled(false); + } + + public BluetoothCodecStatus getCodecStatus() { + if (DBG) Log.d (TAG,"getCodecStatus"); + BluetoothCodecConfig[] mBroadcastCodecConfig = {mCodecConfig}; + return (new BluetoothCodecStatus(mCodecConfig, mBroadcastCodecConfig, mBroadcastCodecConfig)); + } + public int setActiveDevice(BluetoothDevice device) { + if (DBG) Log.d (TAG,"setActiveDevice"); + if (device == null) { + removeActiveDevice(); + return ActiveDeviceManagerService.SHO_SUCCESS; + } + if (!Objects.equals(device, mBroadcastDevice)) { + Log.d(TAG,"setActiveDevice: Not a Broadcast device"); + return ActiveDeviceManagerService.SHO_FAILED; + } + if (!mBroadcastNativeInterface.setActiveDevice(true, mAdvertisingSet.getAdvertiserId())) { + Log.d(TAG,"SetActiveNative failed"); + return ActiveDeviceManagerService.SHO_FAILED; + } + mBroadcastDeviceIsActive = true; + + return ActiveDeviceManagerService.SHO_SUCCESS; + } + + public void notifyBroadcastEnabled(boolean enabled) { + if (DBG) Log.d (TAG,"notifyBroadcastEnabled: " + enabled); + ActiveDeviceManagerService activeDeviceManager = ActiveDeviceManagerService.get(); + if(activeDeviceManager == null) { + Log.e(TAG,"ActiveDeviceManagerService not started. Return"); + return; + } + if (enabled) + activeDeviceManager.enableBroadcast(mBroadcastDevice); + else + activeDeviceManager.disableBroadcast(); + } + + public void updateMetadataFromAvrcp(MediaMetadata data) { + if (DBG) Log.d (TAG,"updateMetadataFromAvrcp"); + mTrackMetadata = new TrackMetadata(data); + } + public void messageFromNative(BroadcastStackEvent event) { + if (DBG) Log.d (TAG,"messageFromNative: event " + event); + switch(event.type) { + case BroadcastStackEvent.EVENT_TYPE_BROADCAST_STATE_CHANGED: + { + Message msg = + mHandler.obtainMessage(MSG_FROM_NATIVE_BROADCAST_STATE, + event.valueInt, event.advHandle); + mHandler.sendMessage(msg); + } + break; + case BroadcastStackEvent.EVENT_TYPE_BROADCAST_AUDIO_STATE_CHANGED: + { + Message msg = + mHandler.obtainMessage(MSG_FROM_NATIVE_BROADCAST_AUDIO_STATE, + event.valueInt, event.advHandle); + mHandler.sendMessage(msg); + } + break; + case BroadcastStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED: + { + Message msg = + mHandler.obtainMessage(MSG_FROM_NATIVE_CODEC_STATE); + msg.obj = event.codecStatus; + mHandler.sendMessage(msg); + } + break; + case BroadcastStackEvent.EVENT_TYPE_ENC_KEY_GENERATED: + { + Message msg = + mHandler.obtainMessage(MSG_FROM_NATIVE_ENCRYPTION_KEY); + msg.obj = event.key; + mHandler.sendMessage(msg); + } + break; + case BroadcastStackEvent.EVENT_TYPE_SETUP_BIG: + { + mBIGHandle = event.bigHandle; + if (event.valueInt == 1) + mNumBises = event.NumBises; + Message msg = + mHandler.obtainMessage(MSG_FROM_NATIVE_SETUP_BIG,event.valueInt, event.advHandle); + mHandler.sendMessage(msg); + } + break; + case BroadcastStackEvent.EVENT_TYPE_BROADCAST_ID_GENERATED: + { + Message msg = + mHandler.obtainMessage(MSG_FROM_NATIVE_BROADCAST_ID); + for (int i = 0; i < mBroadcastIdLength; i++) { + mBroadcastID[i] = (byte)event.BroadcastId[i]; + Log.d(TAG,"mBroadcastID["+i+"]" + " = " + mBroadcastID[i]); + } + mHandler.sendMessage(msg); + } + break; + default: + Log.e (TAG,"messageFromNative: Invalid"); + } + } + class TrackMetadata { + private String title; + private String artistName; + private String albumName; + private String genre; + private long playingTimeMs; + + public TrackMetadata(MediaMetadata data) { + if (data == null) return; + artistName = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_ARTIST)); + albumName = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_ALBUM)); + title = data.getString(MediaMetadata.METADATA_KEY_TITLE); + genre = stringOrBlank(data.getString(MediaMetadata.METADATA_KEY_GENRE)); + playingTimeMs = data.getLong(MediaMetadata.METADATA_KEY_DURATION); + } + private String stringOrBlank(String s) { + return s == null ? new String() : s; + } + } + class BroadcastAdvertiser { + public BroadcastAdvertiser() { + Log.i(TAG,"BroadcastAdvertiser"); + mCallback = new BroadcastAdvertiserCallback(); + mAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser(); + if (mAdvertiser == null) { + Log.e(TAG, "BroadcastAdvertiser: mAdvertiser is null"); + } + } + public void startBroadcastAdvertising() { + Log.i(TAG,"startBroadcastAdvertising"); + if (mAdvertiser == null) { + Log.e(TAG,"startBroadcastAdvertising: Advertiser is null"); + int prev_state = mBroadcastState; + mBroadcastState = BluetoothBroadcast.STATE_DISABLED; + broadcastState(mBroadcastState, prev_state); + return; + } + AdvertisingSetParameters.Builder adv_param = + new AdvertisingSetParameters.Builder(); + adv_param.setLegacyMode(false); + adv_param.setConnectable(false); + adv_param.setScannable(false); + adv_param.setInterval(AdvertisingSetParameters.INTERVAL_MIN); //100msec + adv_param.setTxPowerLevel(mTxPowerLevel); + adv_param.setPrimaryPhy(1); + adv_param.setSecondaryPhy(mSecPhy); + AdvertiseData AdvData = new AdvertiseData.Builder() + .setIncludeDeviceName(true) + .addServiceData(new ParcelUuid(BROADCAST_AUDIO_UUID), mBroadcastID).build(); + PeriodicAdvertisingParameters.Builder periodic_param = new PeriodicAdvertisingParameters.Builder(); + periodic_param.setIncludeTxPower(true); + periodic_param.setInterval(mPaInt); + AdvertiseData PeriodicData = new AdvertiseData.Builder().addServiceData(new ParcelUuid(BASIC_AUDIO_UUID), new byte[0]).build(); + Log.i(TAG,"Calling startAdvertisingSet"); + mAdvertiser.startAdvertisingSet(adv_param.build(), AdvData, null, periodic_param.build(), PeriodicData, 0, 0, mCallback); + } + public void stopBroadcastAdvertising() { + Log.i(TAG,"stopBroadcastAdvertising"); + if (mAdvertiser != null) + mAdvertiser.stopAdvertisingSet(mCallback); + } + + public void updatePAwithBase() { + Log.i(TAG,"updatePAwithBase"); + AdvertiseData PeriodicData = new AdvertiseData.Builder().addServiceData(new ParcelUuid(BASIC_AUDIO_UUID), mBroadcastBase.getBroadcastBaseInfo()).build(); + mAdvertisingSet.setPeriodicAdvertisingData(PeriodicData); + } + } + + private class BroadcastAdvertiserCallback extends AdvertisingSetCallback { + @Override + public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, + int status) { + Log.i(TAG, "onAdvertisingSetStarted status " + status + + " advertisingSet: " + advertisingSet + " txPower " + txPower); + if (status != BluetoothGatt.GATT_SUCCESS) { + Log.e(TAG,"Failed to start Broadcast Advertisement"); + int prev_state = mBroadcastState; + mBroadcastState = BluetoothBroadcast.STATE_DISABLED; + broadcastState(mBroadcastState,prev_state); + } + if (status == BluetoothGatt.GATT_SUCCESS) { + mAdvertisingSet = advertisingSet; + mIsAdvertising = true; + int prev_state = mBroadcastState; + mBroadcastState = BluetoothBroadcast.STATE_ENABLED; + Log.i(TAG,"onAdvertisingSetStarted: adv_id = " + advertisingSet.getAdvertiserId() + "copied id = " + mAdvertisingSet.getAdvertiserId()); + broadcastState(mBroadcastState,prev_state); + if (mHandler.hasMessages(MSG_RESET_ENCRYPTION_FLAG_TIMEOUT)) { + Message msg = + mHandler.obtainMessage(MSG_SET_BROADCAST_ACTIVE); + mHandler.sendMessageDelayed(msg,600); + } else { + notifyBroadcastEnabled(true); + } + int mChMode = mCodecConfig.getChannelMode(); + switch (mChMode) { + case BluetoothCodecConfig.CHANNEL_MODE_MONO: + case BluetoothCodecConfig.CHANNEL_MODE_JOINT_STEREO: + mNumBises = 1 * mNumSubGrps; + break; + case BluetoothCodecConfig.CHANNEL_MODE_STEREO: + mNumBises = 2 * mNumSubGrps; + break; + default: + Log.e(TAG,"channel mode unknown"); + } + mBroadcastBase.populateBase(); + mBroadcastAdvertiser.updatePAwithBase(); + mAdvertisingSet.getOwnAddress(); + } + } + + @Override + public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) { + Log.i(TAG, "onAdvertisingSetStopped advertisingSet: " + advertisingSet); + mIsAdvertising = false; + int prev_state = mBroadcastState; + if (!goingDown && mBroadcastDeviceIsActive) { + Log.d(TAG,"onAdvertisingSetStopped: Unexpected Broadcast turn off"); + notifyBroadcastEnabled(false); + } + if (goingDown) { + Message msg = mHandler.obtainMessage(MSG_UPDATE_BROADCAST_STATE, + BluetoothBroadcast.STATE_DISABLING); + mHandler.sendMessageDelayed(msg,500); + goingDown = false; + } else { + mBroadcastState = BluetoothBroadcast.STATE_DISABLED; + broadcastState(mBroadcastState, prev_state); + } + } + + @Override + public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable, + int status) { + Log.i(TAG, "onAdvertisingEnabled advertisingSet: " + advertisingSet + + " status " + status + " enable: " + enable); + } + + @Override + public void onAdvertisingDataSet(AdvertisingSet advertisingSet, int status) { + Log.i(TAG, "onAdvertisingDataSet advertisingSet: " + advertisingSet + + " status " + status); + if (status == BluetoothGatt.GATT_SUCCESS) { + Log.i(TAG, "onAdvertisingDataSet: Base Info updated"); + } + } + @Override + public void onAdvertisingParametersUpdated(AdvertisingSet advertisingSet, + int txPower, int status) { + Log.i(TAG, "onAdvertisingParametersUpdated advertisingSet: " + advertisingSet + + " status " + status + " txPower " + txPower); + } + + @Override + public void onOwnAddressRead(AdvertisingSet advertisingSet, int addressType, + String address) { + Log.i(TAG, "onOwnAddressRead advertisingSet: " + advertisingSet + + " address " + address + " addressType " + addressType); + mAdvAddress = address; + mAdvAddressType = addressType; + } + + } + class BroadcastBase { + private final int LC3_SAMPLE_RATE_8000 = 0x01; + private final int LC3_SAMPLE_RATE_16000 = 0x02; + private final int LC3_SAMPLE_RATE_24000 = 0x03; + private final int LC3_SAMPLE_RATE_32000 = 0x04; + private final int LC3_SAMPLE_RATE_44100 = 0x05; + private final int LC3_SAMPLE_RATE_48000 = 0x06; + + int presentationDelay = 0x009C40; + byte [] mPresentationDelay = new byte[3]; + byte [] mCodecId = new byte[5]; + byte [] mCodecSpecificLength = new byte[1]; + byte [] mCodecSpecificSampleRate = new byte[3]; + byte [] mCodecSpecificFrameDuration = new byte[3]; + byte [] mCodecSpecificAudioLocation = new byte[6]; + byte [] mCodecSpecificOctetsPerFrame = new byte[3]; + byte [] mCodecSpecificBlocksPerSdu = new byte[3]; + byte [] mCodecSpecificLengthL2 = new byte[1]; + byte [] mCodecSpecificSampleRateL2 = new byte[3]; + byte [] mCodecSpecificFrameDurationL2 = new byte[3]; + byte [] mCodecSpecificAudioLocationL2 = new byte[6]; + byte [] mCodecSpecificOctetsPerFrameL2 = new byte[3]; + byte [] mCodecSpecificBlocksPerSduL2 = new byte[3]; + byte [] mMetadataLength = new byte[1]; + byte [] mMetadataContext = new byte[3]; + byte [] mNumSubgroups = new byte[1]; + byte [] mL2CodecID = new byte[1]; + byte [] mL2CodecSpecificLength = new byte[1]; + byte [] mL2mMetadataLength = new byte[1]; + byte [] mL2NumBises = new byte[1]; + byte [] mL2BisIndices = new byte[2]; + byte [] mL3BisIndex = new byte[1]; + byte [] mL3CodecSpecificLength = new byte[1]; + byte [] mL3CodecSpecificAudioLocation = new byte[6]; + byte mSampleRateLength = 2; + byte mSampleRateType = 0x01; + byte mFrameDurationLength = 2; + byte mFrameDurationType = 0x02; + byte mFrameDuration_7_5 = 0x00;//7.5 msec + byte mFrameDuration_10 = 0x01;//10msec + byte mAudioLocationLength = 5; + byte mAudioLocationType = 0x03; + byte mAudioLocationLeft = 0x01; + byte mAudioLocationRight = 0x02; + byte mAudioLocationCentre = 0x04; + byte mOctetsPerFrameLength = 3; + byte mOctestPerFrameType = 0x04; + byte mBlocksPerSduLength = 2; + byte mBlocksPerSduType = 0x05; + long LC3_CODEC_ID_OLD = 0x0000000001; + long LC3_CODEC_ID = 0x0000000006; + byte mCodecConfigLength = 0x10; //to be changed + byte mMediaContextType = 0x10; + byte [] BroadcastBaseArray = null; + //Metadata AD type + //Metadata + public BroadcastBase() { + //mccid = 0; + //int presentationDelay = 0x000014; + if (mPD == 20) { + Log.d(TAG,"Presentation Delay is set to 20msec"); + presentationDelay = 0x004E20; + } + if (mNewVersion) { + mPresentationDelay = intTobyteArray(presentationDelay, 3); + mNumSubgroups[0] = (byte)mNumSubGrps; + } else { + mPresentationDelay = intTobyteArray(presentationDelay, 3); + if (new_codec_id) { + mCodecId = longTobyteArray(LC3_CODEC_ID,5); + } else { + mCodecId = longTobyteArray(LC3_CODEC_ID_OLD,5); + } + mCodecSpecificLength[0] = mCodecConfigLength; + mCodecSpecificSampleRate = updateSampleRate(); + mCodecSpecificFrameDuration = updateFrameDuration(); + mCodecSpecificAudioLocation = updateAudioLocation(0); + mCodecSpecificOctetsPerFrame = updateOctetsPerFrame(); + mMetadataLength[0] = (byte)0x03; + int index = 0; + mMetadataContext[index++] = (byte)0x02; //length + mMetadataContext[index++] = (byte)mMediaContextType; //Type + mMetadataContext[index++] = (byte)0x01; //Value Music + mNumSubgroups[0] = (byte)mNumSubGrps; // only one set of broadcast is supported. + } + } + public byte [] getBroadcastBaseInfo() { + return BroadcastBaseArray; + } + public void updateBIGhandle(int handle) { + mBIGHandle = handle; + } + + public byte[] getMetadataContext() { + return mMetadataContext; + } + + public int getNumSubGroups() { + return mNumSubgroups[0]; + } + public byte [] updateSampleRate() { + int SR = mCodecConfig.getSampleRate(); + byte bytevalue; + switch (SR) { + case BluetoothCodecConfig.SAMPLE_RATE_48000: + if (mNewVersion) { + bytevalue = (byte)0x08; + } else { + bytevalue = (byte)0x06; + } + break; + case BluetoothCodecConfig.SAMPLE_RATE_44100: + if (mNewVersion) { + bytevalue = (byte)0x07; + } else { + bytevalue = (byte)0x05; + } + break; + case BluetoothCodecConfig.SAMPLE_RATE_32000: + if (mNewVersion) { + bytevalue = (byte)0x06; + } else { + bytevalue = (byte)0x04; + } + break; + case BluetoothCodecConfig.SAMPLE_RATE_24000: + if (mNewVersion) { + bytevalue = (byte)0x05; + } else { + bytevalue = (byte)0x03; + } + break; + case BluetoothCodecConfig.SAMPLE_RATE_16000: + if (mNewVersion) { + bytevalue = (byte)0x03; + } else { + bytevalue = (byte)0x02; + } + break; + case BluetoothCodecConfig.SAMPLE_RATE_8000: + bytevalue = (byte)0x01; + break; + default: + if (mNewVersion) { + bytevalue = (byte)0x08; + } else { + bytevalue = (byte)0x06; + } + } + byte [] ltv = {mSampleRateLength, mSampleRateType, bytevalue}; + return ltv; + } + public byte[] updateOctetsPerFrame() { + long bitrate = (int) mCodecConfig.getCodecSpecific1(); + long frameDuration = (int) mCodecConfig.getCodecSpecific2(); + byte bytevalue; + //Update OctetsPerFrame based on frame duration + switch ((int)bitrate) { + case 1001: + if (frameDuration == 0) { //7.5msec + bytevalue = (byte)30; + } else { //10msec + bytevalue = (byte)40; + } + break; + case 1002: + if (frameDuration == 0) { + bytevalue = (byte)45; + } else { + bytevalue = (byte)60; + } + break; + case 1004: + if (frameDuration == 0) { + bytevalue = (byte)75; + } else { + bytevalue = (byte)100; + } + break; + case 1006: + if (frameDuration == 0) { + bytevalue = (byte)90; + } else { + bytevalue = (byte)120; + } + break; + case 1007: + if (frameDuration == 0) { + bytevalue = (byte)117; + } else { + bytevalue = (byte)155; + } + break; + default: + bytevalue = (byte)100; + } + Log.d(TAG,"updateOctetsPerFrame: " + bytevalue); + byte [] ltv = {mOctetsPerFrameLength, mOctestPerFrameType, bytevalue, 0x00}; + return ltv; + } + private byte[] updateBlocksPerSdu() { + byte[] ltv = {mBlocksPerSduLength, mBlocksPerSduType,0x01}; + return ltv; + } + + public byte [] updateHAPSampleRate() { + int SR = mCodecConfig.getSampleRate(); + byte bytevalue; + switch (SR) { + case BluetoothCodecConfig.SAMPLE_RATE_16000: + bytevalue = (byte)0x02; + break; + case BluetoothCodecConfig.SAMPLE_RATE_24000: + bytevalue = (byte)0x03; + default: + bytevalue = (byte)0x02; + } + byte[] ltv = {mSampleRateLength, mSampleRateType, bytevalue}; + return ltv; + } + public byte [] updateHapOctetsPerFrame() { + long bitrate = mCodecConfig.getCodecSpecific1(); + long frameDuration = (int) mCodecConfig.getCodecSpecific2(); + byte bytevalue; + //Update OctetsPerFrame based on frame duration + switch((int)bitrate) { + case 1001: + if (frameDuration == 0) { //7.5msec + bytevalue = (byte)30; + } else { //10msec + bytevalue = (byte)40; + } + break; + case 1002: + if (frameDuration == 0) { + bytevalue = (byte)45; + } else { + bytevalue = (byte)60; + } + break; + default: + bytevalue = (byte)40; + } + byte [] ltv = {mOctetsPerFrameLength, mOctestPerFrameType, bytevalue, 0x00}; + return ltv; + } + public byte [] updateAudioLocation(int bis_index) { + int ch_mode = mCodecConfig.getChannelMode(); + byte ch = 0; + if (bis_index == 0) { + // stereo + if (ch_mode == BluetoothCodecConfig.CHANNEL_MODE_STEREO || + ch_mode == BluetoothCodecConfig.CHANNEL_MODE_JOINT_STEREO) + ch = (byte)0x03; + else if (ch_mode == BluetoothCodecConfig.CHANNEL_MODE_MONO) + ch = (byte)0x00; + } else { + if (ch_mode == BluetoothCodecConfig.CHANNEL_MODE_STEREO) { + int bises = (mNumBises/((int)mNumSubgroups[0])); + ch = (byte)(mAudioLocationRight - (bis_index % bises)); + } else if (ch_mode == BluetoothCodecConfig.CHANNEL_MODE_JOINT_STEREO) { + ch = (byte)0x03; + } else if (ch_mode == BluetoothCodecConfig.CHANNEL_MODE_MONO) { + ch = (byte)0x00; + } + } + byte [] loc = {mAudioLocationLength, mAudioLocationType, ch, 0x00, 0x00, 0x00}; + return loc; + } + public byte[] updateFrameDuration() { + byte mFD = mFrameDuration_10; + if (mCodecConfig.getCodecSpecific2() == 0) { + Log.d(TAG,"updateFrameDuration: 7.5msec"); + mFD = mFrameDuration_7_5; + } else { + Log.d(TAG,"updateFrameDuration: 10 msec"); + } + byte[] ltv = {mFrameDurationLength,mFrameDurationType,mFD}; + return ltv; + } + public byte[] intTobyteArray(int intValue, int bytelen) { + byte [] val = new byte[bytelen]; + for (int i = 0; i < bytelen; i++) { + val[(bytelen - 1) -i] = (byte)((intValue >> (8 *(bytelen - (i + 1)))) & 0x000000FF); + } + return val; + } + public byte [] longTobyteArray(long longValue, int bytelen) { + byte [] val = new byte[bytelen]; + for (int i = 0; i < bytelen; i++) { + val[(bytelen - 1) -i] = (byte)((longValue >> (8 *(bytelen - (i + 1)))) & 0x00000000000000FF); + } + return val; + } + public int calculateBisPerGroup() { + int mChMode = mCodecConfig.getChannelMode(); + int numbis = 2; + switch (mChMode) { + case BluetoothCodecConfig.CHANNEL_MODE_MONO: + case BluetoothCodecConfig.CHANNEL_MODE_JOINT_STEREO: + Log.d(TAG,"BisPerGroup is 1"); + numbis = 1; + break; + case BluetoothCodecConfig.CHANNEL_MODE_STEREO: + Log.d(TAG,"BisPerGroup is 2"); + numbis = 2; + break; + default: + Log.e(TAG,"channel mode unknown"); + } + return numbis; + } + public void populateBase() { + if (DBG) Log.d(TAG,"populateBase"); + byte [] baseL1 = populate_level1_base(); + byte [] baseL2 = populate_level2_base(); + ByteArrayOutputStream ByteStr = new ByteArrayOutputStream(); + ByteStr.write(baseL1, 0, baseL1.length); + ByteStr.write(baseL2, 0, baseL2.length); + if (!mNewVersion) { + byte [] baseL3 = populate_level3_base(); + ByteStr.write(baseL3, 0, baseL3.length); + } + BroadcastBaseArray = ByteStr.toByteArray(); + } + private byte [] populate_level1_base() { + ByteArrayOutputStream ByteStr = new ByteArrayOutputStream(); + if (mNewVersion) { + mPresentationDelay = intTobyteArray(presentationDelay, 3); + mNumSubgroups[0] = (byte)mNumSubGrps;//calculate based on num bises and channel mode + ByteStr.write(mPresentationDelay, 0, mPresentationDelay.length); + ByteStr.write(mNumSubgroups, 0, mNumSubgroups.length); + } else { + mPresentationDelay = intTobyteArray(presentationDelay, 3); + if (new_codec_id) { + mCodecId = longTobyteArray(LC3_CODEC_ID,5); + } else { + mCodecId = longTobyteArray(LC3_CODEC_ID_OLD,5); + } + mCodecSpecificLength[0] = mCodecConfigLength; + mCodecSpecificSampleRate = updateSampleRate(); + mCodecSpecificFrameDuration = updateFrameDuration(); + mCodecSpecificAudioLocation = updateAudioLocation(0); + mCodecSpecificOctetsPerFrame = updateOctetsPerFrame(); + mMetadataLength[0] = (byte)0x03; + byte [] mediacontext = {2, mMediaContextType, (byte)0x01}; + mNumSubgroups[0] = (byte)mNumSubGrps;//calculate based on num bises and channel mode + + ByteStr.write(mPresentationDelay, 0, mPresentationDelay.length); + ByteStr.write(mCodecId, 0, mCodecId.length); + ByteStr.write(mCodecSpecificLength, 0, mCodecSpecificLength.length); + ByteStr.write(mCodecSpecificSampleRate, 0, mCodecSpecificSampleRate.length); + ByteStr.write(mCodecSpecificFrameDuration, 0, mCodecSpecificFrameDuration.length); + ByteStr.write(mCodecSpecificAudioLocation, 0, mCodecSpecificAudioLocation.length); + ByteStr.write(mCodecSpecificOctetsPerFrame, 0, mCodecSpecificOctetsPerFrame.length); + ByteStr.write(mMetadataLength, 0, mMetadataLength.length); + ByteStr.write(mMetadataContext, 0, mMetadataContext.length); + ByteStr.write(mNumSubgroups, 0, mNumSubgroups.length); + } + return ByteStr.toByteArray(); + } + private byte [] populate_level2_base() { + Log.d(TAG,"populate_level2_base, subgroup = " + mNumSubgroups[0]); + ByteArrayOutputStream ByteStr = new ByteArrayOutputStream(); + byte [] metalength = new byte[1]; + int bisPerGroup = calculateBisPerGroup();//mNumBises/mNumSubGrps; + byte [] numBises = new byte[1]; + numBises = intTobyteArray(bisPerGroup,1); + byte [] bisInd = new byte[bisPerGroup]; + if (mNewVersion) { + byte[] mcid = new byte[1]; + if (new_codec_id) { + mcid = longTobyteArray(LC3_CODEC_ID,5); + } else { + mcid = longTobyteArray(LC3_CODEC_ID_OLD,5); + } + mMetadataLength[0] = (byte)0x04; + byte [] mediacontext = {3, 2, (byte)0x04, (byte)0x00}; + int codecConfigLength = 0x13; + mCodecSpecificLength = intTobyteArray(codecConfigLength, 1); + for (int i = 0; i < mNumSubgroups[0]; i++) { + if (mPartialSimulcast) { + if (i < (mNumSubgroups[0] / 2)) { + //High quality + ByteStr.write(numBises, 0, numBises.length); + ByteStr.write(mcid, 0, mcid.length); + mCodecSpecificSampleRate = updateSampleRate(); + mCodecSpecificFrameDuration = updateFrameDuration(); + mCodecSpecificAudioLocation = updateAudioLocation(0); + mCodecSpecificOctetsPerFrame = updateOctetsPerFrame(); + mCodecSpecificBlocksPerSdu= updateBlocksPerSdu(); + ByteStr.write(mCodecSpecificLength, 0, mCodecSpecificLength.length); + ByteStr.write(mCodecSpecificSampleRate, 0, mCodecSpecificSampleRate.length); + ByteStr.write(mCodecSpecificFrameDuration, 0, mCodecSpecificFrameDuration.length); + ByteStr.write(mCodecSpecificAudioLocation, 0, mCodecSpecificAudioLocation.length); + ByteStr.write(mCodecSpecificOctetsPerFrame, 0, mCodecSpecificOctetsPerFrame.length); + ByteStr.write(mCodecSpecificBlocksPerSdu, 0, mCodecSpecificBlocksPerSdu.length); + ByteStr.write(mMetadataLength, 0, mMetadataLength.length); + ByteStr.write(mediacontext, 0, mediacontext.length); + byte[] level3 = populate_level3_new_base(i, mcid, mCodecSpecificSampleRate, + mCodecSpecificFrameDuration, + mCodecSpecificOctetsPerFrame, + mCodecSpecificBlocksPerSdu, + mediacontext); + ByteStr.write(level3, 0, level3.length); + mMetaInfo.put(i,new MetadataLtv(mediacontext)); + } else { + //Low quality + ByteStr.write(numBises, 0, numBises.length); + ByteStr.write(mcid, 0, mcid.length); + mCodecSpecificSampleRateL2= updateHAPSampleRate(); + mCodecSpecificFrameDurationL2= updateFrameDuration(); + mCodecSpecificAudioLocationL2= updateAudioLocation(0); + mCodecSpecificOctetsPerFrameL2= updateHapOctetsPerFrame(); + mCodecSpecificBlocksPerSduL2= updateBlocksPerSdu(); + ByteStr.write(mCodecSpecificLength, 0, mCodecSpecificLength.length); + ByteStr.write(mCodecSpecificSampleRateL2, 0, mCodecSpecificSampleRateL2.length); + ByteStr.write(mCodecSpecificFrameDurationL2, 0, mCodecSpecificFrameDurationL2.length); + ByteStr.write(mCodecSpecificAudioLocationL2, 0, mCodecSpecificAudioLocationL2.length); + ByteStr.write(mCodecSpecificOctetsPerFrameL2, 0, mCodecSpecificOctetsPerFrameL2.length); + ByteStr.write(mCodecSpecificBlocksPerSduL2, 0, mCodecSpecificBlocksPerSduL2.length); + ByteStr.write(mMetadataLength, 0, mMetadataLength.length); + ByteStr.write(mediacontext, 0, mediacontext.length); + byte[] level3 = populate_level3_new_base(i, mcid, mCodecSpecificSampleRateL2, + mCodecSpecificFrameDurationL2, + mCodecSpecificOctetsPerFrameL2, + mCodecSpecificBlocksPerSduL2, + mediacontext); + ByteStr.write(level3, 0, level3.length); + mMetaInfo.put(i,new MetadataLtv(mediacontext)); + } + } else { + ByteStr.write(numBises, 0, numBises.length); + ByteStr.write(mcid, 0, mcid.length); + mCodecSpecificLengthL2 = intTobyteArray(codecConfigLength, 1); + mCodecSpecificSampleRateL2 = updateSampleRate(); + mCodecSpecificFrameDurationL2 = updateFrameDuration(); + mCodecSpecificAudioLocationL2 = updateAudioLocation(0); + mCodecSpecificOctetsPerFrameL2 = updateOctetsPerFrame(); + mCodecSpecificBlocksPerSduL2 = updateBlocksPerSdu(); + + ByteStr.write(mCodecSpecificLengthL2, 0, mCodecSpecificLengthL2.length); + ByteStr.write(mCodecSpecificSampleRateL2, 0, mCodecSpecificSampleRateL2.length); + ByteStr.write(mCodecSpecificFrameDurationL2, 0, mCodecSpecificFrameDurationL2.length); + ByteStr.write(mCodecSpecificAudioLocationL2, 0, mCodecSpecificAudioLocationL2.length); + ByteStr.write(mCodecSpecificOctetsPerFrameL2, 0, mCodecSpecificOctetsPerFrameL2.length); + ByteStr.write(mCodecSpecificBlocksPerSduL2, 0, mCodecSpecificBlocksPerSduL2.length); + ByteStr.write(mMetadataLength, 0, mMetadataLength.length); + ByteStr.write(mediacontext, 0, mediacontext.length); + byte[] level3 = populate_level3_new_base(0, mcid, mCodecSpecificSampleRateL2, + mCodecSpecificFrameDurationL2, + mCodecSpecificOctetsPerFrameL2, + mCodecSpecificBlocksPerSduL2, + mediacontext); + ByteStr.write(level3, 0, level3.length); + mMetaInfo.put(i,new MetadataLtv(mediacontext)); + } + } + } else { + for (int i = 0; i < mNumSubgroups[0]; i++) { + if (mPartialSimulcast) { + if (i < (mNumSubgroups[0] / 2)) { + //High quality + byte[] mcid = new byte[1]; + mcid = intTobyteArray(0xFE,1); + mL2CodecSpecificLength = intTobyteArray(0,1);//(byte) 0; + ByteStr.write(mcid, 0, mcid.length); + ByteStr.write(mL2CodecSpecificLength, 0, mL2CodecSpecificLength.length); + } else { + //Low quality + byte[] mcid = new byte[5]; + if (new_codec_id) { + mcid = longTobyteArray(LC3_CODEC_ID,5); + } else { + mcid = longTobyteArray(LC3_CODEC_ID_OLD,5); + } + mCodecSpecificLengthL2 = intTobyteArray(mCodecConfigLength, 1); + mCodecSpecificSampleRateL2 = updateHAPSampleRate(); + mCodecSpecificFrameDurationL2 = updateFrameDuration(); + mCodecSpecificAudioLocationL2 = updateAudioLocation(0); + mCodecSpecificOctetsPerFrameL2 = updateHapOctetsPerFrame(); + ByteStr.write(mcid, 0, mcid.length); + ByteStr.write(mL2CodecSpecificLength, 0, mL2CodecSpecificLength.length); + ByteStr.write(mCodecSpecificSampleRateL2, 0, mCodecSpecificSampleRateL2.length); + ByteStr.write(mCodecSpecificFrameDurationL2, 0, mCodecSpecificFrameDurationL2.length); + ByteStr.write(mCodecSpecificAudioLocationL2, 0, mCodecSpecificAudioLocationL2.length); + ByteStr.write(mCodecSpecificOctetsPerFrameL2, 0, mCodecSpecificOctetsPerFrameL2.length); + } + metalength = intTobyteArray(0, 1);//(byte)0; + for (int j = 0; j < bisPerGroup;j++) { + bisInd[j] = (byte)(1 + (bisPerGroup * i) + j); + } + ByteStr.write(metalength, 0, metalength.length); + ByteStr.write(numBises, 0, numBises.length); + ByteStr.write(bisInd, 0, bisInd.length); + } else { + byte [] mcid = new byte[1]; + mcid = intTobyteArray(0xFE,1);//(byte)0xFE; + mL2CodecSpecificLength = intTobyteArray(0,1);//(byte)0; + metalength = intTobyteArray(0,1);//(byte)0; + for (int j = 0; j < bisPerGroup;j++) { + bisInd[j] = (byte)(1 + (bisPerGroup * i) + j); + } + ByteStr.write(mcid, 0, mcid.length); + ByteStr.write(mL2CodecSpecificLength, 0, mL2CodecSpecificLength.length); + ByteStr.write(metalength, 0, metalength.length); + ByteStr.write(numBises, 0, numBises.length); + ByteStr.write(bisInd, 0, bisInd.length); + } + } + } + return ByteStr.toByteArray(); + } + private byte[] populate_level3_base() { + ByteArrayOutputStream ByteStr = new ByteArrayOutputStream(); + for (int i = 0; i < mNumBises; i++) { + byte[] index = new byte[1]; + byte [] configlength = new byte[1]; + index[0] = (byte)(1 + i); //fetch from mAdvertisingSet + configlength[0] = (byte)6; + byte [] config = updateAudioLocation(i+1); + ByteStr.write(index, 0, index.length); + ByteStr.write(configlength,0, configlength.length); + ByteStr.write(config, 0, config.length); + mBisInfo.add(new BisInfo((int)index[0], mCodecId, mCodecSpecificSampleRate, mCodecSpecificFrameDuration, + config, mCodecSpecificOctetsPerFrame, mMetadataContext)); + } + return ByteStr.toByteArray(); + } + private byte[] populate_level3_new_base(int subGroupId, byte[] codecId, byte[] SampleRate, + byte[] frameDuration, byte[] octetsPerFrame, byte[] BlocksPerSdu, + byte[] mMetadata) { + ByteArrayOutputStream ByteStr = new ByteArrayOutputStream(); + int bisPerGroup = calculateBisPerGroup(); + for (int i = 0; i < bisPerGroup; i++) { + byte[] index = new byte[1]; + byte [] configlength = new byte[1]; + index[0] = (byte)(1 + i + (bisPerGroup * subGroupId)); + configlength[0] = (byte)6; + byte [] config = updateAudioLocation(i+1); + ByteStr.write(index, 0, index.length); + ByteStr.write(configlength,0, configlength.length); + ByteStr.write(config, 0, config.length); + mBisInfo.add(new BisInfo((int)index[0], codecId, SampleRate, frameDuration, config, + octetsPerFrame, BlocksPerSdu, mMetadata, subGroupId)); + } + return ByteStr.toByteArray(); + } + } + public class BisInfo { + public int BisIndex; + public byte [] mCodecId = new byte[5]; + public CodecConfigLtv BisCodecConfig; + public MetadataLtv BisMetadata; + public int mSubGroupId; + public BisInfo(int index, byte[] codecId, byte[] CodecSpecificSampleRate, byte[] CodecSpecificFrameDuration, + byte[] CodecSpecificAudioLocation, byte[] CodecSpecificOctetsPerFrame, byte[] AudioContext) { + BisIndex = index; + mCodecId = codecId; + BisCodecConfig = new CodecConfigLtv(CodecSpecificSampleRate, CodecSpecificFrameDuration, + CodecSpecificAudioLocation, CodecSpecificOctetsPerFrame); + BisMetadata = new MetadataLtv(AudioContext); + mSubGroupId = -1; + } + public BisInfo (int index, byte[] codecId, byte[] CodecSpecificSampleRate, byte[] CodecSpecificFrameDuration, + byte[] CodecSpecificAudioLocation, byte[] CodecSpecificOctetsPerFrame, + byte[] CodecSpecificBlocksPerSdu, byte[] AudioContext, int subGroupId) { + BisIndex = index; + mCodecId = codecId; + BisCodecConfig = new CodecConfigLtv(CodecSpecificSampleRate, CodecSpecificFrameDuration, + CodecSpecificAudioLocation, CodecSpecificBlocksPerSdu, + CodecSpecificOctetsPerFrame); + BisMetadata = new MetadataLtv(AudioContext); + mSubGroupId = subGroupId; + } + } + public class CodecConfigLtv{ + byte [] mCodecSpecificSampleRate; + byte [] mCodecSpecificFrameDuration; + byte [] mCodecSpecificAudioLocation; + byte [] mCodecSpecificOctetsPerFrame; + byte [] mCodecSpecificBlocksPerSdu; + public CodecConfigLtv(byte[] CodecSpecificSampleRate, + byte[] CodecSpecificFrameDuration, + byte[] CodecSpecificAudioLocation, + byte[] CodecSpecificOctetsPerFrame) { + mCodecSpecificSampleRate = CodecSpecificSampleRate; + mCodecSpecificFrameDuration = CodecSpecificFrameDuration; + mCodecSpecificAudioLocation = CodecSpecificAudioLocation; + mCodecSpecificOctetsPerFrame = CodecSpecificOctetsPerFrame; + } + public CodecConfigLtv(byte[] CodecSpecificSampleRate, + byte[] CodecSpecificFrameDuration, + byte[] CodecSpecificAudioLocation, + byte[] CodecSpecificOctetsPerFrame, + byte [] CodecSpecificBlocksPerSdu) { + mCodecSpecificSampleRate = CodecSpecificSampleRate; + mCodecSpecificFrameDuration = CodecSpecificFrameDuration; + mCodecSpecificAudioLocation = CodecSpecificAudioLocation; + mCodecSpecificOctetsPerFrame = CodecSpecificOctetsPerFrame; + mCodecSpecificBlocksPerSdu = CodecSpecificBlocksPerSdu; + } + public byte[] getByteArray() { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try { + outputStream.write(mCodecSpecificSampleRate); + outputStream.write(mCodecSpecificFrameDuration); + outputStream.write(mCodecSpecificAudioLocation); + outputStream.write(mCodecSpecificOctetsPerFrame); + if (mNewVersion) { + outputStream.write(mCodecSpecificBlocksPerSdu); + } + } catch (IOException e) { + Log.e(TAG, "getBytes: ioexception caught!" + e); + return null; + } + return outputStream.toByteArray( ); + } + } + public class MetadataLtv { + byte[] mAudioContext; + public MetadataLtv(byte[] audiocontext) { + mAudioContext = audiocontext; + } + public byte[] getByteArray() { + return mAudioContext; + } + } + class BroadcastCodecConfig { + public BroadcastCodecConfig() { + //Default configuration + int sr, ch_mode; + long codecspecific1; + switch(mBroadcastConfigSettings) { + case 1: + sr = BluetoothCodecConfig.SAMPLE_RATE_16000; + ch_mode = BluetoothCodecConfig.CHANNEL_MODE_MONO; + codecspecific1 = 1001;//32kbps + break; + case 2: + sr = BluetoothCodecConfig.SAMPLE_RATE_16000; + ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1001;//32kbps + break; + case 3: + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + ch_mode = BluetoothCodecConfig.CHANNEL_MODE_MONO; + codecspecific1 = 1004;//80kbps + break; + case 4: + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1004;//80kbps + break; + case 5: + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + ch_mode = BluetoothCodecConfig.CHANNEL_MODE_MONO; + codecspecific1 = 1006;//96kbps + break; + case 6: + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1006;//96 + break; + case 7: + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + ch_mode = BluetoothCodecConfig.CHANNEL_MODE_MONO; + codecspecific1 = 1007;//124 + break; + case 8: + default: + sr = BluetoothCodecConfig.SAMPLE_RATE_48000; + ch_mode = BluetoothCodecConfig.CHANNEL_MODE_STEREO; + codecspecific1 = 1007; + break; + + } + mCodecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + sr, BluetoothCodecConfig.BITS_PER_SAMPLE_24, + ch_mode, codecspecific1, 1, 0, 0); + if (mPartialSimulcast) { + mHapCodecConfig = new BluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LC3, + BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, + BluetoothCodecConfig.SAMPLE_RATE_16000, + BluetoothCodecConfig.BITS_PER_SAMPLE_24, + BluetoothCodecConfig.CHANNEL_MODE_STEREO, + 1000, 1, 0, 0); + + } + } + public void updateBroadcastCodecConfig(BluetoothCodecConfig newConfig) { + if (DBG) Log.d(TAG, "updateBroadcastCodecConfig: " + newConfig); + mCodecConfig = newConfig; + int mChMode = mCodecConfig.getChannelMode(); + switch (mChMode) { + case BluetoothCodecConfig.CHANNEL_MODE_MONO: + case BluetoothCodecConfig.CHANNEL_MODE_JOINT_STEREO: + mNumBises = 1 * mNumSubGrps; + break; + case BluetoothCodecConfig.CHANNEL_MODE_STEREO: + mNumBises = 2 * mNumSubGrps; + break; + default: + Log.e(TAG,"channel mode unknown"); + } + } + } + + /** + * Binder object: must be a static class or memory leak may occur. + */ + @VisibleForTesting + static class BluetoothBroadcastBinder extends IBluetoothBroadcast.Stub + implements IProfileServiceBinder { + private BroadcastService mService; + + private BroadcastService getService() { + if (!Utils.checkCallerIsSystemOrActiveUser(TAG)) { + return null; + } + + if (mService != null && mService.isAvailable()) { + return mService; + } + return null; + } + + BluetoothBroadcastBinder(BroadcastService svc) { + mService = svc; + } + + @Override + public void cleanup() { + mService = null; + } + @Override + public boolean SetBroadcast(boolean enable, String packageName) { + BroadcastService service = getService(); + if (service == null) { + return false; + } + if (enable) { + return service.EnableBroadcast(packageName); + } + else { + return service.DisableBroadcast(packageName); + } + //return false; + } + + @Override + public boolean SetEncryption(boolean enable, int enc_len, boolean use_existing, + String packageName) { + BroadcastService service = getService(); + if (service == null) { + return false; + } + return service.SetEncryption(enable, enc_len, use_existing, packageName); + } + + @Override + public byte[] GetEncryptionKey(String packageName) { + BroadcastService service = getService(); + if (service == null) { + return null; + } + return service.GetEncryptionKey(packageName); + } + @Override + public int GetBroadcastStatus(String packageName) { + BroadcastService service = getService(); + if (service == null) { + return BluetoothBroadcast.STATE_DISABLED; + } + return service.GetBroadcastStatus(packageName); + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/broadcast/BroadcastStackEvent.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/broadcast/BroadcastStackEvent.java new file mode 100644 index 00000000000..ae0f9252eb9 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/broadcast/BroadcastStackEvent.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +package com.android.bluetooth.broadcast; + +import android.bluetooth.BluetoothCodecStatus; +import android.bluetooth.BluetoothBroadcast; +/** + * Stack event sent via a callback from JNI to Java, or generated. + */ +public class BroadcastStackEvent { + // Event types for STACK_EVENT message (coming from native) + private static final int EVENT_TYPE_NONE = 0; + public static final int EVENT_TYPE_BROADCAST_STATE_CHANGED = 1; + public static final int EVENT_TYPE_BROADCAST_AUDIO_STATE_CHANGED = 2; + public static final int EVENT_TYPE_ENC_KEY_GENERATED = 3; + public static final int EVENT_TYPE_CODEC_CONFIG_CHANGED = 4; + public static final int EVENT_TYPE_SETUP_BIG = 5; + public static final int EVENT_TYPE_BROADCAST_ID_GENERATED = 6; + + public static final int STATE_IDLE = 0; + public static final int STATE_CONFIGURED = 1; + public static final int STATE_STREAMING = 2; + + public static final int STATE_STOPPED = 0; + public static final int STATE_STARTED = 1; + + public int type = EVENT_TYPE_NONE; + public int advHandle = 0; + public int valueInt = 0; + public int bigHandle = 0; + public int NumBises = 0; + public int[] BisHandles; + public byte[] BroadcastId = new byte[3]; + public String key; + public BluetoothCodecStatus codecStatus; + + BroadcastStackEvent(int type) { + this.type = type; + } + + @Override + public String toString() { + // event dump + StringBuilder result = new StringBuilder(); + result.append("BroadcastStackEvent {type:" + eventTypeToString(type)); + result.append(", value1:" + eventTypeValueIntToString(type, valueInt)); + if (codecStatus != null) { + result.append(", codecStatus:" + codecStatus); + } + result.append("}"); + return result.toString(); + } + + private static String eventTypeToString(int type) { + switch (type) { + case EVENT_TYPE_NONE: + return "EVENT_TYPE_NONE"; + case EVENT_TYPE_BROADCAST_STATE_CHANGED: + return "EVENT_TYPE_BROADCAST_STATE_CHANGED"; + case EVENT_TYPE_BROADCAST_AUDIO_STATE_CHANGED: + return "EVENT_TYPE_BROADCAST_AUDIO_STATE_CHANGED"; + case EVENT_TYPE_ENC_KEY_GENERATED: + return "EVENT_TYPE_ENC_KEY_GENERATED"; + case EVENT_TYPE_CODEC_CONFIG_CHANGED: + return "EVENT_TYPE_CODEC_CONFIG_CHANGED"; + case EVENT_TYPE_SETUP_BIG: + return "EVENT_TYPE_SETUP_BIG"; + default: + return "EVENT_TYPE_UNKNOWN:" + type; + } + } + + private static String eventTypeValueIntToString(int type, int value) { + switch (type) { + case EVENT_TYPE_BROADCAST_STATE_CHANGED: + switch (value) { + case BluetoothBroadcast.STATE_DISABLED: + return "DISABLED"; + case BluetoothBroadcast.STATE_ENABLING: + return "ENABLING"; + case BluetoothBroadcast.STATE_ENABLED: + return "CONFIGURED"; + case BluetoothBroadcast.STATE_STREAMING: + return "STREAMING"; + default: + break; + } + break; + case EVENT_TYPE_BROADCAST_AUDIO_STATE_CHANGED: + switch(value) { + case BluetoothBroadcast.STATE_PLAYING: + return "PLAYING"; + case BluetoothBroadcast.STATE_NOT_PLAYING: + return "NOT PLAYING"; + default: + break; + } + default: + break; + } + return Integer.toString(value); + } +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCHalConstants.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCHalConstants.java new file mode 100644 index 00000000000..eb4df0bcb2d --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCHalConstants.java @@ -0,0 +1,100 @@ +/* + *Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2012 The Android Open Source Project + * + * 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. + */ + +package com.android.bluetooth.cc; + +/* + * @hide + */ +public final class CCHalConstants { + static final int NETWORK_STATE_NOT_AVAILABLE = 0; + static final int NETWORK_STATE_AVAILABLE = 1; + + static final int SERVICE_TYPE_HOME = 0; + static final int SERVICE_TYPE_ROAMING = 1; + + static final int CALL_STATE_ACTIVE = 0; + static final int CALL_STATE_HELD = 1; + static final int CALL_STATE_DIALING = 2; + static final int CALL_STATE_ALERTING = 3; + static final int CALL_STATE_INCOMING = 4; + static final int CALL_STATE_WAITING = 5; + static final int CALL_STATE_IDLE = 6; + static final int CALL_STATE_DISCONNECTED = 7; + + //Call State as expected by Stack/CC + static final int CCS_STATE_INCOMING = 0x00; + static final int CCS_STATE_DIALING = 0x01; + static final int CCS_STATE_ALERTING = 0x02; + static final int CCS_STATE_ACTIVE = 0x03; + static final int CCS_STATE_LOCAL_HELD= 0x04; + static final int CCS_STATE_REMOTELY_HELD= 0x05; + static final int CCS_STATE_LOCAL_REMOTE_HELD= 0x06; + static final int CCS_STATE_DISCONNECTED = 0x07; + + static final int BTCC_OP_ACCEPT = 0; + static final int BTCC_OP_TERMINATE = 1; + static final int BTCC_OP_LOCAL_HLD = 2; + static final int BTCC_OP_LOCAL_RETRIEVE = 3; + static final int BTCC_OP_ORIGINATE = 4; + static final int BTCC_OP_JOIN = 5; + + static final int BTCC_OP_SUCCESS = 0x00; + static final int BTCC_OP_NOT_POSSIBLE = 0x02; + + //default call index for failures + static final int BTCC_DEF_INDEX_FOR_FAILURES = 0; + + static int getCCsCallState(int telephonyCallState) { + int ret = 0xFF; + switch(telephonyCallState) { + case CALL_STATE_ACTIVE: ret = CCS_STATE_ACTIVE; break; + case CALL_STATE_HELD: ret = CCS_STATE_LOCAL_HELD; break; + case CALL_STATE_DIALING: ret = CCS_STATE_DIALING; break; + case CALL_STATE_ALERTING: ret = CCS_STATE_ALERTING; break; + case CALL_STATE_INCOMING: ret = CCS_STATE_INCOMING; break; + case CALL_STATE_DISCONNECTED: ret = CCS_STATE_DISCONNECTED; break; + //this means second Incoming call is waiting + case CALL_STATE_WAITING: ret = CCS_STATE_INCOMING; break; + default: break; + } + return ret; + } + + public static String operationToString(int what) { + switch (what) { + case BTCC_OP_ACCEPT : + return "BTCC_OP_ACCEPT"; + case BTCC_OP_TERMINATE : + return "BTCC_OP_TERMINATE"; + case BTCC_OP_LOCAL_HLD : + return "BTCC_OP_LOCAL_HLD"; + case BTCC_OP_LOCAL_RETRIEVE : + return "BTCC_OP_LOCAL_RETRIEVE"; + case BTCC_OP_ORIGINATE : + return "BTCC_OP_ORIGINATE"; + case BTCC_OP_JOIN : + return "BTCC_OP_JOIN"; + default: + break; + } + return Integer.toString(what); + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCNativeInterface.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCNativeInterface.java new file mode 100644 index 00000000000..1e93c62fc4a --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCNativeInterface.java @@ -0,0 +1,255 @@ +/* + *Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2017 The Android Open Source Project + * + * 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.cc; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.util.Log; +import java.util.ArrayList; +import java.util.List; + +import com.android.bluetooth.Utils; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import java.nio.charset.StandardCharsets; + +/** + * Ccp Native Interface to/from JNI. + */ +public class CCNativeInterface { + private static final String TAG = "CCNativeInterface"; + private static final boolean DBG = true; + private BluetoothAdapter mAdapter; + + @GuardedBy("INSTANCE_LOCK") + private static CCNativeInterface sInstance; + private static final Object INSTANCE_LOCK = new Object(); + + static { + classInitNative(); + } + + private CCNativeInterface() { + mAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mAdapter == null) { + Log.w(TAG, "No Bluetooth Adapter Available"); + } + } + + /** + * This class is a singleton because native library should only be loaded once + * + * @return default instance + */ + public static CCNativeInterface getInstance() { + synchronized (INSTANCE_LOCK) { + if (sInstance == null) { + sInstance = new CCNativeInterface(); + } + return sInstance; + } + } + + /** + * Initialize native stack + * + * @param ccsClients maximum number of CCS clients that can be connected simultaneously + * @param inbandRingingEnabled whether in-band ringing is enabled on this AG + */ + @VisibleForTesting + public void init(int maxCcsClients, boolean inbandRingingEnabled) { + initializeNative("00008fd1-0000-1000-8000-00805F9B34FB", maxCcsClients, inbandRingingEnabled); + } + + /** + * Cleanup the native interface. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void cleanup() { + cleanupNative(); + } + + /** + * Disconnects Call control from a remote device. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean disconnect(BluetoothDevice device) { + return disconnectNative(getByteAddress(device)); + } + /** + * update CC optional supported feature + * @param feature + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean callControlOptionalFeatures(int feature) { + return callControlPointOpcodeSupportedNative(feature); + } + + /** + * Sets the CC call state + * @param state + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean callState(ArrayList callList) { + int len = callList.size(); + byte[] cStateListBytes = new byte[len*3]; + for (int i=0; i mCallStateList = null; + private HashMap mPrevCallStateList = null; + private Queue mLccTobeQueued = null; + private Queue mLccWaitForResponseQ = null; + + private static final int FLAGS_DIRECTION_BIT = 0x0001; + private static final int CC_SIGNAL_STRENGTH_FACTOR = 20; + + private static final int CC_CONTENT_CONTROL_ID = 77; + private static final int CC_OPTIONAL_LOCAL_HOLD_FEAT = 0x01; + private static final int CC_OPTIONAL_JOIN_FEAT = 0x02; + private static final int CALL_CONTROL_OPTIONAL_FEATURES = CC_OPTIONAL_LOCAL_HOLD_FEAT|CC_OPTIONAL_JOIN_FEAT; + //native event + static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1; + static final int EVENT_TYPE_CALL_CONTROL_POINT_CHANGED = 2; + //CC to JNI update + static final int UPDATE_BEARER_NAME = 3; + static final int UPDATE_BEARER_TECH = 4; + static final int UPDATE_STATUS_FLAGS = 5; + static final int UPDATE_SIGNAL_STRENGTH = 6; + static final int UPDATE_BEARERLIST_SUPPORTED = 7; + static final int UPDATE_CONTENT_CONTROL_ID = 8; + static final int UPDATE_CALL_STATE = 9; + static final int UPDATE_CALL_CONTROL_OPCODES_SUPPORTED = 10; + static final int UPDATE_CALL_CONTROL_RESPONSE = 11; + static final int UPDATE_INCOMING_CALL = 12; + static final int PROCESS_CALL_STATE = 13; + static final int PROCESS_PHONE_STATE_CHANGED = 14; + static final int ACTIVE_DEVICE_CHANGED = 15; + + @Override + protected IProfileServiceBinder initBinder() { + return new CcBinder(this); + } + + @Override + protected void create() { + Log.i(TAG, "create()"); + if (mCreated) { + throw new IllegalStateException("create() called twice"); + } + mCreated = true; + } + + @Override + protected void cleanup() { + Log.i(TAG, "cleanup()"); + if (mNativeInterface != null) { + mNativeInterface.cleanup(); + } + } + + @Override + protected boolean start() { + Log.i(TAG, "start()"); + if (sCCService != null) { + Log.w(TAG, "CCService is already running"); + return true; + } + if (DBG) { + Log.d(TAG, "Create CCService Instance"); + } + + mContext = this; + mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), + "AdapterService cannot be null when CCService starts"); + mNativeInterface = Objects.requireNonNull(CCNativeInterface.getInstance(), + "CcNativeInterface cannot be null when CcService starts"); + // Step 2: Get maximum number of connected audio devices + mMaxConnectedAudioDevices = mAdapterService.getMaxConnectedAudioDevices(); + Log.i(TAG, "Max connected audio devices set to " + mMaxConnectedAudioDevices); + + if (mHandler != null) { + mHandler = null; + } + HandlerThread thread = new HandlerThread("BluetoothCCSHandler"); + thread.start(); + Looper looper = thread.getLooper(); + mHandler = new CcsMessageHandler(looper); + //APM's CallControl and CallAudio initialization + CallControl.init(mContext); + mCallAudio = CallAudio.init(mContext); + mNativeInterface.init(mMaxConnectedAudioDevices,InBandRingtoneSupport); + Log.d(TAG, "cc native init done"); + IntentFilter filter = new IntentFilter(); + //mSystemInterface = HeadsetService.getSystemInterfaceObj(); + filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + mBondStateChangedReceiver = new BondStateChangedReceiver(); + mContext.registerReceiver(mBondStateChangedReceiver, filter); + mCallStateList = new HashMap<> (); + mPrevCallStateList = new HashMap<> (); + mLccWaitForResponseQ = new LinkedList<> (); + mLccTobeQueued = new LinkedList<> (); + mActiveDevMgrService = ActiveDeviceManagerService.get(); + setCCService(this); + return true; + } + + @Override + protected boolean stop() { + Log.i(TAG, "stop()"); + if (sCCService == null) { + Log.w(TAG, "stop() called before start()"); + return true; + } + // Step 8: Mark service as stopped + setCCService(null); + // Cleanup native interface + mNativeInterface.cleanup(); + mNativeInterface = null; + mContext.unregisterReceiver(mBondStateChangedReceiver); + // Clear AdapterService + mAdapterService = null; + mMaxConnectedAudioDevices = 1; + mCallOriginatedDevice = null; + CallControl.listenForPhoneState(PhoneStateListener.LISTEN_NONE); + return true; + } + + private static synchronized void setCCService(CCService instance) { + if (DBG) { + Log.d(TAG, "setCCService(): set to: " + instance); + } + sCCService = instance; + } + + public static synchronized CCService getCCService() { + if (sCCService == null) { + Log.w(TAG, "getCCService(): service is null"); + return null; + } + return sCCService; + } + + public boolean updateBearerProviderName(String name) { + Message msg = mHandler.obtainMessage(); + msg.what = UPDATE_BEARER_NAME; + msg.obj = name; + mHandler.sendMessage(msg); + return true; + } + public boolean updateBearerProviderTechnology (int tech_type) { + Message msg = mHandler.obtainMessage(); + msg.what = UPDATE_BEARER_TECH; + msg.arg1 = tech_type; + mHandler.sendMessage(msg); + return true; + } + + public boolean updateSignalStrength(int signal) { + Message msg = mHandler.obtainMessage(); + msg.what = UPDATE_SIGNAL_STRENGTH; + msg.arg1 = signal*CC_SIGNAL_STRENGTH_FACTOR; + mHandler.sendMessage(msg); + return true; + } + + public boolean updateSupportedBearerList(String supportedBearers) { + Message msg = mHandler.obtainMessage(); + msg.what = UPDATE_BEARERLIST_SUPPORTED ; + msg.obj = supportedBearers; + mHandler.sendMessage(msg); + return true; + } + + public void updateOriginateResult(BluetoothDevice device, int event, int res) { + if (mCallOriginatedDevice == null || device != mCallOriginatedDevice) { + Log.e(TAG, "Originate resp ignored, as there is no Orginate req"); + return; + } + if (res != 1) { + mCallOriginatedDevice = null; + updateCallControlResponse(CCHalConstants.BTCC_OP_ORIGINATE, + CCHalConstants.BTCC_DEF_INDEX_FOR_FAILURES, + CCHalConstants.BTCC_OP_NOT_POSSIBLE, device); + } + } + + public boolean updateContentControlID(int ccid) { + Message msg = mHandler.obtainMessage(); + msg.what = UPDATE_CONTENT_CONTROL_ID; + msg.arg1 = ccid; + mHandler.sendMessage(msg); + mCCId = ccid; + return true; + } + + public boolean updateStatusFlags(int statusFlags) { + Message msg = mHandler.obtainMessage(); + msg.what = UPDATE_STATUS_FLAGS; + msg.arg1 = statusFlags; + mHandler.sendMessage(msg); + return true; + } + + public boolean updateCallControlOptionalFeatures(int feature) { + Message msg = mHandler.obtainMessage(); + msg.what = UPDATE_CALL_CONTROL_OPCODES_SUPPORTED; + msg.arg1 = feature; + mHandler.sendMessage(msg); + return true; + } + + public boolean updateCallControlResponse(int op, int index, int status, BluetoothDevice device) { + Message msg = mHandler.obtainMessage(); + msg.what = UPDATE_CALL_CONTROL_RESPONSE ; + msg.arg1 = op; + msg.arg2 = index; + msg.obj = status; + mHandler.sendMessage(msg); + return true; + } + + private boolean updateIncomingCall(int index, String uri) { + Message msg = mHandler.obtainMessage(); + msg.what = UPDATE_INCOMING_CALL ; + msg.arg1 = index; + msg.obj = uri; + mHandler.sendMessage(msg); + return true; + } + + boolean isVirtualCallStarted() { + + return mVirtualCallStarted; + } + + public void setVirtualCallActive(boolean state) { + Log.i(TAG, "setVirtualCallActive: " + state); + if (state == true) { + startScoUsingVirtualVoiceCall(); + } else { + stopScoUsingVirtualVoiceCall(); + } + } + + private void disaptchFakeCallState (CallControlState state) { + if (state != null) { + mCallStateList.put(state.mIndex, state); + } + Message msg = mHandler.obtainMessage(); + msg.what = PROCESS_CALL_STATE; + Collection values = mCallStateList.values(); + ArrayList listOfValues = new ArrayList<>(values); + msg.obj = listOfValues; + mHandler.sendMessage(msg); + } + + boolean startScoUsingVirtualVoiceCall() { + + Log.i(TAG, "startScoUsingVirtualVoiceCall: " + Utils.getUidPidString()); + mVirtualCallStarted = true; + // Send fake call states to mimic outgoing calls + + mCallStateList.clear(); + CallControlState alertingState = new CallControlState(1,CCHalConstants.CALL_STATE_ALERTING,FLAGS_DIRECTION_BIT); + disaptchFakeCallState(alertingState); + CallControlState activeState = new CallControlState(1,CCHalConstants.CALL_STATE_ACTIVE,FLAGS_DIRECTION_BIT); + disaptchFakeCallState(activeState); + return true; + } + + boolean stopScoUsingVirtualVoiceCall() { + + Log.i(TAG, "stopScoUsingVirtualVoiceCall: " + Utils.getUidPidString()); + // 1. Check if virtual call has already started + if (!mVirtualCallStarted) { + Log.w(TAG, "stopScoUsingVirtualVoiceCall: virtual call not started"); + return false; + } + mVirtualCallStarted = false; + // 2. Send fake call states to mimic it ias outgoing calls + + mCallStateList.clear(); + CallControlState disConnectedState = new CallControlState(1,CCHalConstants.CCS_STATE_DISCONNECTED,FLAGS_DIRECTION_BIT); + disaptchFakeCallState(disConnectedState); + return true; + } + + private void updateCallState(ArrayList listOfValues) { + Log.d(TAG, "updateCallState"); + Message msg = mHandler.obtainMessage(); + msg.what = UPDATE_CALL_STATE; + msg.obj = listOfValues; + mHandler.sendMessage(msg); + } + + public void processAndUpdateCallState(ArrayList listOfValues) { + int flags = 0; + + for (CallControlState state : listOfValues) { + Log.i(TAG, "processAndUpdateCallState: direction" + state.mDirection); + if (state.mDirection == 1) { + //Incoming call: off the direction bit + flags = (flags & (~FLAGS_DIRECTION_BIT)); + } else { + //Outgoing call: on the direction bit + flags = (flags | FLAGS_DIRECTION_BIT); + } + state.mFlags = flags; + String uri = ""; + String uri_str = "tel:"; + Log.i(TAG, "processAndUpdateCallState: index = " + state.mIndex); + if (state.mState == CCHalConstants.CALL_STATE_ACTIVE) { + mLatestActiveCallIndex = state.mIndex; + } else if (state.mState == CCHalConstants.CALL_STATE_HELD) { + mLatestHeldCallIndex = state.mIndex; + } + if (state.mState == CCHalConstants.CALL_STATE_INCOMING) { + if (state.mNumber != null) { + uri = uri_str.concat(state.mNumber); + } + Log.i(TAG, "processAndUpdateCallState: inc uri = " + uri); + updateIncomingCall(state.mIndex, uri); + } + } + updateCallState(listOfValues); + } + + private void compareAndUpdateWithPrevCallList (HashMap currentCallStateList) { + Log.d(TAG, "compareAndUpdateWithPrevCallList"); + for (Integer key: mPrevCallStateList.keySet()) { + if (currentCallStateList.containsKey(key) == false) { + //create a fake disconnected for that index + if (mPrevCallStateList.get(key).mState != CCHalConstants.CALL_STATE_DISCONNECTED) { + Log.d(TAG, "inserting DISC state fake!"); + CallControlState fakeDiscForDisappeared = + new CallControlState(key,CCHalConstants.CALL_STATE_DISCONNECTED, mPrevCallStateList.get(key).mFlags); + mCallStateList.put(key, fakeDiscForDisappeared); + } + } + } + mPrevCallStateList.putAll(mCallStateList); + } + + public void clccResponse(int index, int direction, int call_status, int mode, boolean mpty, + String number, int type) { + Log.d(TAG, "clccResponse"); + if (index != 0) { + CallControlState state = new CallControlState(index, direction, call_status, number); + mCallStateList.put(index, state); + } else { + //update the call state to stack as 0 indicates end of call list + compareAndUpdateWithPrevCallList(mCallStateList); + Message msg = mHandler.obtainMessage(); + msg.what = PROCESS_CALL_STATE; + Collection values = mCallStateList.values(); + ArrayList listOfValues = new ArrayList<>(values); + msg.obj = listOfValues; + mHandler.sendMessage(msg); + if (!mLccWaitForResponseQ.isEmpty()) { + mLccWaitForResponseQ.remove(); + } + if (!mLccTobeQueued.isEmpty()) { + mLccTobeQueued.remove(); + getBlcc(); + } + } + } + + private void getBlcc() { + Log.d(TAG, "getBlcc"); + if (mLccTobeQueued.isEmpty()) { + if (CallControl.listCurrentCalls() == true) { + mLccWaitForResponseQ.add(1); + Log.d(TAG, "getBlcc: successfully sent"); + //telephony should always respond with clccresponse + mCallStateList.clear(); + } + } else { + mLccTobeQueued.add(1); + } + } + + private boolean processCallStateChange(CallControlState state) { + Message msg = mHandler.obtainMessage(); + msg.what = PROCESS_PHONE_STATE_CHANGED; + msg.obj = state; + mHandler.sendMessage(msg); + return true; + } + + boolean isInbandRingingEnabled() { + boolean returnVal; + + returnVal = BluetoothHeadset.isInbandRingingSupported(this) && !SystemProperties.getBoolean( + DISABLE_INBAND_RINGING_PROPERTY, true); + Log.d(TAG, "isInbandRingingEnabled returning: " + returnVal); + return returnVal; + } + + boolean isCallAudioNeeded(CallControlState state) { + boolean ret = false; + if (isInbandRingingEnabled() && state.mState == CCHalConstants.CALL_STATE_INCOMING) { + ret = true; + } else if (mCallAudio != null && mCallAudio.isAudioOn() == false && + (state.mState == CCHalConstants.CALL_STATE_ALERTING || + mPrevTelephonyState != null && mPrevTelephonyState.mNumActive == 0 && + state.mNumActive == 1)) { + + ret = true; + } + return ret; + } + + public boolean phoneStateChanged(int numActive, int numHeld, int callState, String number, int type, + String name, boolean isVirtualCall) { + Log.d(TAG, "phoneStateChanged: " + + "callState: " + callState + + "number:" + number + + "numActive:" + numActive + + "isVirtualCall:" + isVirtualCall); + CallControlState currentTelephonyState = new CallControlState(numActive, numHeld,callState, number, type, name); + + if (isCallAudioNeeded(currentTelephonyState)) { + if (mCallAudio != null) { + mCallAudio.connectAudio(); + } else { + Log.e(TAG, "no CallAudio handle"); + } + } + + if (mPrevTelephonyState != null && mPrevTelephonyState.mNumActive == 1 + && currentTelephonyState.mNumActive == 0 && currentTelephonyState.mNumHeld == 0) { + if (mPrevTelephonyState.mNumHeld == 0 && currentTelephonyState.mNumHeld == 1) { + Log.d(TAG, "special case where Active call moved to HOLD"); + } else { + if (mCallAudio != null) { + mCallAudio.disconnectAudio(); + } else { + Log.e(TAG, "no CallAudio handle for disc Call handling"); + } + } + } + + if (callState == CCHalConstants.CALL_STATE_DIALING) { + //ignore this as it is fake Telephony event + return true; + } + + // Should stop all other audio mode in this case + if ((numActive + numHeld) > 0 || callState != CCHalConstants.CALL_STATE_IDLE) { + if (!isVirtualCall && mVirtualCallStarted) { + // stop virtual voice call if there is an incoming Telecom call update + stopScoUsingVirtualVoiceCall(); + } + processCallStateChange(currentTelephonyState); + mPrevTelephonyState = currentTelephonyState; + } else { + // ignore CS non-call state update when virtual call started + if (!isVirtualCall && mVirtualCallStarted) { + Log.i(TAG, "Ignore CS non-call state update"); + return true; + } + } + return true; + } + + public BluetoothDevice getActiveDevice() { + return mActiveDevice; + } + + public int getContentControlID() { + return mCCId; + } + + public boolean setActiveDevice(BluetoothDevice device) { + Message msg = mHandler.obtainMessage(); + msg.what = ACTIVE_DEVICE_CHANGED; + msg.obj = device; + mHandler.sendMessage(msg); + return true; + } + + private boolean setActiveDeviceRemoteTrigger(BluetoothDevice device) { + boolean ret = false; + if (mActiveDevMgrService != null) { + ret = mActiveDevMgrService.setActiveDeviceBlocking(device, ApmConst.AudioFeatures.CALL_AUDIO); + } + Log.d(TAG, "setActiveDevice returns" + ret); + return ret; + } + + private boolean isActiveDevice(BluetoothDevice device) { + boolean ret = false; + if (mActiveDevMgrService != null) { + ret = (device == mActiveDevMgrService.getActiveDevice(ApmConst.AudioFeatures.CALL_AUDIO)); + } + Log.d(TAG, "isActiveDevice returns" + ret); + return ret; + } + + public boolean onCallControlPointChangedRequest(int op, int[] call_indices, int count, String dialNumber, BluetoothDevice device ) { + Log.d(TAG, " onCallControlPointChangedRequest opcode : " + CCHalConstants.operationToString(op)) ; + switch(op) { + case CCHalConstants.BTCC_OP_ACCEPT: { + setActiveDeviceRemoteTrigger (device); + CallControl.answerCall(device); + break; + } + case CCHalConstants.BTCC_OP_TERMINATE: { + int callIndex = call_indices[0]; + Log.d(TAG, "callIndex: " + callIndex); + CallControl.terminateCall(device, callIndex); + break; + } + case CCHalConstants.BTCC_OP_LOCAL_HLD:{ + int callIndex = call_indices[0]; + int res; + int idx = CCHalConstants.BTCC_DEF_INDEX_FOR_FAILURES; + Log.d(TAG, "callIndex: " + callIndex); + if (CallControl.holdCall(device, callIndex) == true) { + res = CCHalConstants.BTCC_OP_SUCCESS; + idx = callIndex; + } else { + res = CCHalConstants.BTCC_OP_NOT_POSSIBLE; + idx = CCHalConstants.BTCC_DEF_INDEX_FOR_FAILURES; + } + updateCallControlResponse(op, idx, res, device); + break; + } + case CCHalConstants.BTCC_OP_LOCAL_RETRIEVE: { + //Analogus to SWAP as stack would have + //already validated the input index is in HELD state + int chld = 2; + int res; + int idx = CCHalConstants.BTCC_DEF_INDEX_FOR_FAILURES; + if (CallControl.processChld(device, chld) == true) { + res = CCHalConstants.BTCC_OP_SUCCESS; + idx = call_indices[0]; + } else { + res = CCHalConstants.BTCC_OP_NOT_POSSIBLE; + idx = CCHalConstants.BTCC_DEF_INDEX_FOR_FAILURES; + } + updateCallControlResponse(op, idx, res, device); + break; + } + case CCHalConstants.BTCC_OP_ORIGINATE: { + Log.d(TAG, "Orignate: from Device: " + device + "dialString: " + dialNumber); + if (dialNumber == null) { + Log.e(TAG, "null dial string"); + break; + } + if (mCallOriginatedDevice != null) { + Log.d(TAG, "Originate is pending from device: " + mCallOriginatedDevice); + updateCallControlResponse(op, CCHalConstants.BTCC_DEF_INDEX_FOR_FAILURES, + CCHalConstants.BTCC_OP_NOT_POSSIBLE, device); + break; + } else { + setActiveDeviceRemoteTrigger (device); + String[] result = dialNumber.split(":"); + if (CallControl.dialOutgoingCall(device, result[1]) == true) { + mCallOriginatedDevice = device; + } else { + updateCallControlResponse(op, CCHalConstants.BTCC_DEF_INDEX_FOR_FAILURES, + CCHalConstants.BTCC_OP_NOT_POSSIBLE, device); + } + } + break; + } + case CCHalConstants.BTCC_OP_JOIN: { + //Stack would have validate to ensure the input indicies + //are valid candidates for JOIN op + int chld = 3; + int res; + int idx = CCHalConstants.BTCC_DEF_INDEX_FOR_FAILURES; + if (CallControl.processChld(device, chld) == true) { + res = CCHalConstants.BTCC_OP_SUCCESS; + idx = call_indices[0]; + } else { + res = CCHalConstants.BTCC_OP_NOT_POSSIBLE; + idx = CCHalConstants.BTCC_DEF_INDEX_FOR_FAILURES; + } + updateCallControlResponse(op, idx, res, device); + break; + } + } + return true; + } + + public void onCallControlInitialized(int status) { + Log.v(TAG, "CallControlInitializedCallback: status=" + status); + if (status == 0) { + //Initialize Telephony and APM related Initialization + CallControl.listenForPhoneState(PhoneStateListener.LISTEN_SERVICE_STATE|PhoneStateListener.LISTEN_SERVICE_STATE); + updateContentControlID(CC_CONTENT_CONTROL_ID); + updateSupportedBearerList("tel"); + updateCallControlOptionalFeatures(CALL_CONTROL_OPTIONAL_FEATURES); + } + } + + + public void onConnectionStateChanged(BluetoothDevice device, int status) { + Log.v(TAG, "onConnectionStateChanged: address=" + device.toString()); + } + + private class BondStateChangedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { + return; + } + int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.ERROR); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); + bondStateChanged(device, state); + } + } + + void bondStateChanged(BluetoothDevice device, int bondState) { + if (DBG) { + Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState); + } + // Remove state machine if the bonding for a device is removed + if (bondState != BluetoothDevice.BOND_NONE) { + return; + } + + } + + private boolean callListContainsDialingCall(ArrayList listOfValues) { + boolean ret = false; + for (CallControlState state : listOfValues) { + if (state.mState == CCHalConstants.CALL_STATE_DIALING + || state.mState == CCHalConstants.CALL_STATE_ALERTING) { + ret = true; + break; + } + } + return ret; + } + + /** Handles CCS messages. */ + private final class CcsMessageHandler extends Handler { + private CcsMessageHandler(Looper looper) { + super(looper); + } + @Override + public void handleMessage(Message msg) { + if (DBG) Log.v(TAG, "CcsMessageHandler: received message=" + messageWhatToString(msg.what)); + ArrayList listOfValues = null; + switch (msg.what) { + case UPDATE_BEARER_NAME: + String bName = (String)msg.obj; + mNativeInterface.updateBearerProviderName(bName); + break; + case UPDATE_BEARER_TECH: + int tech_type = (int)msg.arg1; + mNativeInterface.updateBearerTechnology(tech_type); + break; + case UPDATE_SIGNAL_STRENGTH: + int signal = (int)msg.arg1; + mNativeInterface.updateSignalStrength(signal); + break; + case UPDATE_STATUS_FLAGS: + int statusFlags = (int)msg.arg1; + mNativeInterface.updateStatusFlags(statusFlags); + break; + case UPDATE_BEARERLIST_SUPPORTED : + String bSList = (String)msg.obj; + mNativeInterface.updateSupportedBearerList(bSList); + break; + case UPDATE_CONTENT_CONTROL_ID: + int ccid = (int)msg.arg1; + mNativeInterface.contentControlId(ccid); + break; + case UPDATE_CALL_STATE: + listOfValues = (ArrayList)msg.obj; + Log.d(TAG, "Call list size : " + listOfValues.size()); + boolean status = mNativeInterface.callState(listOfValues); + if (mCallOriginatedDevice != null && callListContainsDialingCall(listOfValues)) { + Log.e(TAG, "push the pending Originate response"); + //Stack will pick the right index + updateCallControlResponse(CCHalConstants.BTCC_OP_ORIGINATE, + CCHalConstants.BTCC_DEF_INDEX_FOR_FAILURES, + CCHalConstants.BTCC_OP_SUCCESS, mCallOriginatedDevice); + mCallOriginatedDevice = null; + } + break; + case UPDATE_CALL_CONTROL_OPCODES_SUPPORTED : + int feature = (int)msg.arg1; + mNativeInterface.callControlOptionalFeatures(feature); + break; + case UPDATE_CALL_CONTROL_RESPONSE : + int op = (int)msg.arg1; + int ind = (int)msg.arg2; + int st = (int)msg.obj; + mNativeInterface.callControlResponse(op, ind, st, null); + break; + case UPDATE_INCOMING_CALL : + int index = (int)msg.arg1; + String uri = (String)msg.obj; + mNativeInterface.updateIncomingCall(index, uri); + break; + case PROCESS_PHONE_STATE_CHANGED: + getBlcc(); + break; + case PROCESS_CALL_STATE: + listOfValues = (ArrayList)msg.obj; + processAndUpdateCallState(listOfValues); + break; + case ACTIVE_DEVICE_CHANGED: + BluetoothDevice device = (BluetoothDevice)msg.obj; + mNativeInterface.setActiveDevice(device,-1); + break; + case EVENT_TYPE_CONNECTION_STATE_CHANGED: + break; + default: + Log.e(TAG, "unknown message! msg.what=" + messageWhatToString(msg.what)); + break; + } + Log.v(TAG, "Exit handleMessage"); + } +} + + public static String messageWhatToString(int what) { + switch (what) { + case UPDATE_BEARER_NAME : + return "UPDATE_BEARER_NAME"; + case UPDATE_BEARER_TECH : + return "UPDATE_BEARER_TECH"; + case UPDATE_SIGNAL_STRENGTH : + return "UPDATE_SIGNAL_STRENGTH"; + case UPDATE_BEARERLIST_SUPPORTED : + return "UPDATE_BEARERLIST_SUPPORTED"; + case UPDATE_CONTENT_CONTROL_ID : + return "UPDATE_CONTENT_CONTROL_ID"; + case UPDATE_CALL_STATE : + return "UPDATE_CALL_STATE"; + case UPDATE_CALL_CONTROL_OPCODES_SUPPORTED : + return "UPDATE_CALL_CONTROL_OPCODES_SUPPORTED "; + case UPDATE_CALL_CONTROL_RESPONSE : + return "UPDATE_CALL_CONTROL_RESPONSE"; + case UPDATE_INCOMING_CALL : + return "UPDATE_INCOMING_CALL"; + case PROCESS_CALL_STATE : + return "PROCESS_CALL_STATE"; + case UPDATE_STATUS_FLAGS: + return "UPDATE_STATUS_FLAGS"; + default: + break; + } + return Integer.toString(what); + } + + /** + * Binder object: must be a static class or memory leak may occur. + */ + + static class CcBinder extends Binder implements IProfileServiceBinder { + private CCService mService; + + private CCService getService() { + if (!Utils.checkCallerIsSystemOrActiveUser(TAG)) { + return null; + } + + if (mService != null && mService.isAvailable()) { + return mService; + } + return null; + } + + CcBinder(CCService svc) { + mService = svc; + } + + @Override + public void cleanup() { + mService = null; + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CallControlState.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CallControlState.java new file mode 100644 index 00000000000..2feb65a971a --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CallControlState.java @@ -0,0 +1,84 @@ +/* + *Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ +/* + * Copyright 2012 The Android Open Source Project + * + * 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. + */ +package com.android.bluetooth.cc; + +import java.util.Objects; +import java.util.Arrays; + +/** + * A blob of data representing an overall call state on the phone + */ +class CallControlState { + + int mIndex; + /** + * Number of active calls + */ + int mNumActive; + /** + * Number of held calls + */ + int mNumHeld; + /** + * Current call setup state + */ + int mState; + /** + * Currently active call's phone number + */ + String mNumber; + /** + * Phone number type + */ + int mType; + + /** + * flags to define direction, information witheld by network or server. + */ + int mFlags; + + /** + * Caller display name + */ + String mName; + + int mDirection; + + CallControlState(int numActive, int numHeld, int callState, String number, int type, + String name) { + mNumActive = numActive; + mNumHeld = numHeld; + mState = callState; + mNumber = number; + mType = type; + mName = name; + } + CallControlState(int index, int callState, int flags) { + mIndex = index; + mState = callState; + mFlags = flags; + } + CallControlState(int index, int direction, int callState, String number) { + mIndex = index; + mDirection = direction; + mState = callState; + mNumber = number; + } + +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupAppMap.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupAppMap.java new file mode 100644 index 00000000000..506d88b8ada --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupAppMap.java @@ -0,0 +1,164 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + *****************************************************************************/ + +package com.android.bluetooth.groupclient; + +import android.bluetooth.IBluetoothGroupCallback; +import android.bluetooth.BluetoothGroupCallback; + +import android.os.Binder; +import android.os.IBinder; +import android.os.IInterface; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Collections; +import java.util.concurrent.ConcurrentHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.UUID; + +/* This class keeps track of registered GroupClient applications and + * managing callbacks to be given to appropriate app or module */ + +public class GroupAppMap { + + private static final String TAG = "BluetoothGroupAppMap"; + + class GroupClientApp { + /* The UUID of the application */ + public UUID uuid; + + /* The id of the application */ + public int appId; + + /* flag to determine if Bluetooth module has registered. */ + public boolean isLocal; + + /* Callbacks to be given to application */ + public IBluetoothGroupCallback appCb; + + /* Callbacks to be given to registered Bluetooth modules*/ + public BluetoothGroupCallback mCallback; + + public boolean isRegistered; + + /** Death receipient */ + private IBinder.DeathRecipient mDeathRecipient; + + GroupClientApp(UUID uuid, boolean isLocal, IBluetoothGroupCallback appCb, + BluetoothGroupCallback localCallbacks) { + this.uuid = uuid; + this.isLocal = isLocal; + this.appCb = appCb; + this.mCallback = localCallbacks; + this.isRegistered = true; + appUuids.add(uuid); + } + + /** + * To link death recipient + */ + void linkToDeath(IBinder.DeathRecipient deathRecipient) { + try { + IBinder binder = ((IInterface) appCb).asBinder(); + binder.linkToDeath(deathRecipient, 0); + mDeathRecipient = deathRecipient; + } catch (RemoteException e) { + Log.e(TAG, "Unable to link deathRecipient for appId: " + appId); + } + } + + } + + List mApps = Collections.synchronizedList(new ArrayList()); + + ArrayList appUuids = new ArrayList(); + + /** + * Add an entry to the application list. + */ + GroupClientApp add(UUID uuid, boolean isLocal, IBluetoothGroupCallback appCb, + BluetoothGroupCallback localCallback) { + synchronized (mApps) { + GroupClientApp app = new GroupClientApp(uuid, isLocal, appCb, localCallback); + mApps.add(app); + return app; + } + } + + /** + * Remove the entry for a given UUID + */ + void remove(UUID uuid) { + synchronized (mApps) { + Iterator i = mApps.iterator(); + while (i.hasNext()) { + GroupClientApp entry = i.next(); + if (entry.uuid.equals(uuid)) { + entry.isRegistered = false; + i.remove(); + break; + } + } + } + } + + /** + * Remove the entry for a given application ID. + */ + void remove(int appId) { + synchronized (mApps) { + Iterator i = mApps.iterator(); + while (i.hasNext()) { + GroupClientApp entry = i.next(); + if (entry.appId == appId) { + entry.isRegistered = false; + i.remove(); + break; + } + } + } + } + + /** + * Get GroupClient application by UUID. + */ + GroupClientApp getByUuid(UUID uuid) { + synchronized (mApps) { + Iterator i = mApps.iterator(); + while (i.hasNext()) { + GroupClientApp entry = i.next(); + if (entry.uuid.equals(uuid)) { + return entry; + } + } + } + Log.e(TAG, "App not found for UUID " + uuid); + return null; + } + + /** + * Get a GroupClient application by appId. + */ + GroupClientApp getById(int appId) { + synchronized (mApps) { + Iterator i = mApps.iterator(); + while (i.hasNext()) { + GroupClientApp entry = i.next(); + if (entry.appId == appId) { + return entry; + } + } + } + Log.e(TAG, "GroupClient App not found for appId " + appId); + return null; + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupClientNativeInterface.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupClientNativeInterface.java new file mode 100644 index 00000000000..749e1b5ee06 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupClientNativeInterface.java @@ -0,0 +1,237 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + *****************************************************************************/ + +package com.android.bluetooth.groupclient; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import java.util.ArrayList; +import java.util.List; +import android.util.Log; +import java.util.UUID; + +import com.android.bluetooth.Utils; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +/** + * CSIP Client Native Interface to/from JNI. + */ +public class GroupClientNativeInterface { + private static final String TAG = "BluetoothGroupNativeIntf"; + private static final boolean DBG = true; + private BluetoothAdapter mAdapter; + + @GuardedBy("INSTANCE_LOCK") + private static GroupClientNativeInterface sInstance; + private static final Object INSTANCE_LOCK = new Object(); + + static { + classInitNative(); + } + + private GroupClientNativeInterface() { + mAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mAdapter == null) { + Log.wtfStack(TAG, "No Bluetooth Adapter Available"); + } + } + + /** + * Get singleton instance. + */ + public static GroupClientNativeInterface getInstance() { + synchronized (INSTANCE_LOCK) { + if (sInstance == null) { + sInstance = new GroupClientNativeInterface(); + } + return sInstance; + } + } + + /** + * Initializes the native interface. + * + * priorities to configure. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void init() { + initNative(); + } + + /** + * Cleanup the native interface. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void cleanup() { + cleanupNative(); + } + + /** + * Register CSIP app with the stack code. + * + * @param appUuidLsb lsb of app uuid. + * @param appUuidMsb msb of app uuid. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void registerCsipApp(long appUuidLsb, long appUuidMsb) { + registerCsipAppNative(appUuidLsb, appUuidMsb); + } + + /** + * Register CSIP app with the stack code. + * + * @param appId ID of the application to be unregistered. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void unregisterCsipApp(int appId) { + unregisterCsipAppNative(appId); + } + + /** + * Change lock value of the coordinated set member + * + * @param appId ID of the application which is requesting change in lock status + * @param setId Identifier of the set + * @param devices List of bluetooth devices for whick lock status change is required + * @param value Lock/Unlock value + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void setLockValue(int appId, int setId, List devices, + int value) { + int i = 0; + int size = ((devices != null) ? devices.size() : 0); + String[] devicesList = new String[size]; + if (size > 0) { + for (BluetoothDevice device: devices) { + devicesList[i++] = device.toString(); + } + } + setLockValueNative(appId, setId, value, devicesList); + } + + /** + * Initiates Csip connection to a remote device. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean connectSetDevice(int appId, BluetoothDevice device) { + return connectSetDeviceNative(appId, getByteAddress(device)); + } + + /** + * Disconnects Csip from a remote device. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean disconnectSetDevice(int appId, BluetoothDevice device) { + return disconnectSetDeviceNative(appId, getByteAddress(device)); + } + + + private BluetoothDevice getDevice(byte[] address) { + return mAdapter.getRemoteDevice(address); + } + + private byte[] getByteAddress(BluetoothDevice device) { + if (device == null) { + return Utils.getBytesFromAddress("00:00:00:00:00:00"); + } + return Utils.getBytesFromAddress(device.getAddress()); + } + + private void onCsipAppRegistered (int status, int appId, + long uuidLsb, long uuidMsb) { + UUID uuid = new UUID(uuidMsb, uuidLsb); + + GroupService service = GroupService.getGroupService(); + if (service != null) { + service.onCsipAppRegistered(status, appId, uuid); + } + } + + private void onConnectionStateChanged(int appId, String bdAddr, + int state, int status) { + BluetoothDevice device = BluetoothAdapter.getDefaultAdapter() + .getRemoteDevice(bdAddr); + + GroupService service = GroupService.getGroupService(); + if (service != null) { + service.onConnectionStateChanged (appId, device, state, status); + } + } + + private void onNewSetFound (int setId, String bdAddr, int size, byte[] sirk, + long uuidLsb, long uuidMsb, boolean lockSupport) { + UUID uuid = new UUID(uuidMsb, uuidLsb); + BluetoothDevice device = BluetoothAdapter.getDefaultAdapter() + .getRemoteDevice(bdAddr); + + GroupService service = GroupService.getGroupService(); + if (service != null) { + service.onNewSetFound(setId, device, size, sirk, uuid, lockSupport); + } + } + + private void onNewSetMemberFound (int setId, String bdAddr) { + BluetoothDevice device = + BluetoothAdapter.getDefaultAdapter().getRemoteDevice(bdAddr); + + GroupService service = GroupService.getGroupService(); + if (service != null) { + service.onNewSetMemberFound(setId, device); + } + } + + private void onLockStatusChanged (int appId, int setId, int value, + int status, String[] bdAddr) { + List lockMembers = new ArrayList(); + for (String address: bdAddr) { + lockMembers.add(mAdapter.getRemoteDevice(address.toUpperCase())); + } + + GroupService service = GroupService.getGroupService(); + if (service != null) { + service.onLockStatusChanged(appId, setId, value, status, lockMembers); + } + } + + private void onLockAvailable (int appId, int setId, String address) { + BluetoothDevice device = mAdapter.getRemoteDevice(address); + GroupService service = GroupService.getGroupService(); + if (service != null) { + service.onLockAvailable(appId, setId, device); + } + } + + private void onSetSizeChanged (int setId, int size, String address) { + BluetoothDevice device = mAdapter.getRemoteDevice(address); + GroupService service = GroupService.getGroupService(); + if (service != null) { + service.onSetSizeChanged(setId, size, device); + } + } + + private void onSetSirkChanged(int setId, byte[] sirk, String address) { + BluetoothDevice device = mAdapter.getRemoteDevice(address); + GroupService service = GroupService.getGroupService(); + if (service != null) { + service.onSetSirkChanged(setId, sirk, device); + } + } + + // Native methods that call JNI interface + private static native void classInitNative(); + private native void initNative(); + private native void cleanupNative(); + private native void registerCsipAppNative(long appUuidLsb, long appUuidMsb); + private native void unregisterCsipAppNative(int appId); + private native void setLockValueNative(int appId, int setId, int value, String[] devicesList); + private native boolean connectSetDeviceNative(int appId, byte[] address); + private native boolean disconnectSetDeviceNative(int appId, byte[] address); +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupScanner.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupScanner.java new file mode 100644 index 00000000000..bdb95bb3977 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupScanner.java @@ -0,0 +1,515 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +package com.android.bluetooth.groupclient; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDeviceGroup; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothUuid; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelUuid; +import android.os.SystemProperties; + +import android.util.Log; + +import com.android.bluetooth.btservice.AdapterService; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.HashSet; +import java.util.List; +import java.util.UUID; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import javax.crypto.spec.IvParameterSpec; + +/** + * Class that handles Bluetooth LE Scan results for Set member discovery. + * It performs scan result resolution for set identification. + * + * @hide + */ +public class GroupScanner { + private static final boolean DBG = true; + private static final boolean VDBG = GroupService.VDBG; + private static final String TAG = "BluetoothGroupScanner"; + + // messages for handling filtered PSRI Scan results + private static final int MSG_HANDLE_LE_SCAN_RESULT = 0; + + // message for starting coordinated set discovery + private static final int MSG_START_SET_DISCOVERY = 1; + + // message to stop coordinated set discovery + private static final int MSG_STOP_SET_DISCOVERY = 2; + + // message when set member discovery timeout happens + private static final int MSG_SET_MEMBER_DISC_TIMEOUT = 3; + + // message to handle PSRI from EIR packet + private static final int MSG_HANDLE_EIR_RESPONSE = 4; + + // PSRI Service AD Type + private final ParcelUuid PSRI_SERVICE_ADTYPE_UUID + = BluetoothUuid.parseUuidFrom(new byte[]{0x2E, 0x00}); + + private static final int PSRI_LEN = 6; + private static final int PSRI_SPLIT_LEN = 3; // 24 bits + private static final int AES_128_IO_LEN = 16; + + // Set Member Discovery timeout + private static final int SET_MEMBER_DISCOVERY_TIMEOUT = 10000; // 10 sec + + private BluetoothAdapter mBluetoothAdapter; + private BluetoothDevice mCurrentDevice; + private BluetoothLeScanner mScanner; + private GroupService mGroupService; + private volatile CsipHandler mHandler; + private Handler mainHandler; + private boolean mScanResolution; + private int mDiscoveryStoppedReason; + private CsipLeScanCallback mCsipScanCallback; + + // parameters for set discovery + private int mSetId; + private byte[] mSirk; + private int mTransport; + private int mSetSize; + private int mTotalDiscovered; + + private int mScanType = 1; + + // filter out duplicate scans + ArrayList scannedDevices = new ArrayList(); + + GroupScanner(GroupService service) { + mGroupService = service; + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + + // register receiver for Bluetooth State change + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); + mGroupService.registerReceiver(mReceiver, filter); + + mainHandler = new Handler(mGroupService.getMainLooper()); + HandlerThread thread = new HandlerThread("CsipScanHandlerThread"); + thread.start(); + mHandler = new CsipHandler(thread.getLooper()); + mCsipScanCallback = new CsipLeScanCallback(); + ScanRecord.DATA_TYPE_GROUP_AD_TYPE = 0x2E; + /* Testing: Property used for deciding scan and filter type. To be removed */ + mScanType = SystemProperties.getInt( + "persist.vendor.service.bt.csip.scantype", 1); + } + + // Handler for CSIP scan operations and set member resolution. + private class CsipHandler extends Handler { + CsipHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + if (VDBG) Log.v(TAG, "msg.what = " + msg.what); + + switch (msg.what) { + case MSG_HANDLE_LE_SCAN_RESULT: + // start processing scan result + int callBackType = msg.arg1; + ScanResult result = (ScanResult) msg.obj; + mCurrentDevice = result.getDevice(); + + /* In case of DUMO device if advertisement is coming from other RPA */ + if (mCurrentDevice.getBondState() == BluetoothDevice.BOND_BONDED) { + return; + } + + // skip scanresult if already processed for this device + if (scannedDevices.contains(mCurrentDevice)) { + if (VDBG) { + Log.w(TAG, "duplicate scanned result or Device" + + mCurrentDevice + " Group info already resolved. Ignore"); + } + return; + } + + scannedDevices.add(mCurrentDevice); + ScanRecord record = result.getScanRecord(); + + // get required service data with PSRI AD Type + byte[] srvcData = null; + /* for debugging purpose */ + if (mScanType == 2) { + srvcData = record.getServiceData(PSRI_SERVICE_ADTYPE_UUID); + } else { + srvcData = record.getGroupIdentifierData(); + } + if (srvcData == null || srvcData.length != PSRI_LEN) { + Log.e(TAG, "Group info with incorrect length found " + + "in advertisement of " + mCurrentDevice); + return; + } + + startPsriResolution(srvcData); + break; + + case MSG_HANDLE_EIR_RESPONSE: + EirData eirData = (EirData)msg.obj; + mCurrentDevice = eirData.curDevice; + byte[] eirGroupData = eirData.groupData; + + // skip eir if already processed for this device + if (scannedDevices.contains(mCurrentDevice)) { + if (VDBG) { + Log.w(TAG, "duplicate eir or Device" + + mCurrentDevice + " PSRI already resolved. Ignore"); + } + return; + } + + scannedDevices.add(mCurrentDevice); + if (eirGroupData == null || eirGroupData.length != PSRI_LEN) { + Log.e(TAG, "PSRI data with incorrect length found " + + "in EIR of " + mCurrentDevice); + return; + } + startPsriResolution(eirGroupData); + break; + + // High priority msg received in front of the message queue + case MSG_SET_MEMBER_DISC_TIMEOUT: + mDiscoveryStoppedReason = BluetoothDeviceGroup.DISCOVERY_STOPPED_BY_TIMEOUT; + case MSG_STOP_SET_DISCOVERY: + handleStopSetDiscovery(); + break; + + // High priority msg received in front of the message queue + case MSG_START_SET_DISCOVERY: + handleStartSetDiscovery(); + break; + + default: + Log.e(TAG, "Unknown message : " + msg.what); + } + } + } + + /* BroadcastReceiver for BT ON State intent for registering BLE Scanner */ + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) { + Log.e(TAG, "Received intent with null action"); + return; + } + + switch (action) { + case BluetoothAdapter.ACTION_STATE_CHANGED: + mScanner = mBluetoothAdapter.getBluetoothLeScanner(); + break; + } + + } + }; + + /* Scan results callback */ + private class CsipLeScanCallback extends ScanCallback { + @Override + public void onScanResult(int callBackType, ScanResult result) { + if (VDBG) Log.v(TAG, "onScanResult callBackType : " + callBackType); + if (mHandler != null && mScanResolution) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_HANDLE_LE_SCAN_RESULT, + callBackType, 0, result)); + } else { + if (VDBG) Log.e(TAG, "onScanResult mHandler is null" + + " or Scan Resolution is stopped"); + } + } + + public void onScanFailed(int errorCode) { + mScanResolution = false; + Log.e(TAG, "Scan failed. Error code: " + new Integer(errorCode).toString()); + } + } + + /* EIR Data */ + private class EirData { + private BluetoothDevice curDevice; + private byte[] groupData; + + EirData(BluetoothDevice device, byte[] data) { + curDevice = device; + groupData = data; + } + } + + /* API that handles PSRI received from EIR */ + public void handleEIRGroupData(BluetoothDevice device, byte[] data) { + if (VDBG) Log.v(TAG, "handleEirData: device: " + device); + if (mHandler != null && mScanResolution) { + EirData eirData = new EirData(device, data); + mHandler.sendMessage(mHandler.obtainMessage(MSG_HANDLE_EIR_RESPONSE, eirData)); + } else { + if (VDBG) Log.e(TAG, "handleEirData mHandler is null" + + " or Inquiry Scan Resolution is stopped"); + } + } + + /* API to start set discovery by starting either LE scan or BREDR Inquiry */ + void startSetDiscovery(int setId, byte[] sirk, int transport, + int size, List setDevices) { + Log.d(TAG, "startGroupDiscovery: groupId: " + setId + ", group size = " + + size + ", Total discovered = " + setDevices.size() + + " Transport = " + transport); + + // check if set discovery is already in progress + if (mScanResolution) { + Log.e(TAG, "Group discovery is already in progress for Group Id: " + mSetId + + ". Ignore this request"); + return; + } + + // mark parameters of the set to be discovered + mSetId = setId; + mTransport = transport; + mSetSize = size; + mTotalDiscovered = setDevices.size(); + mSirk = Arrays.copyOf(sirk, AES_128_IO_LEN); + reverseByteArray(mSirk); + + // clear scanned arrayList and add already found set members to it + scannedDevices.clear(); + scannedDevices.addAll(setDevices); + + //post message in the front of message queue + mHandler.sendMessageAtFrontOfQueue( + mHandler.obtainMessage(MSG_START_SET_DISCOVERY)); + } + + /* API to start discovery with required settings and transport */ + void handleStartSetDiscovery() { + Log.d(TAG, "handleStartGroupDiscovery"); + mScanResolution = true; + + if (mTransport == BluetoothDevice.DEVICE_TYPE_CLASSIC) { + // start BREDR inquiry (unfiltered) + mBluetoothAdapter.startDiscovery(); + } else { + // Confiigure scan filter and start filtered scan for PSRI data + ScanSettings.Builder settingBuilder = new ScanSettings.Builder(); + List filters = new ArrayList(); + byte[] psri = {}; + + mScanType = SystemProperties.getInt( + "persist.vendor.service.bt.csip.scantype", 1); + + // for debugging purpose only + if (mScanType == 2) { + filters.add(new ScanFilter.Builder().setServiceData( + PSRI_SERVICE_ADTYPE_UUID, psri).build()); + } else if (mScanType == 1) { + filters.add(new ScanFilter.Builder().setGroupBasedFiltering(true) + .build()); + } + settingBuilder.setScanMode(ScanSettings.SCAN_MODE_BALANCED) + .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) + .setLegacy(false); + + // start BLE filtered Scan + if (mScanType != 0) { + Log.i(TAG, " filtered scan started");// debug + mScanner.startScan(filters, settingBuilder.build(), mCsipScanCallback); + } else { + Log.i(TAG, " Unfiltered scan started");// debug + mScanner.startScan(mCsipScanCallback); + } + } + + // Start Set Member discovery timeout of 10 sec + mHandler.removeMessages(MSG_SET_MEMBER_DISC_TIMEOUT); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_SET_MEMBER_DISC_TIMEOUT), + SET_MEMBER_DISCOVERY_TIMEOUT); + } + + /* to stop set discorvey procedure - stop LE scan or BREDR inquiry */ + void stopSetDiscovery(int setId, int reason) { + Log.d(TAG, "stopGroupDiscovery"); + + mDiscoveryStoppedReason = reason; + + //post message in the front of message queue + mHandler.sendMessageAtFrontOfQueue( + mHandler.obtainMessage(MSG_STOP_SET_DISCOVERY)); + } + + /* handles actions to be taken once set discovery is needed to be stopped*/ + void handleStopSetDiscovery() { + Log.d(TAG, "handleStopGroupDiscovery"); + mScanResolution = false; + + if (mTransport == BluetoothDevice.DEVICE_TYPE_LE || + mTransport == BluetoothDevice.DEVICE_TYPE_DUAL) { + mScanner.stopScan(mCsipScanCallback); + } else { + mBluetoothAdapter.cancelDiscovery(); + } + + // remove all the queued scan results and set member discovery timeout message + mHandler.removeMessages(MSG_HANDLE_LE_SCAN_RESULT); + mHandler.removeMessages(MSG_SET_MEMBER_DISC_TIMEOUT); + + // Give callback to service to route it to requesting application + mainHandler.post(new Runnable() { + @Override + public void run() { + mGroupService.onSetDiscoveryCompleted( + mSetId, mTotalDiscovered, mDiscoveryStoppedReason); + } + }); + } + + /* Starts resolution of PSRI data received in scan results */ + void startPsriResolution(byte[] psri) { + Log.d(TAG, "startGroupResolution"); + + if (VDBG) printByteArrayInHex(psri, "GroupInfo"); + // obtain remote hash and random number + byte[] remoteHash = new byte[PSRI_SPLIT_LEN]; + byte[] randomNumber = new byte[PSRI_SPLIT_LEN]; + + // Get remote hash from first 24 bits of PSRI + System.arraycopy(psri, 0, remoteHash, 0, PSRI_SPLIT_LEN); + // Get random number from last 24 bits of PSRI + System.arraycopy(psri, PSRI_SPLIT_LEN, randomNumber, 0, PSRI_SPLIT_LEN); + + byte[] localHash = computeLocalHash(randomNumber); + + if (VDBG) { + printByteArrayInHex(localHash, "localHash"); + printByteArrayInHex(remoteHash, "remoteHash"); + } + + if (localHash != null) { + validateSetMember(localHash, remoteHash); + } + } + + /* computes local hash from received random number and SIRK */ + byte[] computeLocalHash(byte[] randomNumber) { + byte[] localHash = new byte[AES_128_IO_LEN]; + byte[] randomNumber128 = new byte[AES_128_IO_LEN]; + System.arraycopy(randomNumber, 0, randomNumber128, 0, PSRI_SPLIT_LEN); + + reverseByteArray(randomNumber128); + + if (VDBG) { + // for debugging + printByteArrayInHex(mSirk, "reversed GroupIRK"); + printByteArrayInHex(randomNumber128, "reverse randomNumber"); + } + + try { + SecretKeySpec skeySpec = new SecretKeySpec(mSirk, "AES"); + Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding"); + cipher.init(Cipher.ENCRYPT_MODE, skeySpec); + localHash = cipher.doFinal(randomNumber128); + reverseByteArray(localHash); + if (VDBG) printByteArrayInHex(localHash, "after AES 128 encryption"); + return Arrays.copyOfRange(localHash, 0, PSRI_SPLIT_LEN); + } catch (Exception e) { + Log.e(TAG, "Exception while generating local hash: " + e); + } + return null; + } + + /* to validate that if remote belongs to a given coordinated set*/ + void validateSetMember(byte[] localHash, byte[] remoteHash) { + if (!Arrays.equals(localHash, remoteHash)) { + return; + } + Log.d(TAG, "New Group device discovered: " + mCurrentDevice); + mTotalDiscovered++; + + // give set member found callback on main thread + mainHandler.post(new Runnable() { + @Override + public void run() { + mGroupService.onSetMemberFound(mSetId, mCurrentDevice); + } + }); + + //check if all set members have been discovered + if (mSetSize > 0 && mTotalDiscovered >= mSetSize) { + // to immediatly ignore processing scan results after completion + mScanResolution = false; + mDiscoveryStoppedReason = BluetoothDeviceGroup.DISCOVERY_COMPLETED; + mHandler.sendMessageAtFrontOfQueue( + mHandler.obtainMessage(MSG_STOP_SET_DISCOVERY)); + } else { + // restart set member discovery timeout + mHandler.removeMessages(MSG_SET_MEMBER_DISC_TIMEOUT); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_SET_MEMBER_DISC_TIMEOUT), + SET_MEMBER_DISCOVERY_TIMEOUT); + } + + } + + /* cleanup tasks on BT OFF*/ + void cleanup() { + mGroupService.unregisterReceiver(mReceiver); + } + + // returns reversed byte array + void reverseByteArray(byte[] byte_arr) { + int size = byte_arr.length; + for (int i = 0; i < size/2; i++) { + byte b = byte_arr[i]; + byte_arr[i] = byte_arr[size - 1 - i]; + byte_arr[size - 1 - i] = b; + } + } + + public static byte[] hexStringToByteArray(String str) { + byte[] b = new byte[str.length() / 2]; + for (int i = 0; i < b.length; i++) { + int index = i * 2; + int val = Integer.parseInt(str.substring(index, index + 2), 16); + b[i] = (byte) val; + } + return b; + } + + // print byte array in hexadecimal format + void printByteArrayInHex(byte[] data, String name) { + final StringBuilder hex = new StringBuilder(); + for(byte b : data) { + hex.append(String.format("%02x", b)); + } + Log.i(TAG, name + ": " + hex); + } +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupService.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupService.java new file mode 100644 index 00000000000..cfff3f8b328 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupService.java @@ -0,0 +1,1093 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +package com.android.bluetooth.groupclient; + +import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission; +import android.bluetooth.BluetoothDeviceGroup; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.bluetooth.DeviceGroup; +import android.bluetooth.IBluetoothDeviceGroup; +import android.bluetooth.IBluetoothGroupCallback; +import android.bluetooth.BluetoothGroupCallback; +import android.content.AttributionSource; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ParcelUuid; +import android.os.SystemProperties; + +import android.util.Log; + +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.Config; +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.btservice.ServiceFactory; +import com.android.bluetooth.Utils; + +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +/** + * Provides Bluetooth CSIP Client profile, as a service in the Bluetooth application. + * @hide + */ +public class GroupService extends ProfileService { + private static final boolean DBG = true; + private static final String TAG = "BluetoothGroupService"; + protected static final boolean VDBG = true;//Log.isLoggable(TAG, Log.VERBOSE); + + private GroupScanner mGroupScanner; + + private static GroupService sGroupService; + + private AdapterService mAdapterService; + + GroupClientNativeInterface mGroupNativeInterface; + + GroupAppMap mAppMap = new GroupAppMap(); + + private static CopyOnWriteArrayList mCoordinatedSets + = new CopyOnWriteArrayList(); + + private static HashMap setSirkMap = new HashMap(); + + private static final int INVALID_APP_ID = 0x10; + private static final int INVALID_SET_ID = 0x10; + private static final UUID EMPTY_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000"); + + /* parameters to hold details for ongoing set discovery and pending set discovery */ + private SetDiscoveryRequest mCurrentSetDisc = null; + private SetDiscoveryRequest mPendingSetDisc = null; + + /* Constants for Coordinated set properties */ + private static final String SET_ID = "SET_ID"; + private static final String INCLUDING_SRVC = "INCLUDING_SRVC"; + private static final String SIZE = "SIZE"; + private static final String SIRK = "SIRK"; + private static final String LOCK_SUPPORT = "LOCK_SUPPORT"; + + private class SetDiscoveryRequest { + private int mAppId = INVALID_APP_ID; + private int mSetId = INVALID_SET_ID; + private boolean mDiscInProgress = false; + + SetDiscoveryRequest() { + mAppId = INVALID_APP_ID; + mSetId = INVALID_SET_ID; + mDiscInProgress = false; + } + + SetDiscoveryRequest(int appId, int setId, boolean inProgress) { + mAppId = appId; + mSetId = setId; + mDiscInProgress = inProgress; + } + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) { + Log.e(TAG, "Received intent with null action"); + return; + } + + switch (action) { + case BluetoothDevice.ACTION_BOND_STATE_CHANGED: + BluetoothDevice device = intent.getParcelableExtra( + BluetoothDevice.EXTRA_DEVICE); + int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.ERROR); + if (bondState == BluetoothDevice.BOND_NONE) { + int setId = getRemoteDeviceGroupId(device, null); + if (setId < BluetoothDeviceGroup.INVALID_GROUP_ID) { + Log.i(TAG, " Group Device "+ device + + " unpaired. Group ID: " + setId); + removeSetMemberFromCSet(setId, device); + } + } + break; + } + + } + }; + + + @Override + protected IProfileServiceBinder initBinder() { + return new GroupBinder(this); + } + + @Override + protected boolean start() { + if (DBG) { + Log.d(TAG, "start()"); + } + + mGroupNativeInterface = Objects.requireNonNull(GroupClientNativeInterface.getInstance(), + "GroupClientNativeInterface cannot be null when GroupService starts"); + + mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), + "AdapterService cannot be null when GroupService starts"); + + mGroupScanner = new GroupScanner(this); + + mGroupNativeInterface.init(); + setGroupService(this); + + // register receiver for Bluetooth State change + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + registerReceiver(mReceiver, filter); + + return true; + } + + private static synchronized void setGroupService(GroupService instance) { + if (DBG) { + Log.d(TAG, "setGroupService(): set to: " + instance); + } + sGroupService = instance; + } + + protected boolean stop() { + if (DBG) { + Log.d(TAG, "stop()"); + } + + if (mGroupScanner != null) { + mGroupScanner.cleanup(); + } + + if (sGroupService == null) { + Log.w(TAG, "stop() called already.."); + return true; + } + + unregisterReceiver(mReceiver); + + // Cleanup native interface + mGroupNativeInterface.cleanup(); + mGroupNativeInterface = null; + + // Mark service as stopped + setGroupService(null); + + // cleanup initializations + mGroupScanner = null; + mAdapterService = null; + + return true; + } + + @Override + protected void cleanup() { + if (DBG) { + Log.d(TAG, "cleanup()"); + } + + // Cleanup native interface + if (mGroupNativeInterface != null) { + mGroupNativeInterface.cleanup(); + mGroupNativeInterface = null; + } + + // cleanup initializations + mGroupScanner = null; + mAdapterService = null; + } + + /** + * Get the GroupService instance + * @return GroupService instance + */ + public static synchronized GroupService getGroupService() { + if (sGroupService == null) { + Log.w(TAG, "getGroupService(): service is NULL"); + return null; + } + + if (!sGroupService.isAvailable()) { + Log.w(TAG, "getGroupService(): service is not available"); + return null; + } + + return sGroupService; + } + + /* API to load coordinated set from bonded device on BT ON */ + public static void loadDeviceGroupFromBondedDevice ( + BluetoothDevice device, String setDetails) { + String[] csets = setDetails.split(" "); + if (VDBG) Log.v(TAG, " Device is part of " + csets.length + " device groups"); + + for (String setInfo: csets) { + String[] setProperties = setInfo.split("~"); + int setId = INVALID_SET_ID, size = 0; + UUID inclSrvcUuid = UUID.fromString("00000000-0000-0000-0000-000000000000"); + boolean lockSupport = false; + + for (String property: setProperties) { + if (VDBG) Log.v(TAG, "Property = " + property); + String[] propSplit = property.split(":"); + if (propSplit[0].equals(SET_ID)) { + setId = Integer.parseInt(propSplit[1]); + } else if (propSplit[0].equals(INCLUDING_SRVC)) { + inclSrvcUuid = UUID.fromString(propSplit[1]); + } else if (propSplit[0].equals(SIZE)) { + size = Integer.parseInt(propSplit[1]); + } else if (propSplit[0].equals(SIRK) && setId != 16) { + setSirkMap.put(setId, GroupScanner.hexStringToByteArray(propSplit[1])); + } else if (propSplit[0].equals(LOCK_SUPPORT)) { + lockSupport = Boolean.parseBoolean(propSplit[1]); + } + } + + DeviceGroup set = getCoordinatedSet(setId, false); + if (set == null) { + List members = new ArrayList(); + members.add(device); + set = new DeviceGroup(setId, size, members, + new ParcelUuid(inclSrvcUuid), lockSupport); + mCoordinatedSets.add(set); + } else { + if (!set.getDeviceGroupMembers().contains(device)) { + set.getDeviceGroupMembers().add(device); + } + } + if (VDBG) Log.v(TAG, "Device " + device + " loaded in Group ("+ setId +")" + + " Devices: " + set.getDeviceGroupMembers()); + } + } + + /* API to accept PSRI data from EIR packet */ + public void handleEIRGroupData(BluetoothDevice device, String data) { + mGroupScanner.handleEIRGroupData(device, data.getBytes()); + } + + public static void setAdvanceAudioSupport() { + Log.d(TAG, "setAdvanceAudioSupport: Setting support from LEA Module"); + + if (SystemProperties.get("persist.vendor.service.bt.adv_audio_mask").isEmpty()) { + SystemProperties.set("persist.vendor.service.bt.adv_audio_mask", + String.valueOf(Config.ADV_AUDIO_UNICAST_FEAT_MASK | + Config.ADV_AUDIO_BCA_FEAT_MASK | + Config.ADV_AUDIO_BCS_FEAT_MASK)); + } + } + + private static class GroupBinder + extends IBluetoothDeviceGroup.Stub implements IProfileServiceBinder { + private GroupService mService; + + private GroupService getService() { + if (mService != null && mService.isAvailable()) { + return mService; + } + return null; + } + + GroupBinder(GroupService service) { + if (DBG) { + Log.v(TAG, "GroupBinder()"); + } + mService = service; + } + + @Override + public void cleanup() { + mService = null; + } + + @Override + public void connect(int appId, BluetoothDevice device, AttributionSource source) { + if (DBG) { + Log.d(TAG, "connect Device " + device); + } + + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery( + service, source, "connect")) { + return; + } + service.connect(appId, device); + } + + @Override + public void disconnect(int appId, BluetoothDevice device, AttributionSource source) { + if (DBG) { + Log.d(TAG, "disconnect Device " + device); + } + + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery( + service, source, "disconnect")) { + return; + } + service.disconnect(appId, device); + } + + @Override + public void registerGroupClientApp(ParcelUuid uuid, + IBluetoothGroupCallback callback, AttributionSource source) { + if (VDBG) { + Log.d(TAG, "registerGroupClientApp"); + } + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery( + service, source, "registerGroupClientApp")) { + return; + } + service.registerGroupClientApp(uuid.getUuid(), callback, null); + } + + @Override + public void unregisterGroupClientApp(int appId, AttributionSource source) { + if (VDBG) { + Log.d(TAG, "unregisterGroupClientApp"); + } + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery( + service, source, "unregisterGroupClientApp")) { + return; + } + service.unregisterGroupClientApp(appId); + } + + @Override + public void setExclusiveAccess(int appId, int groupId, List devices, + int value, AttributionSource source) { + if (VDBG) { + Log.d(TAG, "setExclusiveAccess"); + } + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery( + service, source, "setExclusiveAccess")) { + return; + } + service.setLockValue(appId, groupId, devices, value); + } + + @Override + public void startGroupDiscovery(int appId, int groupId + , AttributionSource source) throws RemoteException { + if (VDBG) { + Log.d(TAG, "startGroupDiscovery"); + } + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery(service, + source, "startGroupDiscovery")) { + return; + } + service.startSetDiscovery(appId, groupId); + } + + @Override + public void stopGroupDiscovery(int appId, int groupId, AttributionSource source) { + if (VDBG) { + Log.d(TAG, "stopGroupDiscovery"); + } + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery( + service, source, "stopGroupDiscovery")) { + return; + } + enforceBluetoothPrivilegedPermission(service); + service.stopSetDiscovery(appId, groupId); + } + + @Override + public void getExclusiveAccessStatus(int appId, int groupId, + List devices, AttributionSource source) { + if (DBG) { + Log.d(TAG, "getExclusiveAccessStatus"); + } + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery( + service, source, "getExclusiveAccessStatus")) { + return; + } + enforceBluetoothPrivilegedPermission(service); + } + + @Override + public List getDiscoveredGroups(boolean mPublicAddr + , AttributionSource source) { + if (DBG) { + Log.d(TAG, "getDiscoveredGroups"); + } + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery( + service, source, "getDiscoveredGroups")) { + return null; + } + return service.getDiscoveredCoordinatedSets(mPublicAddr); + } + + @Override + public DeviceGroup getDeviceGroup(int setId, boolean mPublicAddr, + AttributionSource source) { + if (DBG) { + Log.d(TAG, "getDeviceGroup"); + } + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery( + service, source, "getDeviceGroup")) { + return null; + } + return (service.getCoordinatedSet(setId, mPublicAddr)); + } + + @Override + public int getRemoteDeviceGroupId (BluetoothDevice device, ParcelUuid uuid, + boolean mPublicAddr, AttributionSource source) { + if (DBG) { + Log.d(TAG, "getRemoteDeviceGroupId"); + } + GroupService service = getService(); + if (service == null || !Utils.checkConnectPermissionForDataDelivery( + service, source, "getRemoteDeviceGroupId")) { + return INVALID_SET_ID; + } + return service.getRemoteDeviceGroupId(device, uuid, mPublicAddr); + } + + @Override + public boolean isGroupDiscoveryInProgress (int setId, AttributionSource source) { + if (DBG) { + Log.d(TAG, "isGroupDiscoveryInProgress"); + } + GroupService service = getService(); + if (service == null || !Utils.checkScanPermissionForDataDelivery( + service, source, "isGroupDiscoveryInProgress")) { + return false; + } + return service.isSetDiscoveryInProgress(setId); + } + }; + + /** + * DeathReceipient handler to unregister applications those are + * disconnected ungracefully (ie. crash or forced close). + */ + class GroupAppDeathRecipient implements IBinder.DeathRecipient { + int mAppId; + + GroupAppDeathRecipient(int appId) { + mAppId = appId; + Log.i(TAG, "GroupAppDeathRecipient"); + } + + @Override + public void binderDied() { + if (DBG) { + Log.d(TAG, "Binder is dead - unregistering app (" + mAppId + ")!"); + } + + mAppMap.remove(mAppId); + unregisterGroupClientApp(mAppId); + } + + } + + /* for registration of other Bluetooth profile in Bluetooth App Space*/ + public void registerGroupClientModule(BluetoothGroupCallback callback) { + Log.d(TAG, "registerGroupClientModule"); + + UUID uuid; + + if (mGroupNativeInterface == null) return; + // Generate an unique UUID for Bluetooth Modules which is not used by others apps + do { + uuid = UUID.randomUUID(); + } while(mAppMap.appUuids.contains(uuid)); + + registerGroupClientApp(uuid, null, callback); + } + + /* Registers CSIP App or module with CSIP native layer */ + public void registerGroupClientApp(UUID uuid, IBluetoothGroupCallback appCb, + BluetoothGroupCallback localCallback) { + if (DBG) { + Log.d(TAG, "registerGroupClientApp: UUID = " + uuid.toString()); + } + + boolean isLocal = false; + if (localCallback != null) { + isLocal = true; + } + + mAppMap.add(uuid, isLocal, appCb, localCallback); + mGroupNativeInterface.registerCsipApp(uuid.getLeastSignificantBits(), + uuid.getMostSignificantBits()); + } + + /* Unregisters Bluetooth module (BT profile) with CSIP*/ + public void unregisterGroupClientModule(int appId) { + unregisterGroupClientApp(appId); + } + + /* Unregisters App/Module with CSIP*/ + public void unregisterGroupClientApp(int appId) { + if (DBG) { + Log.d(TAG, "unregisterGroupClientApp: appId = " + appId); + } + + if (mGroupNativeInterface == null) return; + mAppMap.remove(appId); + mGroupNativeInterface.unregisterCsipApp(appId); + } + + /* API to request change in lock value */ + public void setLockValue(int appId, int setId, List devices, + int value) { + if (DBG) { + Log.d(TAG, "setExclusiveAccess: appId = " + appId + ", setId: " + setId + + ", value = " + value + ", set Members = " + devices); + } + + if (mGroupNativeInterface == null) return; + // appId and setId validation is done at stack layer + mGroupNativeInterface.setLockValue(appId, setId, devices, value); + } + + /* Starts the set members discovery for the requested coordinated set */ + public void startSetDiscovery(int appId, int setId) throws RemoteException { + if (DBG) { + Log.d(TAG, "startGroupDiscovery. setId = " + setId + " Initiating appId = " + appId); + } + + // Get Apllication details + GroupAppMap.GroupClientApp app = mAppMap.getById(appId); + if (app == null) { + Log.e(TAG, "Application not found for appId: " + appId); + return; + } + + DeviceGroup cSet = getCoordinatedSet(setId, true); + if (cSet == null || !setSirkMap.containsKey(setId)) { + Log.e(TAG, "Invalid Group Id: " + setId); + mCurrentSetDisc = null; + app.appCb.onGroupDiscoveryStatusChanged(setId, BluetoothDeviceGroup.GROUP_DISCOVERY_STOPPED, + BluetoothDeviceGroup.DISCOVERY_NOT_STARTED_INVALID_PARAMS); + return; + } + + /* check if all set members are already discovered */ + int setSize = cSet.getDeviceGroupSize(); + if (setSize != 0 && cSet.getTotalDiscoveredGroupDevices() >= setSize) { + app.appCb.onGroupDiscoveryStatusChanged(setId, + BluetoothDeviceGroup.GROUP_DISCOVERY_STOPPED, + BluetoothDeviceGroup.DISCOVERY_COMPLETED); + return; + } + + if (mCurrentSetDisc != null && mCurrentSetDisc.mDiscInProgress) { + Log.e(TAG, "Group Discovery is already in Progress for Group: " + + mCurrentSetDisc.mSetId + " from AppId: " + mCurrentSetDisc.mAppId + + " Stop current Group discovery"); + mPendingSetDisc = new SetDiscoveryRequest(appId, setId, false); + mGroupScanner.stopSetDiscovery(mCurrentSetDisc.mSetId, 0); + return; + } else if (mCurrentSetDisc == null) { + mCurrentSetDisc = new SetDiscoveryRequest(appId, setId, false); + } + + int transport; + byte[] sirk; + + sirk = setSirkMap.get(setId); + + /*TODO: Optimize logic if device type is UNKNOWN */ + try { + BluetoothDevice device = cSet.getDeviceGroupMembers().get(0); + transport = device.getType(); + } catch (IndexOutOfBoundsException e) { + Log.e(TAG, "Invalid Group- No device found : " + e); + mCurrentSetDisc = null; + return; + } + + mGroupScanner.startSetDiscovery(setId, sirk, transport, + cSet.getDeviceGroupSize(), cSet.getDeviceGroupMembers()); + mCurrentSetDisc.mDiscInProgress = true; + + try { + if (app.appCb != null) { + app.appCb.onGroupDiscoveryStatusChanged(setId, + BluetoothDeviceGroup.GROUP_DISCOVERY_STARTED, + BluetoothDeviceGroup.DISCOVERY_STARTED_BY_APPL); + } + } catch (RemoteException e) { + Log.e(TAG, "Exception : " + e); + } + } + + /* Stops the set members discovery for the requested coordinated set */ + public void stopSetDiscovery(int appId, int setId) { + if (DBG) { + Log.d(TAG, "stopGroupDiscovery: appId = " + appId + " groupId = " + setId); + } + + // Get Apllication details + GroupAppMap.GroupClientApp app = mAppMap.getById(appId); + + if (app == null) { + Log.e(TAG, "Application not found for appId: " + appId); + return; + } + + // check if requesting app is stopping the discovery + if (mCurrentSetDisc == null || mCurrentSetDisc.mAppId != appId) { + Log.e(TAG, " Either no discovery in progress or Stop Request from" + + " App which has not started Group Discovery"); + return; + } + + mGroupScanner.stopSetDiscovery(setId, BluetoothDeviceGroup.DISCOVERY_STOPPED_BY_APPL); + } + + /* Reading lock status of coordinated set for ordered access procedure */ + public void getLockStatus(int setId, List devices) { + //TODO: Future enhancement + } + + /* To connect to Coordinated Set Device */ + public void connect (int appId, BluetoothDevice device) { + Log.d(TAG, "connect Device: " + device + ", appId: " + appId); + if (mGroupNativeInterface == null) return; + mGroupNativeInterface.connectSetDevice(appId, device); + } + + /* To disconnect from Coordinated Set Device */ + public void disconnect (int appId, BluetoothDevice device) { + Log.d(TAG, "disconnect Device: " + device + ", appId: " + appId); + if (mGroupNativeInterface == null) return; + mGroupNativeInterface.disconnectSetDevice(appId, device); + } + + public List getDiscoveredCoordinatedSets() { + return getDiscoveredCoordinatedSets(true); + } + + /* returns all discovered coordinated sets */ + public List getDiscoveredCoordinatedSets(boolean mPublicAddr) { + if (DBG) { + Log.d(TAG, "getDiscoveredGroups"); + } + + /* Add logic to replace random addresses to public addresses if requested */ + // Iterate on coordinated sets + // check address type. Replace with public if requested + if (mPublicAddr) { + List coordinatedSets = new ArrayList(); + AdapterService adapterService = Objects.requireNonNull( + AdapterService.getAdapterService(), + "AdapterService cannot be null"); + for (DeviceGroup set: mCoordinatedSets) { + DeviceGroup cSet = new DeviceGroup( + set.getDeviceGroupId(), set.getDeviceGroupSize(), + new ArrayList(), + set.getIncludingServiceUUID(), set.isExclusiveAccessSupported()); + for (BluetoothDevice device: set.getDeviceGroupMembers()) { + BluetoothDevice publicDevice = device; + if (adapterService.isIgnoreDevice(device)) { + publicDevice = adapterService.getIdentityAddress(device); + } + cSet.getDeviceGroupMembers().add(publicDevice); + } + coordinatedSets.add(cSet); + } + return coordinatedSets; + } + + return mCoordinatedSets; + } + + public static DeviceGroup getCoordinatedSet(int setId) { + return getCoordinatedSet(setId, true); + } + + /* returns requested coordinated set */ + public static DeviceGroup getCoordinatedSet(int setId, boolean mPublicAddr) { + if (DBG) { + Log.d(TAG, "getDeviceGroup : groupId = " + setId + + " mPublicAddr: " + mPublicAddr); + } + + AdapterService adapterService = Objects.requireNonNull( + AdapterService.getAdapterService(), "AdapterService cannot be null"); + + for (DeviceGroup cSet: mCoordinatedSets) { + if (cSet.getDeviceGroupId() == setId) { + if (!mPublicAddr) { + return cSet; + + // Public addresses are requested. Replace address with public addr + } else { + DeviceGroup set = new DeviceGroup( + cSet.getDeviceGroupId(), cSet.getDeviceGroupSize(), + new ArrayList(), + cSet.getIncludingServiceUUID(), cSet.isExclusiveAccessSupported()); + for (BluetoothDevice device: cSet.getDeviceGroupMembers()) { + if (device.getBondState() == BluetoothDevice.BOND_BONDED) { + BluetoothDevice publicDevice = device; + if (adapterService.isIgnoreDevice(device)) { + publicDevice = adapterService.getIdentityAddress(device); + } + set.getDeviceGroupMembers().add(publicDevice); + } + } + return set; + } + } + } + + return null; + } + + public boolean isSetDiscoveryInProgress (int setId) { + if (DBG) { + Log.d(TAG, "isGroupDiscoveryInProgress: groupId = " + setId); + } + + if (mCurrentSetDisc != null && mCurrentSetDisc.mSetId == setId + && mCurrentSetDisc.mDiscInProgress) + return true; + return false; + } + + public int getRemoteDeviceGroupId (BluetoothDevice device, ParcelUuid uuid) { + return getRemoteDeviceGroupId(device, uuid, true); + } + + public int getRemoteDeviceGroupId (BluetoothDevice device, ParcelUuid uuid, + boolean mPublicAddr) { + if (DBG) { + Log.d(TAG, "getRemoteDeviceGroupId: device = " + device + " uuid = " + uuid + + ", mPublicAddr = " + mPublicAddr); + } + + if (mAdapterService == null) { + Log.e(TAG, "AdapterService instance is NULL. Return."); + return INVALID_SET_ID; + } + + BluetoothDevice setDevice = null; + if (mPublicAddr && mAdapterService.isIgnoreDevice(device)) { + setDevice = mAdapterService.getIdentityAddress(device); + } + + if (uuid == null) { + uuid = new ParcelUuid(EMPTY_UUID); + } + + for (DeviceGroup cSet: mCoordinatedSets) { + if ((cSet.getDeviceGroupMembers().contains(device) || + cSet.getDeviceGroupMembers().contains(setDevice)) + && cSet.getIncludingServiceUUID().equals(uuid)) { + return cSet.getDeviceGroupId(); + } + } + + return INVALID_SET_ID; + } + + /* This API is called when pairing with LE Audio capable set member fails or + * when set member is unpaired. Removing the set member from list gives option + * to user to rediscover it */ + public void removeSetMemberFromCSet(int setId, BluetoothDevice device) { + Log.d(TAG, "removeDeviceFromDeviceGroup: setId = " + setId + ", Device: " + device); + + DeviceGroup cSet = getCoordinatedSet(setId, false); + if (cSet != null) { + cSet.getDeviceGroupMembers().remove(device); + if (cSet.getDeviceGroupMembers().size() == 0) { + Log.i(TAG, "Last device unpaired. Removing Device Group from database"); + mCoordinatedSets.remove(cSet); + return; + } + } + + cSet = getCoordinatedSet(setId, true); + if (cSet != null) { + cSet.getDeviceGroupMembers().remove(device); + if (cSet.getDeviceGroupMembers().size() == 0) { + Log.i(TAG, "Last device unpaired. Removing Device Group from database"); + mCoordinatedSets.remove(cSet); + } + } + } + + public void printAllCoordinatedSets() { + if (VDBG) { + for (DeviceGroup set: mCoordinatedSets) { + Log.i(TAG, "GROUP_ID: " + set.getDeviceGroupId() + + ", size = " + set.getDeviceGroupSize() + + ", discovered = " + set.getTotalDiscoveredGroupDevices() + + ", Including Srvc Uuid = "+ set.getIncludingServiceUUID() + + ", devices = " + set.getDeviceGroupMembers()); + } + } + } + + /* Callback received from CSIP native layer when an APP/module has been registered */ + protected void onCsipAppRegistered (int status, int appId, UUID uuid) { + Log.d(TAG, "onGroupClientAppRegistered: appId: " + appId + ", UUID: " + uuid.toString()); + + GroupAppMap.GroupClientApp app = mAppMap.getByUuid(uuid); + + if (app == null) { + Log.e(TAG, "Application not found for UUID: " + uuid.toString()); + return; + } + + app.appId = appId; + // Give callback to the application that app has been registered + try { + if (app.isRegistered && app.isLocal) { + app.mCallback.onGroupClientAppRegistered(status, appId); + } else if (app.isRegistered && !app.isLocal) { + app.linkToDeath(new GroupAppDeathRecipient(appId)); + app.appCb.onGroupClientAppRegistered(status, appId); + } + } catch (RemoteException e) { + Log.e(TAG, "Exception : " + e); + } + } + + /* When CSIP Profile connection state has been changed */ + protected void onConnectionStateChanged(int appId, BluetoothDevice device, + int state, int status) { + Log.d(TAG, "onConnectionStateChanged: appId: " + appId + ", device: " + device + + ", State: " + state + ", Status: " + status); + + GroupAppMap.GroupClientApp app = mAppMap.getById(appId); + + if (app == null) { + Log.e(TAG, "Application not found for appId: " + appId); + return; + } + + try { + if (app.isRegistered && app.isLocal) { + app.mCallback.onConnectionStateChanged(state, device); + } else if (app.isRegistered && !app.isLocal) { + app.appCb.onConnectionStateChanged(state, device); + } + } catch (RemoteException e) { + Log.e(TAG, "Exception : " + e); + } + } + + /* When a new set member is discovered as a part of Set Discovery procedure */ + protected void onSetMemberFound (int setId, BluetoothDevice device) { + Log.d(TAG, "onGroupDeviceFound: groupId: " + setId + ", device: " + device); + for (DeviceGroup cSet: mCoordinatedSets) { + if (cSet.getDeviceGroupId() == setId + && !cSet.getDeviceGroupMembers().contains(device)) { + cSet.getDeviceGroupMembers().add(device); + break; + } + } + + // Give callback to adapterservice to initiate bonding if required + mAdapterService.processGroupMember(setId, device); + + // Give callback to the application that started Set Discovery + GroupAppMap.GroupClientApp app = mAppMap.getById(mCurrentSetDisc.mAppId); + + if (app == null) { + Log.e(TAG, "Application not found for appId: " + mCurrentSetDisc.mAppId); + return; + } + + try { + if (app.appCb != null) { + app.appCb.onGroupDeviceFound(setId, device); + } + } catch (RemoteException e) { + Log.e(TAG, "Exception : " + e); + } + } + + /* When set discovery procedure has been completed */ + protected void onSetDiscoveryCompleted (int setId, + int totalDiscovered, int reason) { + Log.d(TAG, "onGroupDiscoveryCompleted: groupId: " + setId + ", totalDiscovered = " + + totalDiscovered + "reason: " + reason); + + // mark Set Discovery procedure as completed + mCurrentSetDisc.mDiscInProgress = false; + // Give callback to the application that Set Discovery has been completed + GroupAppMap.GroupClientApp app = mAppMap.getById(mCurrentSetDisc.mAppId); + + if (app == null) { + Log.e(TAG, "Application not found for appId: " + mCurrentSetDisc.mAppId); + return; + } + + try { + if (app.appCb != null) { + app.appCb.onGroupDiscoveryStatusChanged(setId, + BluetoothDeviceGroup.GROUP_DISCOVERY_STOPPED, reason); + } + + DeviceGroup cSet = getCoordinatedSet(setId, false); + if (VDBG && cSet != null) { + Log.i(TAG, "Device Group: groupId" + setId + ", devices: " + + cSet.getDeviceGroupMembers()); + } + + if (mPendingSetDisc != null) { + mCurrentSetDisc = mPendingSetDisc; + mPendingSetDisc = null; + startSetDiscovery(mCurrentSetDisc.mAppId, mCurrentSetDisc.mSetId); + } else { + mCurrentSetDisc = null; + } + } catch (RemoteException e) { + Log.e(TAG, "Exception : " + e); + } + } + + /* Callback received from CSIP native layer when a new Coordinated set has been + * identified with remote device */ + protected void onNewSetFound(int setId, BluetoothDevice device, int size, + byte[] sirk, UUID pSrvcUuid, boolean lockSupport) { + Log.d(TAG, "onNewGroupFound: Address : " + device + ", groupId: " + setId + + ", size: " + size + ", uuid: " + pSrvcUuid.toString()); + + // Form Coordinated Set Object and store in ArrayList + List devices = new ArrayList(); + devices.add(device); + DeviceGroup cSet = new DeviceGroup(setId, size, devices, + new ParcelUuid(pSrvcUuid), lockSupport); + mCoordinatedSets.add(cSet); + + // Store sirk in hashmap of setId, sirk + setSirkMap.put(setId, sirk); + + // Give Callback to all registered application + try { + for (GroupAppMap.GroupClientApp app: mAppMap.mApps) { + if (app.isRegistered && !app.isLocal) { + if (app.appCb != null)//temp check + app.appCb.onNewGroupFound(setId, device, new ParcelUuid(pSrvcUuid)); + } + } + } catch (RemoteException e) { + Log.e(TAG, "Exception : " + e); + } + } + + /* Callback received from CSIP native layer when undiscovered set member is connected */ + protected void onNewSetMemberFound (int setId, BluetoothDevice device) { + Log.d(TAG, "onNewGroupDeviceFound: groupId = " + setId + ", Device = " + device); + + if (mAdapterService == null) { + Log.e(TAG, "AdapterService instance is NULL. Return."); + return; + } + + if (mAdapterService.isIgnoreDevice(device)) { + device = mAdapterService.getIdentityAddress(device); + } + // check if this device is already part of an already existing coordinated set + /* Scenario: When a device was not discovered during initial set discovery + * procedure and later user had explicitely paired with this device + * from pair new device UI option. Not required to send onSetMemberFound + * callback to application*/ + if (setSirkMap.containsKey(setId)) { + for (DeviceGroup cSet: mCoordinatedSets) { + if (cSet.getDeviceGroupId() == setId && + (!cSet.getDeviceGroupMembers().contains(device))) { + cSet.getDeviceGroupMembers().add(device); + break; + } + } + return; + } + } + + /* callback received when lock status is changed for requested coordinated set*/ + protected void onLockStatusChanged (int appId, int setId, int value, int status, + List devices) { + Log.d(TAG, "onExclusiveAccessChanged: appId = " + appId + ", groupId = " + setId + + ", value = " + value + ", status = " + status + ", devices = " + devices); + GroupAppMap.GroupClientApp app = mAppMap.getById(appId); + + if (app == null) { + Log.e(TAG, "Application not found for appId: " + appId); + return; + } + + try { + if (app.isRegistered && app.isLocal) { + app.mCallback.onExclusiveAccessChanged(setId, value, status, devices); + } else if (app.isRegistered && !app.isLocal) { + app.appCb.onExclusiveAccessChanged(setId, value, status, devices); + } + } catch (RemoteException e) { + Log.e(TAG, "Exception : " + e); + } + } + + /* Callback received when earlier denied lock is now available */ + protected void onLockAvailable (int appId, int setId, BluetoothDevice device) { + Log.d(TAG, "onExclusiveAccessAvailable: Remote(" + device + "), Group Id: " + + setId + ", App Id: " + appId); + + GroupAppMap.GroupClientApp app = mAppMap.getById(appId); + if (app == null) { + Log.e(TAG, "Application not found for appId: " + appId); + return; + } + + try { + if (app.isRegistered && app.isLocal) { + app.mCallback.onExclusiveAccessAvailable(setId, device); + } else if (app.isRegistered && !app.isLocal) { + app.appCb.onExclusiveAccessAvailable(setId, device); + } + } catch (RemoteException e) { + Log.e(TAG, "Exception : " + e); + } + + } + + /* Callback received when set size has been changed */ + /* TODO: Scanarios are unknown. Actions are to be decided */ + protected void onSetSizeChanged (int setId, int size, BluetoothDevice device) { + Log.d(TAG, "onGroupSizeChanged: Group Id: " + setId + ", New Size: " + size + + ", Notifying device: " + device); + + // TODO: Logic to be incorporated once use case is understood + } + + /* Callback received when set SIRK has been changed */ + /* TODO: Scanarios are unknown. Actions are to be decided */ + protected void onSetSirkChanged(int setId, byte[] sirk, BluetoothDevice device) { + Log.d(TAG, "onGroupIdChanged Group Id: " + setId + ", Notifying device: " + device); + + // TODO: Logic to be incorporated once use case is understood + } + +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/mcp/McpNativeInterface.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/mcp/McpNativeInterface.java new file mode 100644 index 00000000000..99c0d2985c7 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/mcp/McpNativeInterface.java @@ -0,0 +1,260 @@ +/****************************************************************************** + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + *****************************************************************************/ + + +package com.android.bluetooth.mcp; + +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; + +/** + * Mcp Native Interface to/from JNI. + */ +public class McpNativeInterface { + private static final String TAG = "McpNativeInterface"; + private static final boolean DBG = true; + private BluetoothAdapter mAdapter; + @GuardedBy("INSTANCE_LOCK") + private static McpNativeInterface sInstance; + private static final Object INSTANCE_LOCK = new Object(); + + static { + classInitNative(); + } + + private McpNativeInterface() { + mAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mAdapter == null) { + Log.wtfStack(TAG, "No Bluetooth Adapter Available"); + } + } + + /** + * Get singleton instance. + */ + public static McpNativeInterface getInstance() { + synchronized (INSTANCE_LOCK) { + if (sInstance == null) { + sInstance = new McpNativeInterface(); + } + return sInstance; + } + } + + /** + * Initializes the native interface. + * + * priorities to configure. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void init() { + initNative(); + } + + /** + * Cleanup the native interface. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void cleanup() { + cleanupNative(); + } + + + /** + * update MCP media supported feature + * @param feature + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean mediaControlPointOpcodeSupported(int feature) { + return mediaControlPointOpcodeSupportedNative(feature); + } + + /** + * update MCP media supported feature current value + * @param value + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean mediaControlPoint(int value) { + return mediaControlPointNative(value); + } + + /** + * Sets the Mcp media state + * @param state + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean mediaState(int state) { + return mediaStateNative(state); + } + + /** + * update MCP media player name + * @param player name + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean mediaPlayerName(String playeName) { + return mediaPlayerNameNative(playeName); + } + /** + * update track change notification + * @param track id + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean trackChanged(int status) { + return trackChangedNative(status); + } + /** + * update MCP track position + * @param position + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean trackPosition(int position) { + return trackPositionNative(position); + } + + /** + * update MCP track duration + * @param duration + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean trackDuration(int duration) { + return trackDurationNative(duration); + } + /** + * update MCP track title + * @param title + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean trackTitle(String title) { + return trackTitleNative(title); + } + /** + * update playing order support of media + * @param order + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean playingOrderSupported(int order) { + return playingOrderSupportedNative(order); + } + /** + * update playing order value of media + * @param value + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean playingOrder(int value) { + return playingOrderNative(value); + } + /** + * update active device + * @param device + * @param setId + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean setActiveDevice(BluetoothDevice device, int setId, int profile) { + return setActiveDeviceNative(profile, setId, getByteAddress(device)); + } + /** + * Sets Mcp media content control id + * @param ccid + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean contentControlId(int ccid) { + + return contentControlIdNative(ccid); + } + /** + * Disconnect Mcp disconnect device + * @param device + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean disconnectMcp(BluetoothDevice device) { + return disconnectMcpNative(getByteAddress(device)); + } + + /** + * Disconnect Mcp disconnect device + * @param device + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean bondStateChange(BluetoothDevice device, int state) { + return bondStateChangeNative(state, getByteAddress(device)); + } + + private BluetoothDevice getDevice(byte[] address) { + return mAdapter.getRemoteDevice(address); + } + + private byte[] getByteAddress(BluetoothDevice device) { + if (device == null) { + return Utils.getBytesFromAddress("00:00:00:00:00:00"); + } + return Utils.getBytesFromAddress(device.getAddress()); + } + + // Callbacks from the native stack back into the Java framework. + // All callbacks are routed via the Service which will disambiguate which + private void OnConnectionStateChanged(int state, byte[] address) { + if (DBG) { + Log.d(TAG, "OnConnectionStateChanged: " + state); + } + BluetoothDevice device = getDevice(address); + + McpService service = McpService.getMcpService(); + if (service != null) + service.onConnectionStateChanged(device, state); + } + + private void MediaControlPointChangedRequest(int state, byte[] address) { + BluetoothDevice device = getDevice(address); + if (DBG) { + Log.d(TAG, "MediaControlPointChangedReq: " + state); + } + McpService service = McpService.getMcpService(); + if (service != null) + service.onMediaControlPointChangeReq(device, state); + } + + private void TrackPositionChangedRequest(int position) { + if (DBG) { + Log.d(TAG, "TrackPositionChangedRequest: " + position); + } + McpService service = McpService.getMcpService(); + if (service != null) + service.onTrackPositionChangeReq(position); + } + + private void PlayingOrderChangedRequest(int order) { + if (DBG) { + Log.d(TAG, "PlayingOrderChangedRequest: " + order); + } + McpService service = McpService.getMcpService(); + if (service != null) + service.onPlayingOrderChangeReq(order); + } + + // Native methods that call into the JNI interface + private static native void classInitNative(); + private native void initNative(); + private native void cleanupNative(); + private native boolean mediaControlPointOpcodeSupportedNative(int feature); + private native boolean mediaControlPointNative(int value); + private native boolean mediaStateNative(int state); + private native boolean mediaPlayerNameNative(String playerName); + private native boolean trackChangedNative(int status); + private native boolean trackPositionNative(int position); + private native boolean trackDurationNative(int duration); + private native boolean trackTitleNative(String title); + private native boolean playingOrderSupportedNative(int order); + private native boolean playingOrderNative(int value); + private native boolean setActiveDeviceNative(int profile, int setId, byte[] address); + private native boolean contentControlIdNative(int ccid); + private native boolean disconnectMcpNative(byte[] address); + private native boolean bondStateChangeNative(int state, byte[] address); +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/mcp/McpService.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/mcp/McpService.java new file mode 100644 index 00000000000..1ae3884bb9b --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/mcp/McpService.java @@ -0,0 +1,828 @@ +/****************************************************************************** + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +package com.android.bluetooth.mcp; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import com.android.bluetooth.btservice.ProfileService; +import android.bluetooth.BluetoothUuid; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.ParcelUuid; +import android.os.SystemProperties; +import android.os.UserManager; +import android.util.Log; +import android.os.Message; +import android.os.Binder; +import android.os.IBinder; + +import com.android.bluetooth.BluetoothMetricsProto; +import com.android.bluetooth.Utils; +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.apm.ActiveDeviceManagerService; +import com.android.bluetooth.apm.ApmConst; +import com.android.bluetooth.acm.AcmService; +import com.android.bluetooth.btservice.MetricsLogger; +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.btservice.ServiceFactory; +import com.android.internal.annotations.VisibleForTesting; +import android.media.session.PlaybackState; +import android.media.MediaDescription; +import android.media.MediaMetadata; +import com.android.bluetooth.apm.MediaControlManager; +import android.view.KeyEvent; +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import android.media.session.MediaSessionManager; +import com.android.internal.util.ArrayUtils; +/** + * Provides Bluetooth MCP profile as a service in the Bluetooth application. + * @hide + */ +public class McpService extends ProfileService { + + private static final String TAG = "McpService"; + private static final boolean DBG = true; + public static final int MUSIC_PLAYER_CONTROL = 28; + private static McpService sMcpService; + private BroadcastReceiver mBondStateChangedReceiver; + + private BluetoothDevice mActiveDevice; + private AdapterService mAdapterService; + private McpNativeInterface mNativeInterface; + private static McpService sInstance = null; + private Context mContext; + private McsMessageHandler mHandler; + private int mMaxConnectedAudioDevices = 1; + private String mActiveMediaPlayerName = new String(""); + + public static final String ACTION_CONNECTION_STATE_CHANGED = + "com.android.bluetooth.mcp.action.CONNECTION_STATE_CHANGED"; + //native event + static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1; + static final int EVENT_TYPE_MEDIA_CONTROL_POINT_CHANGED = 2; + static final int EVENT_TYPE_TRACK_POSITION_CHANGED = 3; + static final int EVENT_TYPE_PLAYING_ORDER_CHANGED = 4; + //MCP to JNI update + static final int MEDIA_STATE_UPDATE = 5; + static final int MEDIA_CONTROL_POINT_OPCODES_SUPPORTED_UPDATE = 6; + static final int MEDIA_CONTROL_POINT_UPDATE = 7; + static final int MEDIA_PLAYER_NAME_UPDATE = 8; + static final int TRACK_CHANGED_UPDATE = 9; + static final int TRACK_TITLE_UPDATE = 10; + static final int TRACK_POSITION_UPDATE = 11; + static final int TRACK_DURATION_UPDATE = 12; + static final int PLAYING_ORDER_SUPPORT_UPDATE = 13; + static final int PLAYING_ORDER_UPDATE = 14; + static final int CONTENT_CONTROL_ID_UPDATE = 15; + static final int ACTIVE_DEVICE_CHANGE = 16; + static final int BOND_STATE_CHANGE = 17; + static final int MEDIA_CONTROL_MANAGER_INIT = 18; + + static final int PLAYSTATUS_ERROR = -1; + static final int PLAYSTATUS_STOPPED = 0; + static final int PLAYSTATUS_PLAYING = 1; + static final int PLAYSTATUS_PAUSED = 2; + static final int PLAYSTATUS_SEEK = 3; + + + //super set of supported player supported feature + static final int MCP_MEDIA_CONTROL_SUP_PLAY = 1<<0; + static final int MCP_MEDIA_CONTROL_SUP_PAUSE = 1<<1; + static final int MCP_MEDIA_CONTROL_SUP_FAST_REWIND = 1<<2; + static final int MCP_MEDIA_CONTROL_SUP_FAST_FORWARD = 1<<3; + static final int MCP_MEDIA_CONTROL_SUP_STOP = 1<<4; + static final int MCP_MEDIA_CONTROL_SUP_PREV_TRACK = 1<<11; + static final int MCP_MEDIA_CONTROL_SUP_NEXT_TRACK = 1<<12; + + //media control point opcodes + static final int MCP_MEDIA_CONTROL_OPCODE_PLAY = 0x01; + static final int MCP_MEDIA_CONTROL_OPCODE_PAUSE = 0x02; + static final int MCP_MEDIA_CONTROL_OPCODE_FAST_REWIND = 0x03; + static final int MCP_MEDIA_CONTROL_OPCODE_FAST_FORWARD = 0x04; + static final int MCP_MEDIA_CONTROL_OPCODE_STOP = 0x05; + static final int MCP_MEDIA_CONTROL_OPCODE_PREV_TRACK = 0x30; + static final int MCP_MEDIA_CONTROL_OPCODE_NEXT_TRACK = 0x31; + + //as there is not supported api to fetch player details + static final int DEFAULT_MEDIA_PLAYER_SUPPORTED_FEATURE = 0x181F; + static final int DEFAULT_PLAYER_SUPPORTED_FEATURE = 0x0001; // Single once default + static final int DEFAULT_PLAYING_ORDER = 0x01; // Single Once default + private int mState = -1; + private int mCurrOpCode = -1; + private int mSupportedControlPoint = -1; + private int mControlPoint = -1; + private int mSupportedPlayingOrder = -1; + private int mPlayingOrder = -1; + private int mCcid = -1; + private int mTrackPosition = 0xFFFF; + private int mTrackDuration = 0xFFFF; + private String mPlayerName = null; + private String mTrackTitle = null; + private MediaControlManager mMediaControlManager; + private MediaSessionManager mMediaSessionManager; + /*private class MusicPlayerDetail { + private int state; + private int featureSupported; + private int mSetFeature; + private int playingOrderFeatureSupported; + private int currentPlayingOrder; + private int ccid; + private int mTrackPosition; + private int currentTrackDuration; + private String playerName; + + public MusicPlayerDetail() { + + } + };*/ + //HashMap mMusicPlayerMap = new HashMap(); + @Override + protected IProfileServiceBinder initBinder() { + return new McpBinder(this); + } + + @Override + protected void create() { + Log.i(TAG, "create()"); + } + + @Override + protected void cleanup() { + Log.i(TAG, "cleanup()"); + } + + @Override + protected boolean start() { + Log.i(TAG, "start()"); + if (sMcpService != null) { + Log.w(TAG, "McpService is already running"); + return true; + } + if (DBG) { + Log.d(TAG, " Create McpService Instance"); + } + + mContext = this; + mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), + "AdapterService cannot be null when McpService starts"); + mNativeInterface = Objects.requireNonNull(McpNativeInterface.getInstance(), + "McpNativeInterface cannot be null when McpService starts"); + // Step 2: Get maximum number of connected audio devices + mMaxConnectedAudioDevices = mAdapterService.getMaxConnectedAudioDevices(); + Log.i(TAG, "Max connected audio devices set to " + mMaxConnectedAudioDevices); + //handle to synchronized tx and rx message + if (mHandler != null) { + mHandler = null; + } + HandlerThread thread = new HandlerThread("BluetoothMCSHandler"); + thread.start(); + Looper looper = thread.getLooper(); + mHandler = new McsMessageHandler(looper); + mNativeInterface.init(); + Log.d(TAG, "mcp native init done"); + IntentFilter filter = new IntentFilter(); + + filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + mBondStateChangedReceiver = new BondStateChangedReceiver(); + mContext.registerReceiver(mBondStateChangedReceiver, filter); + setMcpService(this); + mMediaSessionManager = (MediaSessionManager) this.getSystemService( + this.MEDIA_SESSION_SERVICE); + //MediaControlManager.make(this); + Message msg = mHandler.obtainMessage(); + msg.what = MEDIA_CONTROL_MANAGER_INIT; + msg.obj = this; + mHandler.sendMessageDelayed(msg, 100); + return true; + } + + @Override + protected boolean stop() { + Log.i(TAG, "stop()"); + if (sMcpService == null) { + Log.w(TAG, "stop() called before start()"); + return true; + } + // Step 8: Mark service as stopped + setMcpService(null); + // Cleanup native interface + mNativeInterface.cleanup(); + mNativeInterface = null; + mContext.unregisterReceiver(mBondStateChangedReceiver); + // Clear AdapterService + mAdapterService = null; + mMaxConnectedAudioDevices = 1; + return true; + } + + private static void setMcpService(McpService instance) { + if (DBG) { + Log.d(TAG, "setMcpService(): set to: " + instance); + } + sMcpService = instance; + } + /** + * Get the McpService instance + * @return McpService instance + */ + + public synchronized static McpService getMcpService() { + if (sMcpService == null) { + Log.w(TAG, "getMcpService(): service is null"); + return null; + } + return sMcpService; + } + + public synchronized static void clearMcpInstance () { + Log.v(TAG, "clearing MCP instatnce"); + sInstance = null; + Log.v(TAG, "After clearing MCP instatnce "); + } + + public synchronized boolean MediaControlPointOpcodeUpdate(int feature) { + Message msg = mHandler.obtainMessage(); + msg.what = MEDIA_CONTROL_POINT_OPCODES_SUPPORTED_UPDATE; + msg.arg1 = feature; + mHandler.sendMessage(msg); + return true; + } + + public synchronized boolean MediaControlPointUpdate(int value) { + Message msg = mHandler.obtainMessage(); + msg.what = MEDIA_CONTROL_POINT_UPDATE; + msg.arg1 = value; + mHandler.sendMessage(msg); + return true; + } + + private synchronized boolean MediaStateUpdate(int state) { + Message msg = mHandler.obtainMessage(); + msg.what = MEDIA_STATE_UPDATE; + msg.arg1 = state; + mHandler.sendMessage(msg); + return true; + } + + private synchronized boolean MediaPlayerNameUpdate(String name) { + Message msg = mHandler.obtainMessage(); + msg.what = MEDIA_PLAYER_NAME_UPDATE; + msg.obj = name; + mHandler.sendMessage(msg); + return true; + } + + private synchronized boolean PlayingOrderSupportedUpdate(int support) { + Message msg = mHandler.obtainMessage(); + msg.what = PLAYING_ORDER_SUPPORT_UPDATE; + msg.arg1 = support; + mHandler.sendMessage(msg); + return true; + } + + private synchronized boolean PlayingOrderUpdate(int support) { + Message msg = mHandler.obtainMessage(); + msg.what = PLAYING_ORDER_UPDATE; + msg.arg1 = support; + mHandler.sendMessage(msg); + return true; + } + + private synchronized boolean TrackChangedUpdate(int status) { + Message msg = mHandler.obtainMessage(); + msg.what = TRACK_CHANGED_UPDATE; + msg.arg1 = status; + mHandler.sendMessage(msg); + return true; + } + + private synchronized boolean TrackTitleUpdate(String title) { + Message msg = mHandler.obtainMessage(); + msg.what = TRACK_TITLE_UPDATE; + msg.obj = title; + mHandler.sendMessage(msg); + return true; + } + + private synchronized boolean TrackDurationUpdate(int duration) { + Message msg = mHandler.obtainMessage(); + msg.what = TRACK_DURATION_UPDATE; + msg.arg1 = duration; + mHandler.sendMessage(msg); + return true; + } + + private synchronized boolean TrackPositionUpdate(int position) { + Message msg = mHandler.obtainMessage(); + msg.what = TRACK_POSITION_UPDATE; + msg.arg1 = position; + mHandler.sendMessage(msg); + return true; + } + + private synchronized boolean ContentControlID(int ccid) { + Message msg = mHandler.obtainMessage(); + msg.what = CONTENT_CONTROL_ID_UPDATE; + msg.arg1 = ccid; + mHandler.sendMessage(msg); + return true; + } + + private synchronized int convertPlayStateToPlayStatus(PlaybackState state) { + int playStatus = PLAYSTATUS_ERROR; + switch (state.getState()) { + case PlaybackState.STATE_PLAYING: + playStatus = PLAYSTATUS_PLAYING; + break; + + case PlaybackState.STATE_CONNECTING: + case PlaybackState.STATE_NONE: + playStatus = PLAYSTATUS_STOPPED; + break; + + case PlaybackState.STATE_PAUSED: + case PlaybackState.STATE_BUFFERING: + case PlaybackState.STATE_STOPPED: + playStatus = PLAYSTATUS_PAUSED; + break; + + case PlaybackState.STATE_FAST_FORWARDING: + case PlaybackState.STATE_SKIPPING_TO_NEXT: + case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM: + case PlaybackState.STATE_REWINDING: + case PlaybackState.STATE_SKIPPING_TO_PREVIOUS: + playStatus = PLAYSTATUS_SEEK; + break; + + case PlaybackState.STATE_ERROR: + playStatus = PLAYSTATUS_ERROR; + break; + + } + return playStatus; + } + + + /** + * Get the active device. + * + * @return the active device or null if no device is active + */ + public synchronized BluetoothDevice getActiveDevice() { + return mActiveDevice; + } + + public synchronized int getControlContentID() { + int ccid = 1; + return ccid; + } + + public synchronized void onConnectionStateChanged(BluetoothDevice device, int status) { + Log.v(TAG, "onConnectionStateChanged: address=" + device.toString()); + if (status == 0) + return; + Message msg = mHandler.obtainMessage(); + msg.what = EVENT_TYPE_CONNECTION_STATE_CHANGED; + msg.obj = device; + msg.arg2 = status; + mHandler.sendMessage(msg); + return; + } + + public synchronized boolean onMediaControlPointChangeReq(BluetoothDevice device, int state) { + Message msg = mHandler.obtainMessage(); + msg.what = EVENT_TYPE_MEDIA_CONTROL_POINT_CHANGED; + msg.obj = device; + msg.arg1 = state; + mHandler.sendMessage(msg); + return true; + } + + public synchronized boolean onTrackPositionChangeReq(int position) { + Message msg = mHandler.obtainMessage(); + msg.what = EVENT_TYPE_TRACK_POSITION_CHANGED; + msg.arg1 = position; + mHandler.sendMessage(msg); + return true; + } + + public synchronized boolean onPlayingOrderChangeReq(int order) { + Message msg = mHandler.obtainMessage(); + msg.what = EVENT_TYPE_PLAYING_ORDER_CHANGED; + msg.arg1 = order; + mHandler.sendMessage(msg); + return true; + } + + public synchronized boolean SetActiveDevices(BluetoothDevice device, int profile) { + Message msg = mHandler.obtainMessage(); + msg.what = ACTIVE_DEVICE_CHANGE; + msg.obj = device; + msg.arg1 = 0; // it will use to send mark two earbud address in one gp + msg.arg2 = profile; + mHandler.sendMessage(msg); + return true; + } + + public synchronized boolean OnMediaPlayerUpdate(int feature, int state, + int playingOrderSupport, int playingOrder, String playerName) { + Log.w(TAG, "OnMediaPlayerUpdate for player " + playerName); + ContentControlID(getControlContentID()); + /* + if (mMusicPlayerMap.containsKey(playerName)) { + Log.v(TAG, "Player is already there"); + } else { + newPlayer = new MusicPlayerDetail(); + mMusicPlayerMap.add(newPlayer, playerName); + }*/ + + MediaPlayerNameUpdate(playerName); + MediaStateUpdate(state); + //added default value as there is no api for gettiting supported feature from player + MediaControlPointOpcodeUpdate(DEFAULT_MEDIA_PLAYER_SUPPORTED_FEATURE); + PlayingOrderSupportedUpdate(DEFAULT_PLAYER_SUPPORTED_FEATURE); + PlayingOrderUpdate(DEFAULT_PLAYING_ORDER); + return true; + } + + public synchronized boolean OnMediaStateUpdate(int state) { + Log.w(TAG, "OnMediaStateUpdate state " + state); + MediaStateUpdate(state); + return true; + } + + public synchronized boolean OnTrackUpdate(int status, int duration, String title) { + Log.w(TAG, "OnTrackUpdate title " + title + " duration " + duration); + TrackChangedUpdate(status); + if (status != 0) { + TrackTitleUpdate(title); + TrackDurationUpdate(duration); + } + return true; + } + + public synchronized boolean OnTrackPositionUpdate(int position) { + Log.w(TAG, "OnTrackPositionUpdate position " + position); + TrackPositionUpdate(position); + return true; + } + + public synchronized boolean OnPlayingOrderUpdate(int order) { + Log.w(TAG, "OnPlayingOrderUpdate order " + order); + PlayingOrderUpdate(order); + return true; + } + //As apm is not implemented callback for apm above mention function + //implemented workaround + public synchronized void updateMetaData(MediaMetadata data) { + Log.w(TAG, "updateMetaData data " + data); + if (data == null) { + return; + } + //length //TBD to convert into int + int duration = (int)data.getLong(MediaMetadata.METADATA_KEY_DURATION); + String title = data.getString(MediaMetadata.METADATA_KEY_TITLE); + if (title != null && !(title.equals(mTrackTitle))) { + TrackChangedUpdate(1); + TrackTitleUpdate(title); + } + if (duration != mTrackDuration) + TrackDurationUpdate(duration); + } + + public synchronized void updatePlaybackState(PlaybackState playbackState) { + Log.w(TAG, "updatePlaybackState state " + playbackState); + int state = (int)convertPlayStateToPlayStatus(playbackState); + + if (state != PLAYSTATUS_ERROR && mState != state) { + if (state == PLAYSTATUS_STOPPED) + state = PLAYSTATUS_PAUSED; + MediaStateUpdate(state); + if (mCurrOpCode != -1) { + MediaControlPointUpdate(mCurrOpCode); + mCurrOpCode = -1; + } + } + int position = (int)playbackState.getPosition(); + + if (position != mTrackPosition) + TrackPositionUpdate(position); + float speed = playbackState.getPlaybackSpeed(); //for playback speed + } + + public synchronized void updatePlayerName(String packageName, boolean removed) { + Log.w(TAG, "updatePlayerName pkg " + packageName + " removed " + removed); + String name = null; + boolean changed = true; + int tCcid = 0; + int tPlayersupport = 0; + int tMediasupport = 0; + if ((removed && packageName == null ) || + removed && packageName.equals(mPlayerName)) { + //no active media player + MediaStateUpdate(PLAYSTATUS_STOPPED); + name = new String(""); + } else if (packageName != null && !packageName.equals(mPlayerName)) { + name = packageName; + tCcid = getControlContentID(); + tPlayersupport = DEFAULT_PLAYER_SUPPORTED_FEATURE; + tMediasupport = DEFAULT_MEDIA_PLAYER_SUPPORTED_FEATURE; + } else { + Log.d(TAG, "player name is same no need to update " + packageName); + changed = false; + } + if (changed) { + Log.d(TAG, "sending player change update"); + MediaControlPointOpcodeUpdate(tMediasupport); + PlayingOrderSupportedUpdate(tPlayersupport); + MediaPlayerNameUpdate(name); + ContentControlID(tCcid); + } + } + + private int McpPassthroughToKeyCode(int operation) { + mCurrOpCode = operation; + switch (operation) { + case MCP_MEDIA_CONTROL_OPCODE_PLAY: + return KeyEvent.KEYCODE_MEDIA_PLAY; + case MCP_MEDIA_CONTROL_OPCODE_PAUSE: + return KeyEvent.KEYCODE_MEDIA_PAUSE; + case MCP_MEDIA_CONTROL_OPCODE_FAST_REWIND: + return KeyEvent.KEYCODE_MEDIA_REWIND; + case MCP_MEDIA_CONTROL_OPCODE_FAST_FORWARD: + return KeyEvent.KEYCODE_MEDIA_FAST_FORWARD; + case MCP_MEDIA_CONTROL_OPCODE_STOP: + return KeyEvent.KEYCODE_MEDIA_STOP; + case MCP_MEDIA_CONTROL_OPCODE_PREV_TRACK: + return KeyEvent.KEYCODE_MEDIA_PREVIOUS; + case MCP_MEDIA_CONTROL_OPCODE_NEXT_TRACK: + return KeyEvent.KEYCODE_MEDIA_NEXT; + + // Fallthrough for all unknown key mappings + default: + mCurrOpCode = -1; + Log.d(TAG, "unknown passthrough"); + return KeyEvent.KEYCODE_UNKNOWN; + } + } + + private class BondStateChangedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { + return; + } + int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.ERROR); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); + if (sInstance != null) + bondStateChanged(device, state); + } + } + + /** + * Process a change in the bonding state for a device. + * + * @param device the device whose bonding state has changed + * @param bondState the new bond state for the device. Possible values are: + * {@link BluetoothDevice#BOND_NONE}, + * {@link BluetoothDevice#BOND_BONDING}, + * {@link BluetoothDevice#BOND_BONDED}. + */ + @VisibleForTesting + void bondStateChanged(BluetoothDevice device, int bondState) { + if (DBG) { + Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState); + } + // Remove state machine if the bonding for a device is removed + if (bondState != BluetoothDevice.BOND_NONE) { + return; + } + //update to lower layer + Message msg = mHandler.obtainMessage(); + msg.what = BOND_STATE_CHANGE; + msg.obj = device; + msg.arg1 = bondState; + mHandler.sendMessage(msg); + return; + } + private boolean isMcpOnlyDevice(BluetoothDevice device) { + ParcelUuid ASCS_UUID = + ParcelUuid.fromString("0000184E-0000-1000-8000-00805F9B34FB"); + AdapterService adapterService = AdapterService.getAdapterService(); + boolean ascsSupported = + ArrayUtils.contains(adapterService.getRemoteUuids(device), ASCS_UUID); + AcmService mAcmService = AcmService.getAcmService(); + if (mAcmService != null) { + if (ascsSupported && + mAcmService.getConnectionState(device) == BluetoothProfile.STATE_CONNECTED) { + return false; + } + } + Log.d(TAG,"McpOnly device"); + return true; + } + /** Handles MCS messages. */ + private final class McsMessageHandler extends Handler { + private McsMessageHandler(Looper looper) { + super(looper); + } + @Override + public synchronized void handleMessage(Message msg) { + if (DBG) Log.v(TAG, "McsMessageHandler: received message=" + msg.what); + + switch (msg.what) { + + case ACTIVE_DEVICE_CHANGE: + if (DBG) Log.v(TAG, "ACTIVE_DEVICE_CHANGE msg: " + (BluetoothDevice)msg.obj + " msg2 : " + msg.arg1); + mActiveDevice = (BluetoothDevice)msg.obj; + mNativeInterface.setActiveDevice((BluetoothDevice)msg.obj, msg.arg1, msg.arg2); + break; + + case BOND_STATE_CHANGE: + if (DBG) Log.v(TAG, "BOND_STATE_CHANGE msg: " + (BluetoothDevice)msg.obj + " msg2 : " + msg.arg1); + mNativeInterface.bondStateChange((BluetoothDevice)msg.obj, msg.arg2); + break; + + case PLAYING_ORDER_SUPPORT_UPDATE: + if (DBG) Log.v(TAG, "PLAYING_ORDER_SUPPORT_UPDATE msg: " + msg.arg1); + mSupportedPlayingOrder = msg.arg1; + mNativeInterface.playingOrder(msg.arg1); + break; + + case PLAYING_ORDER_UPDATE: + if (DBG) Log.v(TAG, "PLAYING_ORDER_UPDATE msg: " + msg.arg1); + mPlayingOrder = msg.arg1; + mNativeInterface.playingOrder(msg.arg1); + break; + + case MEDIA_CONTROL_POINT_OPCODES_SUPPORTED_UPDATE: + if (DBG) Log.v(TAG, "MEDIA_CONTROL_POINT_OPCODES_SUPPORTED_UPDATE msg: " + msg.arg1); + mSupportedControlPoint = msg.arg1; + mNativeInterface.mediaControlPointOpcodeSupported(msg.arg1); + break; + + case MEDIA_CONTROL_POINT_UPDATE: + if (DBG) Log.v(TAG, "MEDIA_CONTROL_POINT_UPDATE msg: " + msg.arg1); + mControlPoint = msg.arg1; + mNativeInterface.mediaControlPoint(msg.arg1); + break; + + case MEDIA_PLAYER_NAME_UPDATE: + if (DBG) Log.v(TAG, "MEDIA_PLAYER_NAME_UPDATE msg: " + (String)msg.obj); + String name = (String)msg.obj; + mPlayerName = name; + if (name == null) + name = new String(""); + mNativeInterface.mediaPlayerName(name); + break; + + case MEDIA_STATE_UPDATE: + if (DBG) Log.v(TAG, "MEDIA_STATE_UPDATE msg: " + msg.arg1); + mState = msg.arg1; + mNativeInterface.mediaState(msg.arg1); + break; + + case TRACK_CHANGED_UPDATE: + if (DBG) Log.v(TAG, "TRACK_CHANGED_UPDATE msg: " + msg.arg1); + mNativeInterface.trackChanged(msg.arg1); + break; + + case TRACK_DURATION_UPDATE: + if (DBG) Log.v(TAG, "TRACK_DURATION_UPDATE msg: " + msg.arg1); + mTrackDuration = msg.arg1; + mNativeInterface.trackDuration(msg.arg1); + break; + + case TRACK_POSITION_UPDATE: + if (DBG) Log.v(TAG, "TRACK_POSITION_UPDATE msg: " + msg.arg1); + mTrackPosition = msg.arg1; + mNativeInterface.trackPosition(msg.arg1); + break; + + case TRACK_TITLE_UPDATE: + if (DBG) Log.v(TAG, "TRACK_TITLE_UPDATE msg: " + (String)msg.obj); + String title = (String)msg.obj; + mTrackTitle = title; + mNativeInterface.trackTitle(title); + break; + + + case CONTENT_CONTROL_ID_UPDATE: + if (DBG) Log.v(TAG, "CONTENT_CONTROL_ID_UPDATE msg: " + msg.arg1); + mCcid = msg.arg1; + mNativeInterface.contentControlId(mCcid); + break; + + case EVENT_TYPE_CONNECTION_STATE_CHANGED: + if (DBG) Log.v(TAG, "EVENT_TYPE_CONNECTION_STATE_CHANGED msg: " + msg.arg1); + //update to APM + break; + + case EVENT_TYPE_MEDIA_CONTROL_POINT_CHANGED: + if (DBG) Log.v(TAG, "EVENT_TYPE_MEDIA_CONTROL_POINT_CHANGED msg: " + msg.arg1); + BluetoothDevice mMcpDevice = (BluetoothDevice)msg.obj; + int code = McpPassthroughToKeyCode(msg.arg1); + + if (code != KeyEvent.KEYCODE_UNKNOWN) { + Log.w(TAG, "Valid passthrough, dispatch to media player"); + } + if (code == KeyEvent.KEYCODE_MEDIA_PLAY && + !Objects.equals(mMcpDevice, mActiveDevice) && !isMcpOnlyDevice(mMcpDevice)) { + ActiveDeviceManagerService mActiveDeviceManager = ActiveDeviceManagerService.get(); + if (mActiveDeviceManager != null) { + mActiveDeviceManager.setActiveDevice(mMcpDevice, ApmConst.AudioFeatures.MEDIA_AUDIO, false, true); + } + } + + // WAR- For FF/Rewind UC + if (code == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD || + code == KeyEvent.KEYCODE_MEDIA_REWIND) { + if (mState != PLAYSTATUS_SEEK) { + mState = PLAYSTATUS_SEEK; + MediaStateUpdate(mState); + MediaControlPointUpdate(mCurrOpCode); + mCurrOpCode = -1; + Log.w(TAG, "Update Playstate as seeking for FF/Rewind opcode"); + } + } else { + if (mState == PLAYSTATUS_SEEK) { // To-Do + } + } + + KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, code); + mMediaSessionManager.dispatchMediaKeyEvent(event, false); + event = new KeyEvent(KeyEvent.ACTION_UP, code); + mMediaSessionManager.dispatchMediaKeyEvent(event, false); + break; + + case EVENT_TYPE_PLAYING_ORDER_CHANGED: + if (DBG) Log.v(TAG, "EVENT_TYPE_PLAYING_ORDER_CHANGED msg: " + msg.arg1); + //update to APM + break; + + case EVENT_TYPE_TRACK_POSITION_CHANGED: + if (DBG) Log.v(TAG, "EVENT_TYPE_TRACK_POSITION_CHANGED msg: " + msg.arg1); + //update to APM + break; + + case MEDIA_CONTROL_MANAGER_INIT: + if (DBG) Log.v(TAG, "MEDIA_CONTROL_MANAGER_INIT"); + Context context = (Context)msg.obj; + MediaControlManager.make(context); + break; + + default: + Log.e(TAG, "unknown message! msg.what=" + msg.what); + break; + } + Log.v(TAG, "Exit handleMessage"); + } + } + + /** + * Binder object: must be a static class or memory leak may occur. + */ + + static class McpBinder extends Binder implements IProfileServiceBinder { + private McpService mService; + + private McpService getService() { + if (!Utils.checkCallerIsSystemOrActiveUser(TAG)) { + return null; + } + + if (mService != null && mService.isAvailable()) { + return mService; + } + return null; + } + + McpBinder(McpService svc) { + mService = svc; + } + + @Override + public synchronized void cleanup() { + mService = null; + } + } +} + + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PCService.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PCService.java new file mode 100644 index 00000000000..f0847716f34 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PCService.java @@ -0,0 +1,528 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * 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. + */ + +package com.android.bluetooth.pc; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothCodecConfig; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.ParcelUuid; +import android.os.SystemProperties; +import android.os.UserManager; +import android.util.Log; + +import com.android.bluetooth.BluetoothMetricsProto; +import com.android.bluetooth.Utils; +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.MetricsLogger; +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.btservice.ServiceFactory; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; + +public class PCService extends ProfileService{ + private static final String TAG = "PCService"; + private static final boolean DBG = true; + private static final int MAX_PACS_STATE_MACHINES = 50; + + private HandlerThread mStateMachinesThread; + private final HashMap mStateMachines = + new HashMap<>(); + private BroadcastReceiver mBondStateChangedReceiver; + + private AdapterService mAdapterService; + private PacsClientNativeInterface mNativeInterface; + private static PCService sInstance = null; + + public static final String ACTION_CONNECTION_STATE_CHANGED = + "com.android.bluetooth.pacs.action.CONNECTION_STATE_CHANGED"; + + /** + * Get the PCService instance. Returns null if the service hasn't been initialized. + */ + public static PCService get() { + return sInstance; + } + + @Override + protected IProfileServiceBinder initBinder() { + return null; + } + + @Override + protected void create() { + if (DBG) { + Log.d(TAG, "create()"); + } + } + + protected boolean start() { + + if (DBG) { + Log.d(TAG, "start()"); + } + if (sInstance != null) { + Log.w(TAG, "PCService is already running"); + return true; + } + + mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), + "AdapterService cannot be null when PCService starts"); + mNativeInterface = Objects.requireNonNull(PacsClientNativeInterface.getInstance(), + "PacsClientNativeInterface cannot be null when PCService starts"); + + // Start handler thread for state machines + mStateMachines.clear(); + mStateMachinesThread = new HandlerThread("PCService.StateMachines"); + mStateMachinesThread.start(); + mNativeInterface.init(); + sInstance = this; + + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + mBondStateChangedReceiver = new BondStateChangedReceiver(); + registerReceiver(mBondStateChangedReceiver, filter); + + return true; + } + + @Override + protected boolean stop() { + if (DBG) { + Log.d(TAG, "stop()"); + } + if (sInstance == null) { + Log.w(TAG, "stop() called before start()"); + return true; + } + + unregisterReceiver(mBondStateChangedReceiver); + + // Mark service as stopped + sInstance = null; + + // Destroy state machines and stop handler thread + synchronized (mStateMachines) { + for (PacsClientStateMachine sm : mStateMachines.values()) { + sm.doQuit(); + sm.cleanup(); + } + mStateMachines.clear(); + } + + if (mStateMachinesThread != null) { + mStateMachinesThread.quitSafely(); + mStateMachinesThread = null; + } + + // Cleanup native interface + mNativeInterface.cleanup(); + mNativeInterface = null; + + // Clear AdapterService + mAdapterService = null; + return true; + } + + @Override + protected void cleanup() { + if (DBG) { + Log.d(TAG, "cleanup()"); + } + } + + /** + * Get the PCService instance + * @return PCService instance + */ + public static synchronized PCService getPCService() { + if (sInstance == null) { + Log.w(TAG, "getPCService(): service is NULL"); + return null; + } + + return sInstance; + } + + /** + * Connects the pacs profile to the passed in device + * + * @param device is the device with which we will connect the pacs profile + * @return true if pacs profile successfully connected, false otherwise + */ + + public boolean connect(BluetoothDevice device) { + if (DBG) { + Log.d(TAG, "connect(): " + device); + } + if (device == null) { + return false; + } + + synchronized (mStateMachines) { + PacsClientStateMachine smConnect = getOrCreateStateMachine(device); + if (smConnect == null) { + Log.e(TAG, "Cannot connect to " + device + " : no state machine"); + return false; + } + smConnect.sendMessage(PacsClientStateMachine.CONNECT); + } + + return true; + } + + /** + * Disconnects pacs profile for the passed in device + * + * @param device is the device with which we want to disconnected the pacs profile + * @return true if pacs profile successfully disconnected, false otherwise + */ + + public boolean disconnect(BluetoothDevice device) { + if (DBG) { + Log.d(TAG, "disconnect(): " + device); + } + if (device == null) { + return false; + } + synchronized (mStateMachines) { + PacsClientStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + Log.w(TAG, "disconnect: device " + device + " not ever connected/connecting"); + return false; + } + int connectionState = stateMachine.getConnectionState(); + if (connectionState != BluetoothProfile.STATE_CONNECTED + && connectionState != BluetoothProfile.STATE_CONNECTING) { + Log.w(TAG, "disconnect: device " + device + + " not connected/connecting, connectionState=" + connectionState); + return false; + } + stateMachine.sendMessage(PacsClientStateMachine.DISCONNECT); + } + return true; + } + + /** + * start pacs disocvery for the passed in device + * + * @param device is the device with which we want to dicscoer the pacs + * @return true if pacs discovery is successfull, false otherwise + */ + + public boolean startPacsDiscovery(BluetoothDevice device) { + synchronized (mStateMachines) { + Log.i(TAG, "startPacsDiscovery: device=" + device + ", " + Utils.getUidPidString()); + final PacsClientStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + Log.w(TAG, "startPacsDiscovery: device " + device + " was never connected/connecting"); + return false; + } + if (stateMachine.getConnectionState() != BluetoothProfile.STATE_CONNECTED) { + Log.w(TAG, "startPacsDiscovery: profile not connected"); + return false; + } + stateMachine.sendMessage(PacsClientStateMachine.START_DISCOVERY); + } + return true; + } + + /** + * get sink pacs for the passed in device + * + * @param device is the device with which we want to get sink pacs + * @return sink pacs + */ + + public BluetoothCodecConfig[] getSinkPacs(BluetoothDevice device) { + synchronized (mStateMachines) { + final PacsClientStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + Log.e(TAG, "Failed to get Sink Pacs"); + return null; + } + return stateMachine.getSinkPacs(); + } + } + + /** + * get src pacs for the passed in device + * + * @param device is the device with which we want to get src pacs + * @return src pacs + */ + + public BluetoothCodecConfig[] getSrcPacs(BluetoothDevice device) { + synchronized (mStateMachines) { + final PacsClientStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + Log.e(TAG, "Failed to get Src Pacs"); + return null; + } + return stateMachine.getSinkPacs(); + } + } + + /** + * get sink locations for the passed in device + * + * @param device is the device with which we want to get sink location + * @return sink locations + */ + + public int getSinklocations(BluetoothDevice device) { + synchronized (mStateMachines) { + final PacsClientStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + Log.e(TAG, "Failed to get sink locations"); + return -1; + } + return stateMachine.getSinklocations(); + } + } + + /** + * get src locations for the passed in device + * + * @param device is the device with which we want to get src location + * @return src locations + */ + + public int getSrclocations(BluetoothDevice device) { + synchronized (mStateMachines) { + final PacsClientStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + Log.e(TAG, "Failed to get src locations"); + return -1; + } + return stateMachine.getSrclocations(); + } + } + + /** + * get available contexts for the passed in device + * + * @param device is the device with which we want to get available contexts + * @return avaialable contexts + */ + + public int getAvailableContexts(BluetoothDevice device) { + synchronized (mStateMachines) { + final PacsClientStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + Log.e(TAG, "Failed to get available contexts"); + return -1; + } + return stateMachine.getAvailableContexts(); + } + } + + /** + * get supported contexts for the passed in device + * + * @param device is the device with which we want to get supported contexts + * @return supported contexts + */ + + public int getSupportedContexts(BluetoothDevice device) { + synchronized (mStateMachines) { + final PacsClientStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + Log.e(TAG, "Failed to get supported contexts"); + return -1; + } + return stateMachine.getSupportedContexts(); + } + } + + /** + * Get the current connection state of the profile + * + * @param device is the remote bluetooth device + * @return {@link BluetoothProfile#STATE_DISCONNECTED} if this profile is disconnected, + * {@link BluetoothProfile#STATE_CONNECTING} if this profile is being connected, + * {@link BluetoothProfile#STATE_CONNECTED} if this profile is connected, or + * {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected + */ + public int getConnectionState(BluetoothDevice device) { + synchronized (mStateMachines) { + PacsClientStateMachine sm = mStateMachines.get(device); + if (sm == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + return sm.getConnectionState(); + } + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean okToConnect(BluetoothDevice device) { + + int bondState = mAdapterService.getBondState(device); + if (bondState != BluetoothDevice.BOND_BONDED) { + Log.w(TAG, "okToConnect: return false, bondState=" + bondState); + return false; + } + return true; + } + + void messageFromNative(PacsClientStackEvent stackEvent) { + Objects.requireNonNull(stackEvent.device, + "Device should never be null, event: " + stackEvent); + + synchronized (mStateMachines) { + BluetoothDevice device = stackEvent.device; + PacsClientStateMachine sm = mStateMachines.get(device); + if (sm == null) { + if (stackEvent.type == PacsClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { + switch (stackEvent.valueInt1) { + case PacsClientStackEvent.CONNECTION_STATE_CONNECTED: + case PacsClientStackEvent.CONNECTION_STATE_CONNECTING: + sm = getOrCreateStateMachine(device); + break; + default: + break; + } + } + } + if (sm == null) { + Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent); + return; + } + sm.sendMessage(PacsClientStateMachine.STACK_EVENT, stackEvent); + } + } + + void onConnectionStateChangedFromStateMachine(BluetoothDevice device, + int newState, int prevState) { + Log.d(TAG, "onConnectionStateChangedFromStateMachine for device: " + device + + " newState: " + newState); + + synchronized (mStateMachines) { + if (newState == BluetoothProfile.STATE_DISCONNECTED) { + int bondState = mAdapterService.getBondState(device); + if (bondState == BluetoothDevice.BOND_NONE) { + removeStateMachine(device); + } + } else if (newState == BluetoothProfile.STATE_CONNECTED) { + Log.d(TAG, "PacsClient get connected with renderer device: " + device); + } + } + } + + private class BondStateChangedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { + return; + } + int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.ERROR); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); + bondStateChanged(device, state); + } + } + + /** + * Process a change in the bonding state for a device. + * + * @param device the device whose bonding state has changed + * @param bondState the new bond state for the device. Possible values are: + * {@link BluetoothDevice#BOND_NONE}, + * {@link BluetoothDevice#BOND_BONDING}, + * {@link BluetoothDevice#BOND_BONDED}. + */ + @VisibleForTesting + void bondStateChanged(BluetoothDevice device, int bondState) { + if (DBG) { + Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState); + } + // Remove state machine if the bonding for a device is removed + if (bondState != BluetoothDevice.BOND_NONE) { + return; + } + + synchronized (mStateMachines) { + PacsClientStateMachine sm = mStateMachines.get(device); + if (sm == null) { + return; + } + if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { + return; + } + removeStateMachine(device); + } + } + + private void removeStateMachine(BluetoothDevice device) { + synchronized (mStateMachines) { + PacsClientStateMachine sm = mStateMachines.get(device); + if (sm == null) { + Log.w(TAG, "removeStateMachine: device " + device + + " does not have a state machine"); + return; + } + Log.i(TAG, "removeStateMachine: removing state machine for device: " + device); + sm.doQuit(); + sm.cleanup(); + mStateMachines.remove(device); + } + } + + private PacsClientStateMachine getOrCreateStateMachine(BluetoothDevice device) { + if (device == null) { + Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null"); + return null; + } + synchronized (mStateMachines) { + PacsClientStateMachine sm = mStateMachines.get(device); + if (sm != null) { + return sm; + } + if (mStateMachines.size() >= MAX_PACS_STATE_MACHINES) { + Log.e(TAG, "Maximum number of PACS state machines reached: " + + MAX_PACS_STATE_MACHINES); + return null; + } + if (DBG) { + Log.d(TAG, "Creating a new state machine for " + device); + } + sm = PacsClientStateMachine.make(device, this, + mNativeInterface, mStateMachinesThread.getLooper()); + mStateMachines.put(device, sm); + return sm; + } + } + +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientNativeInterface.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientNativeInterface.java new file mode 100644 index 00000000000..b7a4c516b15 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientNativeInterface.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * 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.pc; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothCodecConfig; +import android.util.Log; + +import com.android.bluetooth.Utils; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +/** + * PacsClient Native Interface to/from JNI. + */ +public class PacsClientNativeInterface { + private static final String TAG = "PacsClientNativeInterface"; + private static final boolean DBG = true; + private BluetoothAdapter mAdapter; + private int pacs_client_id = -1; + + @GuardedBy("INSTANCE_LOCK") + private static PacsClientNativeInterface sInstance; + private static final Object INSTANCE_LOCK = new Object(); + + static { + classInitNative(); + } + + private PacsClientNativeInterface() { + mAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mAdapter == null) { + Log.wtf(TAG, "No Bluetooth Adapter Available"); + } + } + + /** + * Get singleton instance. + */ + public static PacsClientNativeInterface getInstance() { + synchronized (INSTANCE_LOCK) { + if (sInstance == null) { + sInstance = new PacsClientNativeInterface(); + } + return sInstance; + } + } + + /** + * Initializes the native interface. + * + * priorities to configure. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void init() { + initNative(); + } + + /** + * Cleanup the native interface. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void cleanup() { + cleanupNative(pacs_client_id); + pacs_client_id = -1; + } + + /** + * Initiates PacsClient connection to a remote device. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean connectPacsClient(BluetoothDevice device) { + return connectPacsClientNative(pacs_client_id, getByteAddress(device)); + } + + /** + * Disconnects PacsClient from a remote device. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean disconnectPacsClient(BluetoothDevice device) { + return disconnectPacsClientNative(pacs_client_id, getByteAddress(device)); + } + + /** + * Trigger service discovery for pacs + * + * @param device the remote device + * @return true on success, otherwise false. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean startDiscoveryNative(BluetoothDevice device) { + return startDiscoveryNative(pacs_client_id, getByteAddress(device)); + } + + /** + * get available audio contexts. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean GetAvailableAudioContexts(BluetoothDevice device) { + return GetAvailableAudioContextsNative(pacs_client_id, getByteAddress(device)); + } + + private BluetoothDevice getDevice(byte[] address) { + if (mAdapter != null) { + return mAdapter.getRemoteDevice(address); + } else { + return null; + } + } + + 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(PacsClientStackEvent event) { + PCService service = PCService.getPCService(); + if (service != null) { + service.messageFromNative(event); + } else { + Log.e(TAG, "Event ignored, service not available: " + 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 OnInitialized(int state, int client_id) { + pacs_client_id = client_id; + } + + private void onConnectionStateChanged(byte[] address, int state) { + PacsClientStackEvent event = + new PacsClientStackEvent(PacsClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); + event.device = getDevice(address); + event.valueInt1 = state; + + if (DBG) { + Log.d(TAG, "onConnectionStateChanged: " + event); + } + sendMessageToService(event); + } + + private void OnAudioContextAvailable(byte[] address, int available_contexts) { + PacsClientStackEvent event = + new PacsClientStackEvent(PacsClientStackEvent.EVENT_TYPE_AUDIO_CONTEXT_AVAIL); + event.device = getDevice(address); + event.valueInt1 = available_contexts; + + if (DBG) { + Log.d(TAG, "OnAudioContextAvailable: " + event); + } + sendMessageToService(event); + } + + private void onServiceDiscovery(BluetoothCodecConfig[] sink_pacs_array, + BluetoothCodecConfig[] src_pacs_array, + int sink_locations, int src_locations, + int available_contexts, int supported_contexts, + int status, byte[] address) { + if (status != 0) { + Log.e(TAG, "onServiceDiscovery: Failed" + status); + return; + } + PacsClientStackEvent event = new PacsClientStackEvent( + PacsClientStackEvent.EVENT_TYPE_SERVICE_DISCOVERY); + event.device = getDevice(address); + event.sinkCodecConfig = sink_pacs_array; + event.srcCodecConfig = src_pacs_array; + event.valueInt1 = sink_locations; + event.valueInt2 = src_locations; + event.valueInt3 = available_contexts; + event.valueInt4 = supported_contexts; + if (DBG) { + Log.d(TAG, "onServiceDiscovery: " + event); + } + for (BluetoothCodecConfig codecConfig : + sink_pacs_array) { + Log.d(TAG, "sink_pacs_array: " + codecConfig); + } + for (BluetoothCodecConfig codecConfig : + src_pacs_array) { + Log.d(TAG, "src_pacs_array: " + codecConfig); + } + if (DBG) { + Log.d(TAG, "sink locs: " + sink_locations + "src locs:" + src_locations); + Log.d(TAG, "avail ctxts: " + available_contexts + "supp ctxts: " + supported_contexts); + } + + sendMessageToService(event); + } + + // Native methods that call into the JNI interface + private static native void classInitNative(); + private native void initNative(); + private native void cleanupNative(int client_id); + private native boolean connectPacsClientNative(int client_id, byte[] address); + private native boolean disconnectPacsClientNative(int client_id, byte[] address); + private native boolean startDiscoveryNative(int client_id, byte[] address); + private native boolean GetAvailableAudioContextsNative(int client_id, byte[] address); +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientStackEvent.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientStackEvent.java new file mode 100644 index 00000000000..ff1be613fd5 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientStackEvent.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * 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. + */ + +package com.android.bluetooth.pc; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothCodecConfig; + + +/** + * Stack event sent via a callback from JNI to Java, or generated + * internally by the Pacs Cleint State Machine. + */ +public class PacsClientStackEvent { + // Event types for STACK_EVENT message (coming from native) + private static final int EVENT_TYPE_NONE = 0; + public static final int EVENT_TYPE_INITIALIZED = 1; + public static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 2; + public static final int EVENT_TYPE_SERVICE_DISCOVERY = 3; + public static final int EVENT_TYPE_AUDIO_CONTEXT_AVAIL = 4; + + // Do not modify without updating the HAL bt_pacs_client.h files. + // Match up with enum class ConnectionState of bt_pacs_client.h. + static final int CONNECTION_STATE_DISCONNECTED = 0; + static final int CONNECTION_STATE_CONNECTING = 1; + static final int CONNECTION_STATE_CONNECTED = 2; + static final int CONNECTION_STATE_DISCONNECTING = 3; + + public int type; + public BluetoothDevice device; + public BluetoothCodecConfig[] sinkCodecConfig; + public BluetoothCodecConfig[] srcCodecConfig; + public int valueInt1; + public int valueInt2; + public int valueInt3; + public int valueInt4; + + PacsClientStackEvent(int type) { + this.type = type; + } + + @Override + public String toString() { + // event dump + StringBuilder result = new StringBuilder(); + result.append("PacsClientStackEvent {type:" + eventTypeToString(type)); + result.append(", device:" + device); + result.append(", value1:" + valueInt1); + result.append(", value2:" + valueInt2); + result.append(", value3:" + valueInt3); + result.append(", value4:" + valueInt4); + if (sinkCodecConfig != null) { + result.append(", sinkCodecConfig:" + sinkCodecConfig); + } + if (srcCodecConfig != null) { + result.append(", srcCodecConfig:" + srcCodecConfig); + } + result.append("}"); + return result.toString(); + } + + private static String eventTypeToString(int type) { + switch (type) { + case EVENT_TYPE_NONE: + return "EVENT_TYPE_NONE"; + case EVENT_TYPE_CONNECTION_STATE_CHANGED: + return "EVENT_TYPE_CONNECTION_STATE_CHANGED"; + case EVENT_TYPE_INITIALIZED: + return "EVENT_TYPE_INITIALIZED"; + case EVENT_TYPE_AUDIO_CONTEXT_AVAIL: + return "EVENT_TYPE_AUDIO_CONTEXT_AVAIL"; + case EVENT_TYPE_SERVICE_DISCOVERY: + return "EVENT_TYPE_SERVICE_DISCOVERY"; + default: + return "EVENT_TYPE_UNKNOWN:" + type; + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientStateMachine.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientStateMachine.java new file mode 100644 index 00000000000..23ff16eac5b --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientStateMachine.java @@ -0,0 +1,703 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * 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. + */ + +/** + * Bluetooth PacsClient StateMachine. There is one instance per remote device. + * - "Disconnected" and "Connected" are steady states. + * - "Connecting" and "Disconnecting" are transient states until the + * connection / disconnection is completed. + * + * + * (Disconnected) + * | ^ + * CONNECT | | DISCONNECTED + * V | + * (Connecting)<--->(Disconnecting) + * | ^ + * CONNECTED | | DISCONNECT + * V | + * (Connected) + * NOTES: + * - If state machine is in "Connecting" state and the remote device sends + * DISCONNECT request, the state machine transitions to "Disconnecting" state. + * - Similarly, if the state machine is in "Disconnecting" state and the remote device + * sends CONNECT request, the state machine transitions to "Connecting" state. + * + * DISCONNECT + * (Connecting) ---------------> (Disconnecting) + * <--------------- + * CONNECT + * + */ + +package com.android.bluetooth.pc; + +import static android.Manifest.permission.BLUETOOTH_CONNECT; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import com.android.bluetooth.Utils; +import android.bluetooth.BluetoothCodecConfig; +import android.content.Intent; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.content.Context; + +import com.android.bluetooth.btservice.ProfileService; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Scanner; + +final class PacsClientStateMachine extends StateMachine { + private static final boolean DBG = false; + private static final String TAG = "PacsClientStateMachine"; + + static final int CONNECT = 1; + static final int DISCONNECT = 2; + static final int START_DISCOVERY = 3; + static final int GET_AVAILABLE_CONTEXTS = 4; + @VisibleForTesting + static final int STACK_EVENT = 101; + private static final int CONNECT_TIMEOUT = 201; + + // NOTE: the value is not "final" - it is modified in the unit tests + @VisibleForTesting + static int sConnectTimeoutMs = 30000; // 30s + + private Disconnected mDisconnected; + private Connecting mConnecting; + private Disconnecting mDisconnecting; + private Connected mConnected; + private int mLastConnectionState = -1; + + private PCService mService; + private PacsClientNativeInterface mNativeInterface; + private BluetoothCodecConfig[] mSinkPacsConfig; + private BluetoothCodecConfig[] mSrcPacsConfig; + private int mSinkLocations; + private int mSrcLocations; + private int mAvailableContexts; + private int mSupportedContexts; + private Context mContext; + + private final BluetoothDevice mDevice; + + PacsClientStateMachine(BluetoothDevice device, PCService svc, + PacsClientNativeInterface nativeInterface, Looper looper) { + super(TAG, looper); + mDevice = device; + mService = svc; + mNativeInterface = nativeInterface; + + mDisconnected = new Disconnected(); + mConnecting = new Connecting(); + mDisconnecting = new Disconnecting(); + mConnected = new Connected(); + + addState(mDisconnected); + addState(mConnecting); + addState(mDisconnecting); + addState(mConnected); + + setInitialState(mDisconnected); + } + + static PacsClientStateMachine make(BluetoothDevice device, PCService svc, + PacsClientNativeInterface nativeInterface, Looper looper) { + Log.i(TAG, "make for device " + device); + PacsClientStateMachine PacsClientSm = new PacsClientStateMachine(device, svc, + nativeInterface, looper); + PacsClientSm.start(); + return PacsClientSm; + } + + public void doQuit() { + log("doQuit for device " + mDevice); + quitNow(); + } + + public void cleanup() { + log("cleanup for device " + mDevice); + } + + @VisibleForTesting + class Disconnected extends State { + @Override + public void enter() { + Log.i(TAG, "Enter Disconnected(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + + removeDeferredMessages(DISCONNECT); + + if (mLastConnectionState != -1) { + // Don't broadcast during startup + broadcastConnectionState(BluetoothProfile.STATE_DISCONNECTED, + mLastConnectionState); + } + cleanupDevice(); + } + + @Override + public void exit() { + Log.i(TAG, "Exit Disconnected(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_DISCONNECTED; + } + + @Override + public boolean processMessage(Message message) { + log("Disconnected process message(" + mDevice + "): " + messageWhatToString( + message.what)); + + switch (message.what) { + case CONNECT: + log("Connecting to " + mDevice); + if (!mNativeInterface.connectPacsClient(mDevice)) { + Log.e(TAG, "Disconnected: error connecting to " + mDevice); + break; + } + if (mService.okToConnect(mDevice)) { + transitionTo(mConnecting); + } else { + // Reject the request and stay in Disconnected state + Log.w(TAG, "Outgoing PacsClient Connecting request rejected: " + mDevice); + } + break; + case DISCONNECT: + Log.w(TAG, "Disconnected: DISCONNECT ignored: " + mDevice); + break; + case STACK_EVENT: + PacsClientStackEvent event = (PacsClientStackEvent) message.obj; + if (DBG) { + Log.d(TAG, "Disconnected: stack event: " + event); + } + if (!mDevice.equals(event.device)) { + Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case PacsClientStackEvent.EVENT_TYPE_INITIALIZED: + if(event.valueInt1 != 0) { + Log.e(TAG, "Disconnected: error initializing PACS"); + return NOT_HANDLED; + } + Log.d(TAG, "PACS Initialized succesfully (DISCONNECTED)"); + break; + case PacsClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1); + break; + default: + Log.e(TAG, "Disconnected: ignoring stack event: " + event); + break; + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Disconnected state + private void processConnectionEvent(int state) { + switch (state) { + case PacsClientStackEvent.CONNECTION_STATE_DISCONNECTED: + Log.w(TAG, "Ignore PacsClient DISCONNECTED event: " + mDevice); + break; + case PacsClientStackEvent.CONNECTION_STATE_CONNECTING: + if (mService.okToConnect(mDevice)) { + Log.i(TAG, "Incoming PacsClient Connecting request accepted: " + mDevice + + "state: " + state); + transitionTo(mConnecting); + } else { + // Reject the connection and stay in Disconnected state itself + Log.w(TAG, "Incoming PacsClient Connecting request rejected: " + mDevice + + "state: " + state); + mNativeInterface.disconnectPacsClient(mDevice); + } + break; + case PacsClientStackEvent.CONNECTION_STATE_CONNECTED: + Log.w(TAG, "PacsClient Connected from Disconnected state: " + mDevice + + "state: " + state); + if (mService.okToConnect(mDevice)) { + Log.i(TAG, "Incoming PacsClient Connected request accepted: " + mDevice + + "state: " + state); + transitionTo(mConnected); + } else { + // Reject the connection and stay in Disconnected state itself + Log.w(TAG, "Incoming PacsClient Connected request rejected: " + mDevice + + "state: " + state); + mNativeInterface.disconnectPacsClient(mDevice); + } + break; + case PacsClientStackEvent.CONNECTION_STATE_DISCONNECTING: + Log.w(TAG, "Ignore PacsClient DISCONNECTING event: " + mDevice + + "state: " + state); + break; + default: + Log.e(TAG, "Incorrect state: " + state + " device: " + mDevice + + "state: " + state); + break; + } + } + } + + @VisibleForTesting + class Connecting extends State { + @Override + public void enter() { + Log.i(TAG, "Enter Connecting(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs); + broadcastConnectionState(BluetoothProfile.STATE_CONNECTING, mLastConnectionState); + } + + @Override + public void exit() { + Log.i(TAG, "Exit Connecting(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_CONNECTING; + removeMessages(CONNECT_TIMEOUT); + } + + @Override + public boolean processMessage(Message message) { + log("Connecting process message(" + mDevice + "): " + + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: + deferMessage(message); + break; + case CONNECT_TIMEOUT: + Log.w(TAG, "Connecting connection timeout: " + mDevice); + mNativeInterface.disconnectPacsClient(mDevice); + PacsClientStackEvent disconnectEvent = + new PacsClientStackEvent( + PacsClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); + disconnectEvent.device = mDevice; + disconnectEvent.valueInt1 = PacsClientStackEvent.CONNECTION_STATE_DISCONNECTED; + sendMessage(STACK_EVENT, disconnectEvent); + break; + case DISCONNECT: + log("Connecting: connection canceled to " + mDevice); + mNativeInterface.disconnectPacsClient(mDevice); + transitionTo(mDisconnected); + break; + case START_DISCOVERY: + case GET_AVAILABLE_CONTEXTS: + deferMessage(message); + break; + case STACK_EVENT: + PacsClientStackEvent event = (PacsClientStackEvent) message.obj; + log("Connecting: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case PacsClientStackEvent.EVENT_TYPE_INITIALIZED: + if(event.valueInt1 != 0) { + Log.e(TAG, "Disconnected: error initializing PACS"); + return NOT_HANDLED; + } + Log.d(TAG, "PACS Initialized succesfully (CONNECTING)"); + break; + case PacsClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1); + break; + case PacsClientStackEvent.EVENT_TYPE_SERVICE_DISCOVERY: + case PacsClientStackEvent.EVENT_TYPE_AUDIO_CONTEXT_AVAIL: + deferMessage(message); + break; + default: + Log.e(TAG, "Disconnected: ignoring stack event: " + event); + break; + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Connecting state + private void processConnectionEvent(int state) { + switch (state) { + case PacsClientStackEvent.CONNECTION_STATE_DISCONNECTED: + Log.w(TAG, "Connecting device disconnected: " + mDevice); + transitionTo(mDisconnected); + break; + case PacsClientStackEvent.CONNECTION_STATE_CONNECTED: + transitionTo(mConnected); + break; + case PacsClientStackEvent.CONNECTION_STATE_CONNECTING: + break; + case PacsClientStackEvent.CONNECTION_STATE_DISCONNECTING: + Log.w(TAG, "Connecting interrupted: device is disconnecting: " + mDevice); + transitionTo(mDisconnecting); + break; + default: + Log.e(TAG, "Incorrect state: " + state); + break; + } + } + } + + @VisibleForTesting + class Disconnecting extends State { + @Override + public void enter() { + Log.i(TAG, "Enter Disconnecting(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + sendMessageDelayed(CONNECT_TIMEOUT, sConnectTimeoutMs); + broadcastConnectionState(BluetoothProfile.STATE_DISCONNECTING, mLastConnectionState); + } + + @Override + public void exit() { + Log.i(TAG, "Exit Disconnecting(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING; + removeMessages(CONNECT_TIMEOUT); + } + + @Override + public boolean processMessage(Message message) { + log("Disconnecting process message(" + mDevice + "): " + + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: + deferMessage(message); + break; + case CONNECT_TIMEOUT: { + Log.w(TAG, "Disconnecting connection timeout: " + mDevice); + mNativeInterface.disconnectPacsClient(mDevice); + PacsClientStackEvent disconnectEvent = + new PacsClientStackEvent( + PacsClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); + disconnectEvent.device = mDevice; + disconnectEvent.valueInt1 = PacsClientStackEvent.CONNECTION_STATE_DISCONNECTED; + sendMessage(STACK_EVENT, disconnectEvent); + break; + } + case START_DISCOVERY: + case GET_AVAILABLE_CONTEXTS: + case DISCONNECT: + deferMessage(message); + break; + case STACK_EVENT: + PacsClientStackEvent event = (PacsClientStackEvent) message.obj; + log("Disconnecting: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case PacsClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1); + break; + case PacsClientStackEvent.EVENT_TYPE_SERVICE_DISCOVERY: + case PacsClientStackEvent.EVENT_TYPE_AUDIO_CONTEXT_AVAIL: + deferMessage(message); + break; + default: + Log.e(TAG, "Disconnected: ignoring stack event: " + event); + break; + } + + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Disconnecting state + private void processConnectionEvent(int state) { + switch (state) { + case PacsClientStackEvent.CONNECTION_STATE_DISCONNECTED: + Log.i(TAG, "Disconnected: " + mDevice); + transitionTo(mDisconnected); + break; + case PacsClientStackEvent.CONNECTION_STATE_CONNECTED: + if (mService.okToConnect(mDevice)) { + Log.w(TAG, "Disconnecting interrupted: device is connected: " + mDevice); + transitionTo(mConnected); + } else { + // Reject the connection and stay in Disconnecting state + Log.w(TAG, "Incoming PacsClient Connected request rejected: " + mDevice); + mNativeInterface.disconnectPacsClient(mDevice); + } + break; + case PacsClientStackEvent.CONNECTION_STATE_CONNECTING: + if (mService.okToConnect(mDevice)) { + Log.i(TAG, "Disconnecting interrupted: try to reconnect: " + mDevice); + transitionTo(mConnecting); + } else { + // Reject the connection and stay in Disconnecting state + Log.w(TAG, "Incoming PacsClient Connecting request rejected: " + mDevice); + mNativeInterface.disconnectPacsClient(mDevice); + } + break; + case PacsClientStackEvent.CONNECTION_STATE_DISCONNECTING: + break; + default: + Log.e(TAG, "Incorrect state: " + state); + break; + } + } + } + + @VisibleForTesting + class Connected extends State { + @Override + public void enter() { + Log.i(TAG, "Enter Connected(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + removeDeferredMessages(CONNECT); + mNativeInterface.startDiscoveryNative(mDevice); + broadcastConnectionState(BluetoothProfile.STATE_CONNECTED, mLastConnectionState); + } + + @Override + public void exit() { + Log.i(TAG, "Exit Connected(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_CONNECTED; + } + + @Override + public boolean processMessage(Message message) { + log("Connected process message(" + mDevice + "): " + + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: + Log.w(TAG, "Connected: CONNECT ignored: " + mDevice); + break; + case DISCONNECT: + log("Disconnecting from " + mDevice); + if (!mNativeInterface.disconnectPacsClient(mDevice)) { + // If error in the native stack, transition directly to Disconnected state. + Log.e(TAG, "Connected: error disconnecting from " + mDevice); + transitionTo(mDisconnected); + break; + } + transitionTo(mDisconnecting); + break; + case START_DISCOVERY: + log("sending start discovery to " + mDevice); + if (!mNativeInterface.startDiscoveryNative(mDevice)) { + Log.e(TAG, "connected: error sending startdiscovery to " + mDevice); + } + break; + case GET_AVAILABLE_CONTEXTS: + log("get available audio conxtes from " + mDevice); + mNativeInterface.GetAvailableAudioContexts(mDevice); + break; + case STACK_EVENT: + PacsClientStackEvent event = (PacsClientStackEvent) message.obj; + log("Connected: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtf(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case PacsClientStackEvent.EVENT_TYPE_INITIALIZED: + deferMessage(message); + break; + case PacsClientStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1); + break; + case PacsClientStackEvent.EVENT_TYPE_SERVICE_DISCOVERY: + processPacsRecordEvent(event.sinkCodecConfig, event.srcCodecConfig, + event.valueInt1, event.valueInt2, + event.valueInt3, event.valueInt4); + break; + case PacsClientStackEvent.EVENT_TYPE_AUDIO_CONTEXT_AVAIL: + mAvailableContexts = event.valueInt1; + break; + default: + Log.e(TAG, "Disconnected: ignoring stack event: " + event); + break; + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Connected state + private void processConnectionEvent(int state) { + switch (state) { + case PacsClientStackEvent.CONNECTION_STATE_DISCONNECTED: + Log.i(TAG, "Disconnected from " + mDevice); + transitionTo(mDisconnected); + break; + case PacsClientStackEvent.CONNECTION_STATE_DISCONNECTING: + Log.i(TAG, "Disconnecting from " + mDevice); + transitionTo(mDisconnecting); + break; + default: + Log.e(TAG, "Connection State Device: " + mDevice + " bad state: " + state); + break; + } + } + + private void processPacsRecordEvent(BluetoothCodecConfig[] sinkCodecConfig, + BluetoothCodecConfig[] srcCodecConfig, + int sink_locations, int src_locations, + int available_contexts, int supported_contexts) { + mSinkPacsConfig = sinkCodecConfig; + mSrcPacsConfig = srcCodecConfig; + mSinkLocations = sink_locations; + mSrcLocations = src_locations; + mAvailableContexts = available_contexts; + mSupportedContexts = supported_contexts; + } + } + + int getConnectionState() { + String currentState = getCurrentState().getName(); + switch (currentState) { + case "Disconnected": + return BluetoothProfile.STATE_DISCONNECTED; + case "Connecting": + return BluetoothProfile.STATE_CONNECTING; + case "Connected": + return BluetoothProfile.STATE_CONNECTED; + case "Disconnecting": + return BluetoothProfile.STATE_DISCONNECTING; + default: + Log.e(TAG, "Bad currentState: " + currentState); + return BluetoothProfile.STATE_DISCONNECTED; + } + } + + BluetoothDevice getDevice() { + return mDevice; + } + + synchronized boolean isConnected() { + return getCurrentState() == mConnected; + } + + + private void cleanupDevice() { + log("cleanup device " + mDevice); + mSinkLocations = -1; + mSrcLocations = -1; + mAvailableContexts = -1; + mSupportedContexts = -1; + } + + BluetoothCodecConfig[] getSinkPacs() { + synchronized (this) { + return mSinkPacsConfig; + } + } + + BluetoothCodecConfig[] getSrcPacs() { + synchronized (this) { + return mSrcPacsConfig; + } + } + + int getSinklocations() { + synchronized (this) { + return mSinkLocations; + } + } + + int getSrclocations() { + synchronized (this) { + return mSrcLocations; + } + } + + int getAvailableContexts() { + synchronized (this) { + return mAvailableContexts; + } + } + + int getSupportedContexts() { + synchronized (this) { + return mSupportedContexts; + } + } + + // This method does not check for error condition (newState == prevState) + private void broadcastConnectionState(int newState, int prevState) { + log("Connection state " + mDevice + ": " + profileStateToString(prevState) + + "->" + profileStateToString(newState)); + mService.onConnectionStateChangedFromStateMachine(mDevice, newState, prevState); + Intent intent = new Intent(PCService.ACTION_CONNECTION_STATE_CHANGED); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); + intent.putExtra(BluetoothProfile.EXTRA_STATE, newState); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mService.sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); + } + + private static String messageWhatToString(int what) { + switch (what) { + case CONNECT: + return "CONNECT"; + case DISCONNECT: + return "DISCONNECT"; + case STACK_EVENT: + return "STACK_EVENT"; + case CONNECT_TIMEOUT: + return "CONNECT_TIMEOUT"; + default: + break; + } + return Integer.toString(what); + } + + private static String profileStateToString(int state) { + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return "DISCONNECTED"; + case BluetoothProfile.STATE_CONNECTING: + return "CONNECTING"; + case BluetoothProfile.STATE_CONNECTED: + return "CONNECTED"; + case BluetoothProfile.STATE_DISCONNECTING: + return "DISCONNECTING"; + default: + break; + } + return Integer.toString(state); + } + + @Override + protected void log(String msg) { + if (DBG) { + super.log(msg); + } + } +} diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpController.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpController.java new file mode 100644 index 00000000000..ab29aa03178 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpController.java @@ -0,0 +1,733 @@ +/* + *Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * 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. + */ + +package com.android.bluetooth.vcp; + +import static android.Manifest.permission.BLUETOOTH_CONNECT; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.bluetooth.BluetoothVcp; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.ParcelUuid; +import android.os.SystemProperties; +import android.os.UserManager; +import android.util.Log; + +import com.android.bluetooth.apm.ApmConst; +import com.android.bluetooth.apm.DeviceProfileMap; +import com.android.bluetooth.apm.VolumeManager; +import com.android.bluetooth.acm.AcmService; +import com.android.bluetooth.BluetoothMetricsProto; +import com.android.bluetooth.Utils; +import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.btservice.MetricsLogger; +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.btservice.ServiceFactory; +import com.android.bluetooth.groupclient.GroupService; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; + +import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; + +public class VcpController { + private static final String TAG = "VcpController"; + private static final boolean DBG = true; + private static final int MAX_VCP_STATE_MACHINES = 50; + private static final int VCP_MIN_VOL = 0; + private static final int VCP_MAX_VOL = 255; + private static final String ACTION_CONNECT_DEVICE = + "com.android.bluetooth.vcp.test.action.CONNECT_DEVICE"; + private static final String ACTION_DISCONNECT_DEVICE = + "com.android.bluetooth.vcp.test.action.DISCONNECT_DEVICE"; + + private HandlerThread mStateMachinesThread; + private final HashMap mStateMachines = + new HashMap<>(); + private HashMap mConnectionMode = new HashMap(); + private BroadcastReceiver mBondStateChangedReceiver; + + private AdapterService mAdapterService; + private VcpControllerNativeInterface mNativeInterface; + private DeviceProfileMap mDpm; + private AcmService mAcmService; + private static VcpController sInstance = null; + private Context mContext; + private boolean mPtsTest = false; + private final BroadcastReceiver mVcpControllerTestReceiver = new VcpControllerTestReceiver(); + + private VcpController(Context context) { + if (DBG) { + Log.d(TAG, "Create VcpController Instance"); + } + + mContext = context; + mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), + "AdapterService cannot be null when VcpController starts"); + mNativeInterface = Objects.requireNonNull(VcpControllerNativeInterface.getInstance(), + "VcpControllerNativeInterface cannot be null when VcpController starts"); + + // Start handler thread for state machines + mStateMachines.clear(); + mStateMachinesThread = new HandlerThread("VcpController.StateMachines"); + mStateMachinesThread.start(); + mNativeInterface.init(); + + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + mBondStateChangedReceiver = new BondStateChangedReceiver(); + mContext.registerReceiver(mBondStateChangedReceiver, filter); + + if (mAdapterService.isAdvBCAAudioFeatEnabled()) { + Log.d(TAG, "Adv BCA Audio supported, enable VCP for broadcast"); + SystemProperties.set("persist.vendor.service.bt.vcpForBroadcast", "true"); + } else { + SystemProperties.set("persist.vendor.service.bt.vcpForBroadcast", "false"); + } + mPtsTest = SystemProperties.getBoolean("persist.vendor.service.bt.vcp_controller.pts", false); + if (mPtsTest) { + Log.d(TAG, "Register for VcpControllerTestReceiver"); + IntentFilter filter2 = new IntentFilter(); + filter2.addAction(ACTION_CONNECT_DEVICE); + filter2.addAction(ACTION_DISCONNECT_DEVICE); + context.registerReceiver(mVcpControllerTestReceiver, filter2); + } + } + + /** + * Make VcpController instance and Initialize + * + * @param context: application context + * @return VcpController instance + */ + public static VcpController make(Context context) { + Log.v(TAG, "make"); + + if(sInstance == null) { + sInstance = new VcpController(context); + } + Log.v(TAG, "Exit make"); + return sInstance; + } + + /** + * Get the VcpController instance, which provides the public APIs + * to volume control operation via VCP connection + * + * @return VcpController instance + */ + public static synchronized VcpController getVcpController() { + if (sInstance == null) { + Log.w(TAG, "getVcpController(): service is NULL"); + return null; + } + + return sInstance; + } + + public static void clearVcpInstance () { + Log.v(TAG, "clearing VCP instatnce"); + sInstance = null; + Log.v(TAG, "After clearing VCP instatnce "); + } + + public synchronized void doQuit() { + if (DBG) { + Log.d(TAG, "doQuit()"); + } + if (sInstance == null) { + Log.w(TAG, "doQuit() called before make()"); + return; + } + + // Cleanup native interface + mNativeInterface.cleanup(); + mNativeInterface = null; + mContext.unregisterReceiver(mBondStateChangedReceiver); + if (mPtsTest) { + mContext.unregisterReceiver(mVcpControllerTestReceiver); + } + + // Mark service as stopped + sInstance = null; + + // Destroy state machines and stop handler thread + synchronized (mStateMachines) { + for (VcpControllerStateMachine sm : mStateMachines.values()) { + sm.doQuit(); + sm.cleanup(); + } + mStateMachines.clear(); + } + + if (mStateMachinesThread != null) { + mStateMachinesThread.quitSafely(); + mStateMachinesThread = null; + } + + // Clear AdapterService + mAdapterService = null; + } + + /** + * Connect with the remote device for unicast or broadcast mode. + * + * @param device: the remote device to connect + * @param mode: connection mode: can be any of + * {@link #BluetoothVcp.MODE_UNICAST} or {@link #BluetoothVcp.MODE_BROADCAST} + * + * @return true if connect is accepted, false if connect request is rejected. + */ + public boolean connect(BluetoothDevice device, int mode) { + if (DBG) { + Log.d(TAG, "connect(): " + device + ", mode: " + mode); + } + if (device == null) { + return false; + } + + synchronized (mStateMachines) { + VcpControllerStateMachine smConnect = getOrCreateStateMachine(device); + if (smConnect == null) { + Log.e(TAG, "Cannot connect to " + device + " : no state machine"); + } + + int preConnMode; + if (mConnectionMode.containsKey(device)) { + preConnMode = mConnectionMode.get(device); + if ((preConnMode & mode) == 0) { + int connMode = preConnMode | mode; + mConnectionMode.put(device, connMode); + broadcastConnectionModeChanged(device, connMode); + } + } else { + preConnMode = BluetoothVcp.MODE_NONE; + mConnectionMode.put(device, mode); + broadcastConnectionModeChanged(device, mode); + } + + if (smConnect.getConnectionState() != BluetoothProfile.STATE_CONNECTED) { + smConnect.sendMessage(VcpControllerStateMachine.CONNECT, device); + } else { + if (preConnMode == BluetoothVcp.MODE_BROADCAST && + mode == BluetoothVcp.MODE_UNICAST) { + Log.d(TAG, "VCP connection from BROADCAST-ONLY to UNICAST_BROADCAST: " + device); + } + } + } + + return true; + } + + /** + * Disconnect with the remote device for unicast or broadcast mode. + * + * @param device: the remote device to connect + * @param mode: connection mode: can be any of + * {@link #BluetoothVcp.MODE_UNICAST} or {@link #BluetoothVcp.MODE_BROADCAST} + * + * @return true if disconnect is accepted, false if disconnect is rejected. + */ + public boolean disconnect(BluetoothDevice device, int mode) { + if (DBG) { + Log.d(TAG, "disconnect(): " + device + ", mode: " + mode); + } + if (device == null) { + return false; + } + + synchronized (mStateMachines) { + int preConnMode = getConnectionMode(device); + int connMode = BluetoothVcp.MODE_NONE; + + if ((preConnMode & mode) != 0) { + connMode = preConnMode & ~mode; + broadcastConnectionModeChanged(device, connMode); + } else { + Log.d(TAG, "disconnect ignore as Vcp is not connected for mode: " + mode); + return false; + } + + if (connMode == BluetoothVcp.MODE_NONE) { + mConnectionMode.remove(device); + VcpControllerStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + Log.w(TAG, "disconnect: device " + device + " not ever connected/connecting"); + return false; + } + int connectionState = stateMachine.getConnectionState(); + if (connectionState != BluetoothProfile.STATE_CONNECTED + && connectionState != BluetoothProfile.STATE_CONNECTING) { + Log.w(TAG, "disconnect: device " + device + + " not connected/connecting, connectionState=" + connectionState); + return false; + } + stateMachine.sendMessage(VcpControllerStateMachine.DISCONNECT, device); + } else { + mConnectionMode.put(device, connMode); + } + } + return true; + } + + /** + * Set absolute volume to remote device via VCP connection + * + * @param device: remote device instance + * @param volume: requested volume settings for remote device + * @return true if set abs volume requst is accepted, false if set + * abs volume request is rejected + */ + public boolean setAbsoluteVolume(BluetoothDevice device, int volume, int audioType) { + synchronized (mStateMachines) { + Log.i(TAG, "setAbsVolume: device=" + device + ", " + Utils.getUidPidString()); + final VcpControllerStateMachine stateMachine = mStateMachines.get(device); + + if (stateMachine == null) { + Log.w(TAG, "setAbsVolume: device " + device + " was never connected/connecting"); + return false; + } + + if (stateMachine.getConnectionState() != BluetoothProfile.STATE_CONNECTED) { + Log.w(TAG, "setAbsVolume: profile not connected"); + return false; + } + + stateMachine.sendMessage(VcpControllerStateMachine.SET_VOLUME, volume, audioType, device); + } + return true; + } + + /** + * Mute or unmute remote device via VCP connection + * + * @param device: remote device instance + * @param enableMute: true if mute, false if unmute + * @return true if mute requst is accepted, false if mute + * request is rejected + */ + public boolean setMute(BluetoothDevice device, boolean enableMute) { + synchronized (mStateMachines) { + Log.i(TAG, "setMute: device=" + device + ", " + "enableMute: " + enableMute); + final VcpControllerStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + Log.w(TAG, "setMute: device " + device + " was never connected/connecting"); + return false; + } + if (stateMachine.getConnectionState() != BluetoothProfile.STATE_CONNECTED) { + Log.w(TAG, "setMute: profile not connected"); + return false; + } + if (enableMute) { + stateMachine.sendMessage(VcpControllerStateMachine.MUTE, device); + } else { + stateMachine.sendMessage(VcpControllerStateMachine.UNMUTE, device); + } + } + return true; + } + + /** + * Get current absolute volume of the remote device + * + * @param device: remote device instance + * @return current absolute volume of the remote device + */ + public int getAbsoluteVolume(BluetoothDevice device) { + synchronized (mStateMachines) { + final VcpControllerStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + return -1; + } + return stateMachine.getVolume(); + } + } + + /** + * Get mute status of remote device + * + * @param device: remote device instance + * @return current mute status of the remote device: + * true if mute status, false if unmute status + */ + public boolean isMute(BluetoothDevice device) { + synchronized (mStateMachines) { + final VcpControllerStateMachine stateMachine = mStateMachines.get(device); + if (stateMachine == null) { + return false; + } + return stateMachine.isMute(); + } + } + + /** + * Get the current connection state of the VCP + * + * @param device is the remote bluetooth device + * @return {@link BluetoothProfile#STATE_DISCONNECTED} if VCP is disconnected, + * {@link BluetoothProfile#STATE_CONNECTING} if VCP is being connected, + * {@link BluetoothProfile#STATE_CONNECTED} if VCP is connected, or + * {@link BluetoothProfile#STATE_DISCONNECTING} if VCP is being disconnected + */ + public int getConnectionState(BluetoothDevice device) { + synchronized (mStateMachines) { + VcpControllerStateMachine sm = mStateMachines.get(device); + if (sm == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + return sm.getConnectionState(); + } + } + + /** + * Get current VCP Connection mode + * + * @param device: remote device instance + * @return current connection mode of VCP: + * {@link #BluetoothVcp.MODE_NONE} if none VCP connection + * {@link #BluetoothVcp.MODE_UNICAST} if VCP is connected for unicast + * {@link #BluetoothVcp.MODE_BROADCAST} if VCP is connected for broadcast + * {@link #BluetoothVcp.MODE_UNICAST_BROADCAST} if VCP is connected + * for both unicast and broadcast + */ + public int getConnectionMode(BluetoothDevice device) { + synchronized (mStateMachines) { + if (mConnectionMode.containsKey(device)) { + return mConnectionMode.get(device); + } + return BluetoothVcp.MODE_NONE; + } + } + + /** + * Check if VCP is connected for broadcast mode + * + * @param device: remote device instance + * @return true if VCP is connected for broadcast or uncast-broadcast + * return false if VCP is connected for unicast-only + */ + public boolean isBroadcastDevice(BluetoothDevice device) { + if (device == null) + return false; + + synchronized (mStateMachines) { + if (mConnectionMode.containsKey(device)) { + if ((mConnectionMode.get(device) & BluetoothVcp.MODE_BROADCAST) != 0) { + return true; + } + } + } + return false; + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean okToConnect(BluetoothDevice device) { + // Check if this is an incoming connection in Quiet mode. + if (mAdapterService.isQuietModeEnabled()) { + Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled"); + return false; + } + + int bondState = mAdapterService.getBondState(device); + if (bondState != BluetoothDevice.BOND_BONDED) { + Log.w(TAG, "okToConnect: return false, bondState=" + bondState); + return false; + } + return true; + } + + void messageFromNative(VcpStackEvent stackEvent) { + Objects.requireNonNull(stackEvent.device, + "Device should never be null, event: " + stackEvent); + + synchronized (mStateMachines) { + BluetoothDevice device = stackEvent.device; + VcpControllerStateMachine sm = mStateMachines.get(device); + if (sm == null) { + if (stackEvent.type == VcpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) { + switch (stackEvent.valueInt1) { + case VcpStackEvent.CONNECTION_STATE_CONNECTED: + case VcpStackEvent.CONNECTION_STATE_CONNECTING: + sm = getOrCreateStateMachine(device); + break; + default: + break; + } + } + } + if (sm == null) { + Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent); + return; + } + sm.sendMessage(VcpControllerStateMachine.STACK_EVENT, stackEvent); + } + } + + int getCsipSetId(BluetoothDevice device, ParcelUuid uuid) { + GroupService csipService = GroupService.getGroupService(); + if (csipService != null) { + return csipService.getRemoteDeviceGroupId(device, uuid); + } else { + return -1; + } + } + + void onConnectionStateChangedFromStateMachine(BluetoothDevice device, + int newState, int prevState) { + Log.d(TAG, "onConnectionStateChangedFromStateMachine for device: " + device + + " newState: " + newState); + + if (device == null) { + Log.d(TAG, "device is null "); + return; + } + + mDpm = DeviceProfileMap.getDeviceProfileMapInstance(); + mAcmService = AcmService.getAcmService(); + BluetoothDevice grpDevice; + if (mAcmService != null) { + grpDevice = mAcmService.getGroup(device); + } else { + Log.w(TAG, "AcmService is null"); + grpDevice = device; + } + + synchronized (mStateMachines) { + if (newState == BluetoothProfile.STATE_DISCONNECTED) { + int bondState = mAdapterService.getBondState(device); + if (bondState == BluetoothDevice.BOND_NONE) { + removeStateMachine(device); + } + + if (mAcmService != null && + (mAcmService.isVcpPeerDeviceConnected(device, getCsipSetId(device, null)))) { + Log.d(TAG, "VCP Peer device connected, this is not last member, skip update to APM "); + } else { + ///* Update VCP profile disconnected to APM/ACM + Log.d(TAG, "All group members are disconnected, update to APM"); + if (mDpm != null) { + mDpm.profileConnectionUpdate(grpDevice, + ApmConst.AudioFeatures.MEDIA_VOLUME_CONTROL, ApmConst.AudioProfiles.VCP, false); + + mDpm.profileConnectionUpdate(grpDevice, + ApmConst.AudioFeatures.CALL_VOLUME_CONTROL, ApmConst.AudioProfiles.VCP, false); + } + } + setAbsVolumeSupport(device, false, -1); + updateConnState(device, newState); + //*/ + Log.d(TAG, "VCP get disconnected with renderer device: " + device); + } else if (newState == BluetoothProfile.STATE_CONNECTED) { + Log.d(TAG, "VCP get connected with renderer device: " + device); + + if (mAcmService != null && + (mAcmService.isVcpPeerDeviceConnected(device, getCsipSetId(device, null)))) { + Log.d(TAG, "VCP Peer device connected, this is not first connected member, skip update to APM "); + } else { + ///* Update VCP profile connected to APM/ACM + Log.d(TAG, "The first connected memeber, update to APM"); + if (mDpm != null) { + mDpm.profileConnectionUpdate(grpDevice, + ApmConst.AudioFeatures.MEDIA_VOLUME_CONTROL, ApmConst.AudioProfiles.VCP, true); + + mDpm.profileConnectionUpdate(grpDevice, + ApmConst.AudioFeatures.CALL_VOLUME_CONTROL, ApmConst.AudioProfiles.VCP, true); + } + } + // Set Abs Volume Support with true until get initial volume of remote + //*/ + } + } + } + + void setAbsVolumeSupport(BluetoothDevice device, boolean isAbsVolSupported, int initial_volume) { + mAcmService = AcmService.getAcmService(); + if (mAcmService != null) { + Log.d(TAG, "Update Abs Volume Support to upper layer "); + mAcmService.setAbsVolSupport(device, isAbsVolSupported, initial_volume); + } + } + + void notifyVolumeChanged(BluetoothDevice device, int volume, int audioType) { + Log.d(TAG, "notify volume changed for renderer device: " + device + " audioType: " + audioType); + // Notify ACM volume changed for device + mAcmService = AcmService.getAcmService(); + if (mAcmService != null) { + mAcmService.onVolumeStateChanged(device, volume, audioType); + } + Intent intent = new Intent(BluetoothVcp.ACTION_VOLUME_CHANGED); + intent.putExtra(BluetoothVcp.EXTRA_VOLUME, volume); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mContext.sendBroadcast(intent, BLUETOOTH_CONNECT, + Utils.getTempAllowlistBroadcastOptions()); + } + + void notifyMuteChanged(BluetoothDevice device, boolean mute) { + Log.d(TAG, "notify mute changed for renderer device: " + device + " mute: " + mute); + // Notify ACM mute changed + mAcmService = AcmService.getAcmService(); + if (mAcmService != null) { + mAcmService.onMuteStatusChanged (device, mute); + } + Intent intent = new Intent(BluetoothVcp.ACTION_MUTE_CHANGED); + intent.putExtra(BluetoothVcp.EXTRA_MUTE, mute); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mContext.sendBroadcast(intent, BLUETOOTH_CONNECT, + Utils.getTempAllowlistBroadcastOptions()); + } + + void broadcastConnectionModeChanged(BluetoothDevice device, int mode) { + Log.d(TAG, "broadccast connection mode changed for device: " + device + ", mode: " + mode); + Intent intent = new Intent(BluetoothVcp.ACTION_CONNECTION_MODE_CHANGED); + intent.putExtra(BluetoothVcp.EXTRA_MODE, mode); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mContext.sendBroadcast(intent, BLUETOOTH_CONNECT, + Utils.getTempAllowlistBroadcastOptions()); + } + + public void updateConnState(BluetoothDevice device, int newState) { + Log.d(TAG, "updateConnState: device: " + device + ", state: " + newState); + VolumeManager mVolumeManager = VolumeManager.get(); + mVolumeManager.onConnStateChange(device, newState, ApmConst.AudioProfiles.VCP); + } + + private class BondStateChangedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { + return; + } + int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.ERROR); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); + bondStateChanged(device, state); + } + } + + /** + * Process a change in the bonding state for a device. + * + * @param device the device whose bonding state has changed + * @param bondState the new bond state for the device. Possible values are: + * {@link BluetoothDevice#BOND_NONE}, + * {@link BluetoothDevice#BOND_BONDING}, + * {@link BluetoothDevice#BOND_BONDED}. + */ + @VisibleForTesting + void bondStateChanged(BluetoothDevice device, int bondState) { + if (DBG) { + Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState); + } + // Remove state machine if the bonding for a device is removed + if (bondState != BluetoothDevice.BOND_NONE) { + return; + } + + synchronized (mStateMachines) { + VcpControllerStateMachine sm = mStateMachines.get(device); + if (sm == null) { + return; + } + if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { + return; + } + mConnectionMode.remove(device); + removeStateMachine(device); + } + } + + private void removeStateMachine(BluetoothDevice device) { + synchronized (mStateMachines) { + VcpControllerStateMachine sm = mStateMachines.get(device); + if (sm == null) { + Log.w(TAG, "removeStateMachine: device " + device + + " does not have a state machine"); + return; + } + Log.i(TAG, "removeStateMachine: removing state machine for device: " + device); + sm.doQuit(); + sm.cleanup(); + mStateMachines.remove(device); + } + } + + private VcpControllerStateMachine getOrCreateStateMachine(BluetoothDevice device) { + if (device == null) { + Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null"); + return null; + } + synchronized (mStateMachines) { + VcpControllerStateMachine sm = mStateMachines.get(device); + if (sm != null) { + return sm; + } + if (mStateMachines.size() >= MAX_VCP_STATE_MACHINES) { + Log.e(TAG, "Maximum number of VCP state machines reached: " + + MAX_VCP_STATE_MACHINES); + return null; + } + if (DBG) { + Log.d(TAG, "Creating a new state machine for " + device); + } + sm = VcpControllerStateMachine.make(device, this, mContext, + mNativeInterface, mStateMachinesThread.getLooper()); + mStateMachines.put(device, sm); + return sm; + } + } + + private class VcpControllerTestReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(ACTION_CONNECT_DEVICE)) { + Log.d(TAG, "Receive ACTION_CONNECT_DEVICE"); + int mode = intent.getIntExtra(BluetoothVcp.EXTRA_MODE, 0); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + connect(device, mode); + } else if (action.equals(ACTION_DISCONNECT_DEVICE)) { + Log.d(TAG, "Receive ACTION_DISCONNECT_DEVICE"); + int mode = intent.getIntExtra(BluetoothVcp.EXTRA_MODE, 0); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + disconnect(device, mode); + } + } + } +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpControllerNativeInterface.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpControllerNativeInterface.java new file mode 100644 index 00000000000..89cc69bf01d --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpControllerNativeInterface.java @@ -0,0 +1,200 @@ +/* + *Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * 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.vcp; + +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; + +/** + * Vcp Controller Native Interface to/from JNI. + */ +public class VcpControllerNativeInterface { + private static final String TAG = "VcpControllerNativeInterface"; + private static final boolean DBG = true; + private BluetoothAdapter mAdapter; + + @GuardedBy("INSTANCE_LOCK") + private static VcpControllerNativeInterface sInstance; + private static final Object INSTANCE_LOCK = new Object(); + + static { + classInitNative(); + } + + private VcpControllerNativeInterface() { + mAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mAdapter == null) { + Log.wtfStack(TAG, "No Bluetooth Adapter Available"); + } + } + + /** + * Get singleton instance. + */ + public static VcpControllerNativeInterface getInstance() { + synchronized (INSTANCE_LOCK) { + if (sInstance == null) { + sInstance = new VcpControllerNativeInterface(); + } + return sInstance; + } + } + + /** + * Initializes the native interface. + * + * priorities to configure. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void init() { + initNative(); + } + + /** + * Cleanup the native interface. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void cleanup() { + cleanupNative(); + } + + /** + * Initiates Vcp connection to a remote device. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean connectVcp(BluetoothDevice device, boolean isDirect) { + return connectVcpNative(getByteAddress(device), isDirect); + } + + /** + * Disconnects Vcp from a remote device. + * + * @param device the remote device + * @return true on success, otherwise false. + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean disconnectVcp(BluetoothDevice device) { + return disconnectVcpNative(getByteAddress(device)); + } + + /** + * Sets the Vcp Abs volume + * @param volume + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean setAbsVolume(int volume, BluetoothDevice device) { + return setAbsVolumeNative(volume, getByteAddress(device)); + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean mute(BluetoothDevice device) { + return muteNative(getByteAddress(device)); + } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean unmute(BluetoothDevice device) { + return unmuteNative(getByteAddress(device)); + } + + private BluetoothDevice getDevice(byte[] address) { + return mAdapter.getRemoteDevice(address); + } + + 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(VcpStackEvent event) { + VcpController service = VcpController.getVcpController(); + if (service != null) { + service.messageFromNative(event); + } else { + Log.e(TAG, "Event ignored, service not available: " + 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 onConnectionStateChanged(int state, byte[] address) { + VcpStackEvent event = + new VcpStackEvent(VcpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED); + event.device = getDevice(address); + event.valueInt1 = state; + + if (DBG) { + Log.d(TAG, "onConnectionStateChanged: " + event); + } + sendMessageToService(event); + } + + private void OnVolumeStateChange(int volume, int mute, byte[] address) { + VcpStackEvent event = new VcpStackEvent( + VcpStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED); + event.device = getDevice(address); + event.valueInt1 = volume; + event.valueInt2 = mute; + + if (DBG) { + Log.d(TAG, "OnVolumeStateChange: " + event); + } + sendMessageToService(event); + } + + private void OnVolumeFlagsChange(int flags, byte[] address) { + VcpStackEvent event = new VcpStackEvent( + VcpStackEvent.EVENT_TYPE_VOLUME_FLAGS_CHANGED); + event.device = getDevice(address); + event.valueInt1 = flags; + + if (DBG) { + Log.d(TAG, "OnVolumeFlagsChange: " + event); + } + sendMessageToService(event); + } + + // Native methods that call into the JNI interface + private static native void classInitNative(); + private native void initNative(); + private native void cleanupNative(); + private native boolean connectVcpNative(byte[] address, boolean isDirect); + private native boolean disconnectVcpNative(byte[] address); + private native boolean setAbsVolumeNative(int volume, byte[] address); + private native boolean muteNative(byte[] address); + private native boolean unmuteNative(byte[] address); +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpControllerStateMachine.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpControllerStateMachine.java new file mode 100644 index 00000000000..fc35753ddc4 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpControllerStateMachine.java @@ -0,0 +1,1046 @@ +/* + *Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * 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. + */ + +/** + * Bluetooth VCP StateMachine. There is one instance per remote device. + * - "Disconnected" and "Connected" are steady states. + * - "Connecting" and "Disconnecting" are transient states until the + * connection / disconnection is completed. + * + * + * (Disconnected) + * | ^ + * CONNECT | | DISCONNECTED + * V | + * (Connecting)<--->(Disconnecting) + * | ^ + * CONNECTED | | DISCONNECT + * V | + * (Connected) + * NOTES: + * - If state machine is in "Connecting" state and the remote device sends + * DISCONNECT request, the state machine transitions to "Disconnecting" state. + * - Similarly, if the state machine is in "Disconnecting" state and the remote device + * sends CONNECT request, the state machine transitions to "Connecting" state. + * + * DISCONNECT + * (Connecting) ---------------> (Disconnecting) + * <--------------- + * CONNECT + * + */ + +package com.android.bluetooth.vcp; + +import static android.Manifest.permission.BLUETOOTH_CONNECT; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import com.android.bluetooth.Utils; +import android.bluetooth.BluetoothVcp; +import android.content.Context; +import android.content.Intent; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import com.android.bluetooth.btservice.ProfileService; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Scanner; + +final class VcpControllerStateMachine extends StateMachine { + private static final boolean DBG = true; + private static final String TAG = "VcpControllerStateMachine"; + + static final int CONNECT = 1; + static final int DISCONNECT = 2; + static final int SET_VOLUME = 3; + static final int MUTE = 4; + static final int UNMUTE = 5; + + @VisibleForTesting + static final int STACK_EVENT = 101; + private static final int CONNECT_TIMEOUT = 201; + private static final int SET_ABS_VOL_TIMEOUT = 202; + private static final int CHANGE_MUTE_TIMEOUT = 203; + + private static final int UNMUTE_STATE = 0; + private static final int MUTE_STATE = 1; + private static final int VOLUME_SETTING_NOT_PERSISTED = 0x00; + private static final int VOLUME_SETTING_PERSISTED = 0x01; + + private static final int MAX_ERROR_RETRY_TIMES = 3; + private static final int VCP_MAX_VOL = 255; + // The default VCP volume 0x77 (119) + private static final int VCP_DEFAULT_VOL = 119; + private static final int CMD_TIMEOUT_DELAY = 2000; + + + // NOTE: the value is not "final" - it is modified in the unit tests + @VisibleForTesting + static int sConnectTimeoutMs = 30000; // 30s + + private Disconnected mDisconnected; + private Connecting mConnecting; + private Disconnecting mDisconnecting; + private Connected mConnected; + private int mLastConnectionState = -1; + + private VcpController mVcpController; + private VcpControllerNativeInterface mNativeInterface; + private Context mContext; + + /* Current remote volume */ + private int mRemoteVolume; + /* Requested volume in progress of Native Layer setAbsVolume */ + private int mRequestedVolume; + /* Cached new volume if has a requested volume in progress */ + private int mCachedVolume; + private int mAbsVolRetryTimes; + private boolean mAbsVolSetInProgress; + + /* Current remote mute state */ + private int mMuteState; + /* Requested mute state in progress of Native Layer mute/unMute */ + private int mRequestedMuteState; + /* Cached new mute state if has a requested mute state in progress */ + private int mCachedMuteState; + private int mChangeMuteRetryTimes; + private boolean mMuteChangeInProgress; + + private int mVolumeFlags; + private final BluetoothDevice mDevice; + private int mVolumeControlAudioType; + private int mCachedVolumeControlAudioType; + + VcpControllerStateMachine(BluetoothDevice device, VcpController svc, Context context, + VcpControllerNativeInterface nativeInterface, Looper looper) { + super(TAG, looper); + mDevice = device; + mVcpController = svc; + mContext = context; + mNativeInterface = nativeInterface; + + mDisconnected = new Disconnected(); + mConnecting = new Connecting(); + mDisconnecting = new Disconnecting(); + mConnected = new Connected(); + + addState(mDisconnected); + addState(mConnecting); + addState(mDisconnecting); + addState(mConnected); + + setInitialState(mDisconnected); + } + + static VcpControllerStateMachine make(BluetoothDevice device, VcpController svc, + Context context, VcpControllerNativeInterface nativeInterface, Looper looper) { + Log.i(TAG, "make for device " + device); + VcpControllerStateMachine VcpControllerSm = + new VcpControllerStateMachine(device, svc, context, nativeInterface, looper); + VcpControllerSm.start(); + return VcpControllerSm; + } + + public void doQuit() { + log("doQuit for device " + mDevice); + quitNow(); + } + + public void cleanup() { + log("cleanup for device " + mDevice); + } + + @VisibleForTesting + class Disconnected extends State { + @Override + public void enter() { + Log.i(TAG, "Enter Disconnected(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + + removeDeferredMessages(DISCONNECT); + if (mLastConnectionState != -1) { + // Don't broadcast during startup + broadcastConnectionState(BluetoothProfile.STATE_DISCONNECTED, + mLastConnectionState); + } + + cleanupDevice(); + } + + @Override + public void exit() { + log("Exit Disconnected(" + mDevice + "): " + messageWhatToString( + getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_DISCONNECTED; + } + + @Override + public boolean processMessage(Message message) { + log("Disconnected process message(" + mDevice + "): " + messageWhatToString( + message.what)); + + switch (message.what) { + case CONNECT: + BluetoothDevice device = (BluetoothDevice) message.obj; + log("Connecting to " + device); + + if (!mDevice.equals(device)) { + Log.e(TAG, "CONNECT failed, device=" + device + ", currentDev=" + mDevice); + break; + } + + if (!mNativeInterface.connectVcp(mDevice, true)) { + Log.e(TAG, "Disconnected: error connecting to " + mDevice); + break; + } + + transitionTo(mConnecting); + break; + case DISCONNECT: + Log.w(TAG, "Disconnected: DISCONNECT ignored: " + mDevice); + break; + case STACK_EVENT: + VcpStackEvent event = (VcpStackEvent) message.obj; + if (DBG) { + Log.d(TAG, "Disconnected: stack event: " + event); + } + if (!mDevice.equals(event.device)) { + Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event); + break; + } + switch (event.type) { + case VcpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1); + break; + default: + Log.e(TAG, "Disconnected: ignoring stack event: " + event); + break; + } + break; + default: + Log.e(TAG, "Unexpected msg " + messageWhatToString(message.what) + + ": " + message); + return NOT_HANDLED; + } + return HANDLED; + } + + // in Disconnected state + private void processConnectionEvent(int state) { + switch (state) { + case VcpStackEvent.CONNECTION_STATE_DISCONNECTED: + Log.w(TAG, "Ignore VCP DISCONNECTED event: " + mDevice); + break; + case VcpStackEvent.CONNECTION_STATE_CONNECTING: + Log.i(TAG, "Incoming VCP Connecting request accepted: " + mDevice); + if (mVcpController.okToConnect(mDevice)) { + transitionTo(mConnecting); + } else { + // Reject the connection and stay in Disconnected state itself + Log.w(TAG, "Incoming VCP Connecting request rejected: " + mDevice); + mNativeInterface.disconnectVcp(mDevice); + } + break; + case VcpStackEvent.CONNECTION_STATE_CONNECTED: + Log.w(TAG, "VCP Connected from Disconnected state: " + mDevice); + if (mVcpController.okToConnect(mDevice)) { + Log.i(TAG, "Incoming VCP Connected request accepted: " + mDevice); + transitionTo(mConnected); + } else { + // Reject the connection and stay in Disconnected state itself + Log.w(TAG, "Incoming VCP Connected request rejected: " + mDevice); + mNativeInterface.disconnectVcp(mDevice); + } + break; + case VcpStackEvent.CONNECTION_STATE_DISCONNECTING: + Log.w(TAG, "Ignore VCP DISCONNECTING event: " + mDevice); + break; + default: + Log.e(TAG, "Incorrect state: " + state + " device: " + mDevice); + break; + } + } + } + + @VisibleForTesting + class Connecting extends State { + @Override + public void enter() { + Log.i(TAG, "Enter Connecting(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + sendMessageDelayed(CONNECT_TIMEOUT, mDevice, sConnectTimeoutMs); + broadcastConnectionState(BluetoothProfile.STATE_CONNECTING, mLastConnectionState); + } + + @Override + public void exit() { + log("Exit Connecting(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_CONNECTING; + removeMessages(CONNECT_TIMEOUT); + } + + @Override + public boolean processMessage(Message message) { + log("Connecting process message(" + mDevice + "): " + + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: + deferMessage(message); + break; + case CONNECT_TIMEOUT: + Log.w(TAG, "Connecting connection timeout: " + mDevice); + mNativeInterface.disconnectVcp(mDevice); + // We timed out trying to connect, transition to Disconnected state + BluetoothDevice device = (BluetoothDevice) message.obj; + if (!mDevice.equals(device)) { + Log.e(TAG, "Unknown device timeout " + device); + break; + } + Log.w(TAG, "CONNECT_TIMEOUT"); + transitionTo(mDisconnected); + break; + case DISCONNECT: + log("Connecting: connection canceled to " + mDevice); + mNativeInterface.disconnectVcp(mDevice); + transitionTo(mDisconnected); + break; + case SET_VOLUME: + case MUTE: + case UNMUTE: + case SET_ABS_VOL_TIMEOUT: + case CHANGE_MUTE_TIMEOUT: + deferMessage(message); + break; + case STACK_EVENT: + VcpStackEvent event = (VcpStackEvent) message.obj; + log("Connecting: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case VcpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1); + break; + default: + Log.e(TAG, "Connecting: ignoring stack event: " + event); + break; + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Connecting state + private void processConnectionEvent(int state) { + switch (state) { + case VcpStackEvent.CONNECTION_STATE_DISCONNECTED: + Log.w(TAG, "Connecting device disconnected: " + mDevice); + transitionTo(mDisconnected); + break; + case VcpStackEvent.CONNECTION_STATE_CONNECTED: + transitionTo(mConnected); + break; + case VcpStackEvent.CONNECTION_STATE_CONNECTING: + break; + case VcpStackEvent.CONNECTION_STATE_DISCONNECTING: + Log.w(TAG, "Connecting interrupted: device is disconnecting: " + mDevice); + transitionTo(mDisconnecting); + break; + default: + Log.e(TAG, "Incorrect state: " + state); + break; + } + } + } + + @VisibleForTesting + class Disconnecting extends State { + @Override + public void enter() { + Log.i(TAG, "Enter Disconnecting(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + sendMessageDelayed(CONNECT_TIMEOUT, mDevice, sConnectTimeoutMs); + broadcastConnectionState(BluetoothProfile.STATE_DISCONNECTING, mLastConnectionState); + } + + @Override + public void exit() { + log("Exit Disconnecting(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_DISCONNECTING; + removeMessages(CONNECT_TIMEOUT); + } + + @Override + public boolean processMessage(Message message) { + log("Disconnecting process message(" + mDevice + "): " + + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: + deferMessage(message); + break; + case CONNECT_TIMEOUT: { + Log.w(TAG, "Disconnecting connection timeout: " + mDevice); + mNativeInterface.disconnectVcp(mDevice); + // We timed out trying to connect, transition to Disconnected state + BluetoothDevice device = (BluetoothDevice) message.obj; + if (!mDevice.equals(device)) { + Log.e(TAG, "Unknown device timeout " + device); + break; + } + transitionTo(mDisconnected); + Log.w(TAG, "CONNECT_TIMEOUT"); + break; + } + case DISCONNECT: + deferMessage(message); + break; + case SET_VOLUME: + case MUTE: + case UNMUTE: + case SET_ABS_VOL_TIMEOUT: + case CHANGE_MUTE_TIMEOUT: + deferMessage(message); + break; + case STACK_EVENT: + VcpStackEvent event = (VcpStackEvent) message.obj; + log("Disconnecting: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case VcpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1); + break; + default: + Log.e(TAG, "Disconnecting: ignoring stack event: " + event); + break; + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Disconnecting state + private void processConnectionEvent(int state) { + switch (state) { + case VcpStackEvent.CONNECTION_STATE_DISCONNECTED: + Log.i(TAG, "Disconnected: " + mDevice); + transitionTo(mDisconnected); + break; + case VcpStackEvent.CONNECTION_STATE_CONNECTED: + if (mVcpController.okToConnect(mDevice)) { + Log.w(TAG, "Disconnecting interrupted: device is connected: " + mDevice); + transitionTo(mConnected); + } else { + // Reject the connection and stay in Disconnecting state + Log.w(TAG, "Incoming VCP Connected request rejected: " + mDevice); + mNativeInterface.disconnectVcp(mDevice); + } + break; + case VcpStackEvent.CONNECTION_STATE_CONNECTING: + if (mVcpController.okToConnect(mDevice)) { + Log.i(TAG, "Disconnecting interrupted: try to reconnect: " + mDevice); + transitionTo(mConnecting); + } else { + // Reject the connection and stay in Disconnecting state + Log.w(TAG, "Incoming VCP Connecting request rejected: " + mDevice); + mNativeInterface.disconnectVcp(mDevice); + } + break; + case VcpStackEvent.CONNECTION_STATE_DISCONNECTING: + break; + default: + Log.e(TAG, "Incorrect state: " + state); + break; + } + } + } + + @VisibleForTesting + class Connected extends State { + @Override + public void enter() { + Log.i(TAG, "Enter Connected(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + removeDeferredMessages(CONNECT); + broadcastConnectionState(BluetoothProfile.STATE_CONNECTED, mLastConnectionState); + } + + @Override + public void exit() { + log("Exit Connected(" + mDevice + "): " + + messageWhatToString(getCurrentMessage().what)); + mLastConnectionState = BluetoothProfile.STATE_CONNECTED; + } + + @Override + public boolean processMessage(Message message) { + log("Connected process message(" + mDevice + "): " + + messageWhatToString(message.what)); + + switch (message.what) { + case CONNECT: { + Log.w(TAG, "Connected: CONNECT ignored: " + mDevice); + break; + } + case DISCONNECT: { + log("Disconnecting from " + mDevice); + if (!mNativeInterface.disconnectVcp(mDevice)) { + // If error in the native stack, transition directly to Disconnected state. + Log.e(TAG, "Connected: error disconnecting from " + mDevice); + transitionTo(mDisconnected); + break; + } + transitionTo(mDisconnecting); + break; + } + case SET_VOLUME: { + BluetoothDevice device = (BluetoothDevice) message.obj; + if (!mDevice.equals(device)) { + Log.w(TAG, "SET_VOLUME failed " + device + + " is not currentDevice"); + break; + } + log("Set volume for " + device); + + processSetAbsVolume(message.arg1, message.arg2); + break; + } + case MUTE: { + BluetoothDevice device = (BluetoothDevice) message.obj; + if (!mDevice.equals(device)) { + Log.w(TAG, "Mute failed " + device + + " is not currentDevice"); + break; + } + log("Mute for " + device); + + processSetMute(); + break; + } + case UNMUTE: { + BluetoothDevice device = (BluetoothDevice) message.obj; + if (!mDevice.equals(device)) { + Log.w(TAG, "Unmute failed " + device + + " is not currentDevice"); + break; + } + log("Unmute for " + device); + + processSetUnmute(); + break; + } + case SET_ABS_VOL_TIMEOUT: { + BluetoothDevice device = (BluetoothDevice) message.obj; + if (!mDevice.equals(device)) { + Log.w(TAG, "Set abs vol timeout failed " + device + + " is not currentDevice"); + break; + } + + mAbsVolSetInProgress = false; + if (mAbsVolRetryTimes >= MAX_ERROR_RETRY_TIMES) { + Log.w(TAG, "Set abs vol retry exceed max times"); + mRequestedVolume = -1; + mAbsVolRetryTimes = 0; + break; + } else { + mAbsVolRetryTimes += 1; + if (mNativeInterface.setAbsVolume(mRequestedVolume, mDevice)) { + sendMessageDelayed(SET_ABS_VOL_TIMEOUT, mDevice, + CMD_TIMEOUT_DELAY); + mAbsVolSetInProgress = true; + } else { + mRequestedVolume = -1; + mAbsVolRetryTimes = 0; + Log.e(TAG, "Set absolute volume failed for device: " + mDevice); + } + } + break; + } + case CHANGE_MUTE_TIMEOUT: { + BluetoothDevice device = (BluetoothDevice) message.obj; + if (!mDevice.equals(device)) { + Log.w(TAG, "Mute timeout failed " + device + + " is not currentDevice"); + break; + } + + mMuteChangeInProgress = false; + if (mChangeMuteRetryTimes >= MAX_ERROR_RETRY_TIMES) { + Log.w(TAG, "Mute retry exceed max times"); + mChangeMuteRetryTimes = 0; + mRequestedMuteState = -1; + break; + } else { + mChangeMuteRetryTimes += 1; + boolean ret; + if (mRequestedMuteState == MUTE_STATE) { + ret = mNativeInterface.mute(mDevice); + } else { + ret = mNativeInterface.unmute(mDevice); + } + + if (ret) { + sendMessageDelayed(CHANGE_MUTE_TIMEOUT, mDevice, + CMD_TIMEOUT_DELAY); + mMuteChangeInProgress = true; + } else { + mChangeMuteRetryTimes = 0; + mRequestedMuteState = -1; + Log.e(TAG, "Change Mute failed for device: " + mDevice); + } + } + break; + } + case STACK_EVENT: + VcpStackEvent event = (VcpStackEvent) message.obj; + log("Connected: stack event: " + event); + if (!mDevice.equals(event.device)) { + Log.wtfStack(TAG, "Device(" + mDevice + "): event mismatch: " + event); + } + switch (event.type) { + case VcpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: + processConnectionEvent(event.valueInt1); + break; + case VcpStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED: + processVolumeStateEvent(event.valueInt1, event.valueInt2); + break; + case VcpStackEvent.EVENT_TYPE_VOLUME_FLAGS_CHANGED: + processVolumeFlagsChanged(event.valueInt1); + break; + default: + Log.e(TAG, "Connected: ignoring stack event: " + event); + break; + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + // in Connected state + private void processConnectionEvent(int state) { + switch (state) { + case VcpStackEvent.CONNECTION_STATE_DISCONNECTED: + Log.i(TAG, "Disconnected from " + mDevice); + transitionTo(mDisconnected); + break; + case VcpStackEvent.CONNECTION_STATE_DISCONNECTING: + Log.i(TAG, "Disconnecting from " + mDevice); + transitionTo(mDisconnecting); + break; + default: + Log.e(TAG, "Connection State Device: " + mDevice + " bad state: " + state); + break; + } + } + } + + private void processSetAbsVolume(int volume, int audioType) { + log("process set absolute volume"); + + if (mAbsVolSetInProgress) { + mCachedVolume = volume; + mCachedVolumeControlAudioType = audioType; + Log.w(TAG, "There is already a volume command in progress, cache volume: " + + mCachedVolume + " cached audio type: " + audioType); + return; + } + + if (mRemoteVolume == -1) { + Log.w(TAG, "remote not tell initial volume"); + return; + } + + if (mRemoteVolume == volume) { + Log.w(TAG, "Ignore set abs volume as current volume equals to requested volume"); + return; + } + + Log.d(TAG, "set abs volume for audio type: " + audioType); + if (mNativeInterface.setAbsVolume(volume, mDevice)) { + sendMessageDelayed(SET_ABS_VOL_TIMEOUT, mDevice, + CMD_TIMEOUT_DELAY); + mAbsVolSetInProgress = true; + mRequestedVolume = volume; + mVolumeControlAudioType = audioType; + mCachedVolume = -1; + } else { + Log.e(TAG, "Set absolute volume failed for device: " + mDevice); + } + } + + private void processSetMute() { + log("process mute"); + + if (mMuteChangeInProgress) { + mCachedMuteState = MUTE_STATE; + Log.w(TAG, "There is already a mute change in progress, cache mute"); + return; + } + + if (mRemoteVolume == -1) { + Log.w(TAG, "remote not tell initial volume"); + return; + } + + if (mMuteState == MUTE_STATE) { + Log.w(TAG, "Ignore mute request as current state is mute"); + return; + } + + if (mNativeInterface.mute(mDevice)) { + sendMessageDelayed(CHANGE_MUTE_TIMEOUT, mDevice, + CMD_TIMEOUT_DELAY); + mMuteChangeInProgress = true; + mRequestedMuteState = MUTE_STATE; + mCachedMuteState = -1; + } else { + Log.e(TAG, "Mute failed for device: " + mDevice); + } + } + + private void processSetUnmute() { + log("process unmute"); + + if (mMuteChangeInProgress) { + mCachedMuteState = UNMUTE_STATE; + Log.w(TAG, "There is already a mute change in progress, cache unmute"); + return; + } + + if (mRemoteVolume == -1) { + Log.w(TAG, "remote not tell initial volume"); + return; + } + + if (mMuteState == UNMUTE_STATE) { + Log.w(TAG, "Ignore unmute request as current state is unmute"); + return; + } + + if (mNativeInterface.unmute(mDevice)) { + sendMessageDelayed(CHANGE_MUTE_TIMEOUT, mDevice, + CMD_TIMEOUT_DELAY); + mMuteChangeInProgress = true; + mRequestedMuteState = UNMUTE_STATE; + mCachedMuteState = -1; + } else { + Log.e(TAG, "Unmute failed for device: " + mDevice); + } + } + + private void processVolumeStateEvent(int vcpVol, int mute) { + log("process volume state event"); + + if (mRemoteVolume == -1 || mMuteState != mute || + mMuteChangeInProgress == true) { + processMuteChanged(mute); + } + + if (mRemoteVolume == -1 || mRemoteVolume != vcpVol || + mAbsVolSetInProgress == true) { + processVolumeChanged(vcpVol); + } + } + + private void processVolumeChanged(int vcpVol) { + log("process volume setting changed"); + + if (mAbsVolSetInProgress == true) { + mAbsVolSetInProgress = false; + removeMessages(SET_ABS_VOL_TIMEOUT); + if (mRequestedVolume == vcpVol) { + mRequestedVolume = -1; + mAbsVolRetryTimes = 0; + + if ((mCachedVolume != -1) && (mCachedVolume != vcpVol)) { + mVcpController.notifyVolumeChanged(mDevice, vcpVol, mVolumeControlAudioType); + mVolumeControlAudioType = -1; + Log.w(TAG, "Set cached volume to remote"); + if (mNativeInterface.setAbsVolume(mCachedVolume, mDevice)) { + sendMessageDelayed(SET_ABS_VOL_TIMEOUT, mDevice, + CMD_TIMEOUT_DELAY); + mAbsVolSetInProgress = true; + mRequestedVolume = mCachedVolume; + mVolumeControlAudioType = mCachedVolumeControlAudioType; + mCachedVolumeControlAudioType = -1; + mCachedVolume = -1; + return; + } else { + Log.e(TAG, "Set cached volume failed for device: " + mDevice); + mCachedVolume = -1; + } + } + } else { + Log.w(TAG, "Remote changed volume not equal to requested volume"); + if (mAbsVolRetryTimes >= MAX_ERROR_RETRY_TIMES) { + Log.w(TAG, "Set abs vol retry exceed max times"); + mRequestedVolume = -1; + mAbsVolRetryTimes = 0; + } else { + mAbsVolRetryTimes += 1; + if (mNativeInterface.setAbsVolume(mRequestedVolume, mDevice)) { + sendMessageDelayed(SET_ABS_VOL_TIMEOUT, mDevice, + CMD_TIMEOUT_DELAY); + mAbsVolSetInProgress = true; + return; + } else { + Log.e(TAG, "Set absolute volume failed for device: " + mDevice); + mRequestedVolume = -1; + mAbsVolRetryTimes = 0; + } + } + } + } + + if (mRemoteVolume == -1) { + // Set initial volume if volume flags is not persisted + if ((mVolumeFlags == VOLUME_SETTING_NOT_PERSISTED)) { + int initialVolume = VCP_DEFAULT_VOL; + if (vcpVol != initialVolume) { + mRemoteVolume = vcpVol; + Log.w(TAG, "Set initial volume to remote if volume persisted flag is false"); + if (mNativeInterface.setAbsVolume(initialVolume, mDevice)) { + sendMessageDelayed(SET_ABS_VOL_TIMEOUT, mDevice, + CMD_TIMEOUT_DELAY); + mAbsVolSetInProgress = true; + mRequestedVolume = initialVolume; + mVcpController.setAbsVolumeSupport(mDevice, true, initialVolume); + mVcpController.updateConnState(mDevice, BluetoothProfile.STATE_CONNECTED); + return; + } else { + Log.e(TAG, "Set absolute volume failed for device: " + mDevice); + } + } + } + Log.w(TAG, "Set abs volume support and update initial volume to ACM"); + mRemoteVolume = vcpVol; + mVcpController.setAbsVolumeSupport(mDevice, true, vcpVol); + mVcpController.updateConnState(mDevice, BluetoothProfile.STATE_CONNECTED); + return; + } + + if (mRemoteVolume != vcpVol) { + mRemoteVolume = vcpVol; + mVcpController.notifyVolumeChanged(mDevice, vcpVol, mVolumeControlAudioType); + mVolumeControlAudioType = -1; + long pecentVolChanged = ((long)vcpVol * 100) / 0xff; + Log.w(TAG, "percent volume changed: " + pecentVolChanged + "%"); + } + } + + private void processMuteChanged(int mute) { + log("process mute changed"); + + if (mMuteChangeInProgress == true) { + mMuteChangeInProgress = false; + mChangeMuteRetryTimes = 0; + removeMessages(CHANGE_MUTE_TIMEOUT); + + if ((mCachedMuteState != -1) && (mCachedMuteState != mute)) { + Log.w(TAG, "Set cached mute state to remote"); + boolean ret; + if (mCachedMuteState == MUTE_STATE) { + ret = mNativeInterface.mute(mDevice); + } else { + ret = mNativeInterface.unmute(mDevice); + } + + if (ret) { + sendMessageDelayed(CHANGE_MUTE_TIMEOUT, mDevice, + CMD_TIMEOUT_DELAY); + mMuteChangeInProgress = true; + mRequestedMuteState = mCachedMuteState; + mCachedMuteState = -1; + return; + } + mCachedMuteState = -1; + } + } + + if (mMuteState != mute) { + mMuteState = mute; + boolean isMute = (mMuteState == MUTE_STATE) ? true : false; + mVcpController.notifyMuteChanged(mDevice, isMute); + Log.w(TAG, "Mute state changed to " + mMuteState); + } + } + + private void processVolumeFlagsChanged(int flags) { + log("process volume flags changed"); + mVolumeFlags = flags; + } + + int getConnectionState() { + String currentState = getCurrentState().getName(); + switch (currentState) { + case "Disconnected": + return BluetoothProfile.STATE_DISCONNECTED; + case "Connecting": + return BluetoothProfile.STATE_CONNECTING; + case "Connected": + return BluetoothProfile.STATE_CONNECTED; + case "Disconnecting": + return BluetoothProfile.STATE_DISCONNECTING; + default: + Log.e(TAG, "Bad currentState: " + currentState); + return BluetoothProfile.STATE_DISCONNECTED; + } + } + + int getVolume() { + return mRemoteVolume; + } + + boolean isMute() { + if (mMuteState == MUTE_STATE) + return true; + else + return false; + } + + BluetoothDevice getDevice() { + return mDevice; + } + + synchronized boolean isConnected() { + return getCurrentState() == mConnected; + } + + // This method does not check for error condition (newState == prevState) + private void broadcastConnectionState(int newState, int prevState) { + log("Connection state " + mDevice + ": " + profileStateToString(prevState) + + "->" + profileStateToString(newState)); + mVcpController.onConnectionStateChangedFromStateMachine(mDevice, + newState, prevState); + + Intent intent = new Intent(BluetoothVcp.ACTION_CONNECTION_STATE_CHANGED); + intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState); + intent.putExtra(BluetoothProfile.EXTRA_STATE, newState); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + mContext.sendBroadcast(intent, BLUETOOTH_CONNECT, + Utils.getTempAllowlistBroadcastOptions()); + } + + private void cleanupDevice() { + log("cleanup device " + mDevice); + mRemoteVolume = -1; + mRequestedVolume = -1; + mCachedVolume = -1; + mAbsVolRetryTimes = 0; + mAbsVolSetInProgress = false; + mMuteState = -1; + mRequestedMuteState = -1; + mCachedMuteState = -1; + mChangeMuteRetryTimes = 0; + mMuteChangeInProgress = false; + mVolumeFlags = -1; + mVolumeControlAudioType = -1; + mCachedVolumeControlAudioType = -1; + } + + private static String messageWhatToString(int what) { + switch (what) { + case CONNECT: + return "CONNECT"; + case DISCONNECT: + return "DISCONNECT"; + case STACK_EVENT: + return "STACK_EVENT"; + case CONNECT_TIMEOUT: + return "CONNECT_TIMEOUT"; + case SET_VOLUME: + return "SET_VOLUME"; + case MUTE: + return "MUTE"; + case UNMUTE: + return "UNMUTE"; + case SET_ABS_VOL_TIMEOUT: + return "SET_ABS_VOL_TIMEOUT"; + case CHANGE_MUTE_TIMEOUT: + return "CHANGE_MUTE_TIMEOUT"; + default: + return "UNKNOWN(" + what + ")"; + } + } + + private static String profileStateToString(int state) { + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return "DISCONNECTED"; + case BluetoothProfile.STATE_CONNECTING: + return "CONNECTING"; + case BluetoothProfile.STATE_CONNECTED: + return "CONNECTED"; + case BluetoothProfile.STATE_DISCONNECTING: + return "DISCONNECTING"; + default: + break; + } + return Integer.toString(state); + } + + public void dump(StringBuilder sb) { + ProfileService.println(sb, "mDevice: " + mDevice); + ProfileService.println(sb, " StateMachine: " + this); + // Dump the state machine logs + StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter); + super.dump(new FileDescriptor(), printWriter, new String[]{}); + printWriter.flush(); + stringWriter.flush(); + ProfileService.println(sb, " StateMachineLog:"); + Scanner scanner = new Scanner(stringWriter.toString()); + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + ProfileService.println(sb, " " + line); + } + scanner.close(); + } + + @Override + protected void log(String msg) { + if (DBG) { + super.log(msg); + } + } +} + diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpStackEvent.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpStackEvent.java new file mode 100644 index 00000000000..e77f95d8483 --- /dev/null +++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpStackEvent.java @@ -0,0 +1,79 @@ +/* + *Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * 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. + */ + +package com.android.bluetooth.vcp; + +import android.bluetooth.BluetoothDevice; + +/** + * Stack event sent via a callback from JNI to Java, or generated + * internally by the VCP State Machine. + */ +public class VcpStackEvent { + // Event types for STACK_EVENT message (coming from native) + private static final int EVENT_TYPE_NONE = 0; + public static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1; + public static final int EVENT_TYPE_VOLUME_STATE_CHANGED = 2; + public static final int EVENT_TYPE_VOLUME_FLAGS_CHANGED = 3; + + // Do not modify without updating the HAL bt_vcp.h files. + // Match up with enum class ConnectionState of bt_vcp_controller.h. + static final int CONNECTION_STATE_DISCONNECTED = 0; + static final int CONNECTION_STATE_CONNECTING = 1; + static final int CONNECTION_STATE_CONNECTED = 2; + static final int CONNECTION_STATE_DISCONNECTING = 3; + + public int type; + public BluetoothDevice device; + public int valueInt1; + public int valueInt2; + + VcpStackEvent(int type) { + this.type = type; + } + + @Override + public String toString() { + // event dump + StringBuilder result = new StringBuilder(); + result.append("VcpStackEvent {type:" + eventTypeToString(type)); + result.append(", device:" + device); + result.append(", value1:" + valueInt1); + result.append(", value2:" + valueInt2); + result.append("}"); + return result.toString(); + } + + private static String eventTypeToString(int type) { + switch (type) { + case EVENT_TYPE_NONE: + return "EVENT_TYPE_NONE"; + case EVENT_TYPE_CONNECTION_STATE_CHANGED: + return "EVENT_TYPE_CONNECTION_STATE_CHANGED"; + case EVENT_TYPE_VOLUME_STATE_CHANGED: + return "EVENT_TYPE_VOLUME_STATE_CHANGED"; + case EVENT_TYPE_VOLUME_FLAGS_CHANGED: + return "EVENT_TYPE_VOLUME_FLAGS_CHANGED"; + default: + return "EVENT_TYPE_UNKNOWN:" + type; + } + } +} + diff --git a/le_audio/packages/apps/Settings/Android.bp b/le_audio/packages/apps/Settings/Android.bp new file mode 100644 index 00000000000..c59967bbcee --- /dev/null +++ b/le_audio/packages/apps/Settings/Android.bp @@ -0,0 +1,4 @@ +filegroup { + name: "settings-bluetooth-adva-srcs", + srcs: ["src/**/*.java"], +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BADevicePreferenceController.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BADevicePreferenceController.java new file mode 100644 index 00000000000..7f79465ded5 --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BADevicePreferenceController.java @@ -0,0 +1,144 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * 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. + */ +package com.android.settings.bluetooth; + +import android.content.Context; +import android.content.pm.PackageManager; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceScreen; + +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settings.bluetooth.BluetoothDeviceUpdater; +import com.android.settings.bluetooth.SavedBluetoothDeviceUpdater; +import com.android.settings.connecteddevice.dock.DockUpdater; +import com.android.settings.connecteddevice.DevicePreferenceCallback; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; +import android.util.Log; +import androidx.annotation.Keep; + +@Keep +public class BADevicePreferenceController extends BasePreferenceController + implements LifecycleObserver, OnStart, OnStop, BleBroadcastSourceInfoPreferenceCallback { + + private static final String TAG = "BADevicePreferenceController"; + //Up to 3 Elements can be viewed here + private static final int MAX_DEVICE_NUM = 3; + + private PreferenceGroup mPreferenceGroup; + private BluetoothBroadcastSourceInfoEntries mBleSourceInfoUpdater; + private int mPreferenceSize; + private CachedBluetoothDevice mCachedDevice; + + public BADevicePreferenceController(Context context, Lifecycle lifecycle, String preferenceKey) { + super(context, preferenceKey); + + lifecycle.addObserver(this); + BroadcastScanAssistanceUtils.debug(TAG, "constructor: KEY" + preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH) + ) + ? AVAILABLE + : CONDITIONALLY_UNAVAILABLE; + } + + @Override + public String getPreferenceKey() { + return new String("added_sources"); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + BroadcastScanAssistanceUtils.debug(TAG, "displayPreference"); + super.displayPreference(screen); + mPreferenceGroup = screen.findPreference(getPreferenceKey()); + mPreferenceGroup.setVisible(false); + + if (isAvailable()) { + BroadcastScanAssistanceUtils.debug(TAG, "registering wth BleSrcInfo updaters"); + final Context context = screen.getContext(); + if (mBleSourceInfoUpdater != null) { + mBleSourceInfoUpdater.setPrefContext(context); + } + } + } + + @Override + public void onStart() { + if (mBleSourceInfoUpdater != null) { + mBleSourceInfoUpdater.registerCallback(); + } + } + + @Override + public void onStop() { + if (mBleSourceInfoUpdater != null) { + mBleSourceInfoUpdater.unregisterCallback(); + } + } + + public void init(DashboardFragment fragment, CachedBluetoothDevice device) { + BroadcastScanAssistanceUtils.debug(TAG, "Init"); + mCachedDevice = device; + mBleSourceInfoUpdater = new BluetoothBroadcastSourceInfoEntries(fragment.getContext(), + fragment, BADevicePreferenceController.this, + device); + mPreferenceSize = 0; + } + + @Override + public void onBroadcastSourceInfoAdded(Preference preference) { + BroadcastScanAssistanceUtils.debug(TAG, "onBroadcastSourceInfoAdded"); + + if (mPreferenceSize < MAX_DEVICE_NUM) { + boolean ret = mPreferenceGroup.addPreference(preference); + BroadcastScanAssistanceUtils.debug(TAG, "addPreference returns" + ret); + mPreferenceSize++; + } + updatePreferenceVisiblity(); + } + + @Override + public void onBroadcastSourceInfoRemoved(Preference preference) { + BroadcastScanAssistanceUtils.debug(TAG, "onBroadcastSourceInfoRemoved"); + mPreferenceSize--; + boolean ret = mPreferenceGroup.removePreference(preference); + BroadcastScanAssistanceUtils.debug(TAG, "removePreference returns " + ret); + updatePreferenceVisiblity(); + } + + @VisibleForTesting + void setPreferenceGroup(PreferenceGroup preferenceGroup) { + mPreferenceGroup = preferenceGroup; + } + + @VisibleForTesting + void updatePreferenceVisiblity() { + BroadcastScanAssistanceUtils.debug(TAG, "updatePreferenceVisiblity:" + mPreferenceSize); + mPreferenceGroup.setVisible(mPreferenceSize > 0); + } +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoDetailsController.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoDetailsController.java new file mode 100644 index 00000000000..e470b29f084 --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoDetailsController.java @@ -0,0 +1,679 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * 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. + */ + +package com.android.settings.bluetooth; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.BleBroadcastAudioScanAssistManager; +import android.bluetooth.BleBroadcastSourceChannel; +import android.bluetooth.BleBroadcastSourceChannel; + +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; +import java.lang.String; +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; +import androidx.preference.EditTextPreference; +import androidx.preference.MultiSelectListPreference; +import com.android.settingslib.widget.ActionButtonsPreference; + +import com.android.settingslib.bluetooth.A2dpProfile; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.VendorCachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothProfile; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.bluetooth.MapProfile; +import com.android.settingslib.bluetooth.PanProfile; +import com.android.settingslib.bluetooth.PbapServerProfile; +import com.android.settingslib.core.lifecycle.Lifecycle; +import androidx.appcompat.app.AlertDialog; +import android.text.Html; +import android.text.TextUtils; +import android.content.DialogInterface; +import android.widget.Toast; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.HashSet; +import java.util.Arrays; +import java.util.Iterator; +import com.android.settings.R; +import java.util.Map; + +/** + * This class Broadcast Source Info details of the given Scan delegator + */ +public class BleBroadcastSourceInfoDetailsController extends BluetoothDetailsController + implements Preference.OnPreferenceClickListener, + Preference.OnPreferenceChangeListener, CachedBluetoothDevice.Callback { + private static final String TAG = "BleBroadcastSourceInfoDetailsController"; + private final String EMPTY_BD_ADDRESS = "00:00:00:00:00:00"; + + //Display controls + private static final String KEY_SOURCE_INFO_GROUP = "broadcast_source_details_category"; + private static final String KEY_SOURCE_ID = "broadcast_si_sourceId"; + private static final String KEY_SOURCE_DEVICE = "broadcast_si_source_address"; + private static final String KEY_SOURCE_ENC_STATUS = "broadcast_si_encryption_state"; + private static final String KEY_SOURCE_METADATA = "broadcast_si_metadata"; + private static final String KEY_SOURCE_METADATA_STATE = "broadcast_si_metadata_state"; + private static final String KEY_SOURCE_AUDIO_STATE = "broadcast_si_audio_state"; + + //Input Controls + private static final String KEY_SOURCE_METADATA_SWITCH = "broadcast_si_enable_metadata_sync"; + private static final String KEY_SOURCE_AUDIOSYNC_SWITCH = "broadcast_si_enable_audio_sync"; + private static final String KEY_UPDATE_BCAST_CODE = "update_broadcast_code"; + private static final String KEY_UPDATE_SOURCE_INFO = "bcast_si_update_button"; + private static final String KEY_REMOVE_SOURCE_INFO = "bcast_si_remove_button"; + + private CachedBluetoothDevice mCachedDevice; + private VendorCachedBluetoothDevice mVendorCachedDevice; + private PreferenceCategory mSourceInfoContainer; + + private Preference mSourceIdPref; + private Preference mSourceDevicePref; + private Preference mSourceEncStatusPref; + private Preference mSourceMetadataPref; + private Preference mSourceMetadataSyncStatusPref; + private MultiSelectListPreference mSourceAudioSyncStatusPref; + private SwitchPreference mSourceMetadataSyncSwitchPref; + private SwitchPreference mSourceAudioSyncSwitchPref; + private EditTextPreference mSourceUpdateBcastCodePref; + private ActionButtonsPreference mSourceUpdateSourceInfoPref; + private ActionButtonsPreference mSourceRemoveSourceInfoPref; + private boolean mIsValueChanged = false; + private BleBroadcastSourceInfo mBleBroadcastSourceInfo; + private BleBroadcastAudioScanAssistManager mScanAssistanceMgr; + private boolean isBroadcastPINUpdated = false; + private String mBroadcastCode; + private int mSourceInfoIndex; + private String EMPTY_ENTRY = "EMPTY ENTRY"; + private int mMetadataSyncState; + private int mAudioSyncState; + private boolean mIsButtonRefreshOnly = false; + private boolean mGroupOp = false; + private AlertDialog mScanAssistGroupOpDialog = null; + private List mBisIndicies; + private boolean mPAsyncCtrlNeeded = false; + + public BleBroadcastSourceInfoDetailsController(Context context, + PreferenceFragmentCompat fragment, + BleBroadcastSourceInfo bleSourceInfo, CachedBluetoothDevice device, + int sourceInfoIndex, Lifecycle lifecycle) { + super(context, fragment, device, lifecycle); + Context mContext = context; + mBleBroadcastSourceInfo = bleSourceInfo; + mCachedDevice = device; + LocalBluetoothManager mgr = Utils.getLocalBtManager(context); + LocalBluetoothProfileManager profileManager = mgr.getProfileManager(); + mVendorCachedDevice = VendorCachedBluetoothDevice.getVendorCachedBluetoothDevice(device, profileManager); + mScanAssistanceMgr = mVendorCachedDevice.getScanAssistManager(); + lifecycle.addObserver(this); + mSourceInfoIndex = sourceInfoIndex; + clearInputs(); + mPAsyncCtrlNeeded = false; + } + + private void clearInputs() + { //Keep the default state of Metadata as ON always + mMetadataSyncState = + BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC; + mAudioSyncState = + BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_INVALID; + mBroadcastCode = null; + isBroadcastPINUpdated = false; + } + + private void triggerRemoveBroadcastSource() { + if (mScanAssistanceMgr != null) { + mScanAssistanceMgr.removeBroadcastSource( + mBleBroadcastSourceInfo.getSourceId(), mGroupOp); + } + } + + private void onRemoveBroadcastSourceInfoPressed() { + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":onRemoveBroadcastSourceInfoPressed:" + + mBleBroadcastSourceInfo); + + ///*_CSIP + if (mCachedDevice.isGroupDevice()) { + String name = mCachedDevice.getName(); + if (TextUtils.isEmpty(name)) { + name = mContext.getString(R.string.bluetooth_device); + } + String message = mContext.getString(R.string.group_remove_source_message, name); + String title = mContext.getString(R.string.group_remove_source_title); + + DialogInterface.OnClickListener groupOpListener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (mScanAssistGroupOpDialog != null) { + mScanAssistGroupOpDialog.dismiss(); + } + mGroupOp = true; + triggerRemoveBroadcastSource(); + } + }; + DialogInterface.OnClickListener nonGroupOpListener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (mScanAssistGroupOpDialog != null) { + mScanAssistGroupOpDialog.dismiss(); + } + + mGroupOp = false; + triggerRemoveBroadcastSource(); + } + }; + mGroupOp = false; + mScanAssistGroupOpDialog = BroadcastScanAssistanceUtils.showAssistanceGroupOptionsDialog(mContext, + mScanAssistGroupOpDialog, groupOpListener, nonGroupOpListener, title, Html.fromHtml(message)); + } else { + //_CSIP*/ + mGroupOp = false; + triggerRemoveBroadcastSource(); + ///*_CSIP + } + //_CSIP*/ + } + + private int getSyncState(int metadataSyncState, int audioSyncState) { + + if (audioSyncState == BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED && + metadataSyncState == BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC) { + return BleBroadcastAudioScanAssistManager.SYNC_METADATA_AUDIO; + } + + if (audioSyncState == BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED && + metadataSyncState != BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC) { + return BleBroadcastAudioScanAssistManager.SYNC_AUDIO; + } + if (audioSyncState != BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED && + metadataSyncState == BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC) { + return BleBroadcastAudioScanAssistManager.SYNC_METADATA; + } + + return -1; + } + + private void triggerUpdateBroadcastSource() { + if (mScanAssistanceMgr != null) { + if (mIsValueChanged == true) { + int syncState = getSyncState(mMetadataSyncState, mAudioSyncState); + if (syncState == -1) { + Log.e(TAG, "triggerUpdateBroadcastSource: Invalid sync Input, Ignore"); + return; + } + mScanAssistanceMgr.updateBroadcastSource( + mBleBroadcastSourceInfo.getSourceId(), + getSyncState(mMetadataSyncState, mAudioSyncState), + mBisIndicies, mGroupOp); + mIsValueChanged = false; + } + if (isBroadcastPINUpdated) { + mScanAssistanceMgr.setBroadcastCode( + mBleBroadcastSourceInfo.getSourceId(),mBroadcastCode, mGroupOp); + isBroadcastPINUpdated = false; + } + clearInputs(); + } + } + + private void onUpdateBroadcastSourceInfoPressed() { + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + + "onUpdateBroadcastSourceInfoPressed:" + mBleBroadcastSourceInfo); + + ///*_CSIP + if (mCachedDevice.isGroupDevice()) { + String name = mCachedDevice.getName(); + if (TextUtils.isEmpty(name)) { + name = mContext.getString(R.string.bluetooth_device); + } + String message = mContext.getString(R.string.group_update_source_message, name); + String title = mContext.getString(R.string.group_update_source_title); + + DialogInterface.OnClickListener groupOpListener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mGroupOp = true; + triggerUpdateBroadcastSource(); + } + }; + DialogInterface.OnClickListener nonGroupOpListener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mGroupOp = false; + triggerUpdateBroadcastSource(); + } + }; + mGroupOp = false; + mScanAssistGroupOpDialog = BroadcastScanAssistanceUtils.showAssistanceGroupOptionsDialog(mContext, + mScanAssistGroupOpDialog, groupOpListener, nonGroupOpListener, title, Html.fromHtml(message)); + } else { + //_CSIP*/ + mGroupOp = false; + triggerUpdateBroadcastSource(); + ///*_CSIP + } + //_CSIP*/ + } + + @Override + protected void init(PreferenceScreen screen) { + mSourceInfoContainer = + (PreferenceCategory)screen.findPreference(getPreferenceKey()); + mSourceIdPref = (Preference)mSourceInfoContainer.findPreference( + KEY_SOURCE_ID); + mSourceDevicePref = (Preference)mSourceInfoContainer.findPreference( + KEY_SOURCE_DEVICE); + mSourceEncStatusPref = (Preference)mSourceInfoContainer.findPreference( + KEY_SOURCE_ENC_STATUS); + mSourceMetadataPref = (Preference)mSourceInfoContainer.findPreference( + KEY_SOURCE_METADATA); + mSourceMetadataSyncStatusPref = (Preference)mSourceInfoContainer.findPreference( + KEY_SOURCE_METADATA_STATE); + if (mPAsyncCtrlNeeded) { + mSourceMetadataSyncSwitchPref = (SwitchPreference)mSourceInfoContainer.findPreference( + KEY_SOURCE_METADATA_SWITCH); + + if (mSourceMetadataSyncSwitchPref != null) { + mSourceMetadataSyncSwitchPref.setOnPreferenceClickListener(this); + } + } + mSourceAudioSyncStatusPref = (MultiSelectListPreference)mSourceInfoContainer.findPreference( + KEY_SOURCE_AUDIO_STATE); + if (mSourceAudioSyncStatusPref != null) { + mSourceAudioSyncStatusPref.setOnPreferenceChangeListener(this); + } + + mSourceAudioSyncSwitchPref = (SwitchPreference)mSourceInfoContainer.findPreference( + KEY_SOURCE_AUDIOSYNC_SWITCH); + if (mSourceAudioSyncSwitchPref != null) { + mSourceAudioSyncSwitchPref.setOnPreferenceClickListener(this); + } + mSourceUpdateBcastCodePref = + (EditTextPreference)mSourceInfoContainer.findPreference( + KEY_UPDATE_BCAST_CODE); + if (mSourceUpdateBcastCodePref != null) { + mSourceUpdateBcastCodePref.setOnPreferenceClickListener(this); + mSourceUpdateBcastCodePref.setOnPreferenceChangeListener(this); + } + mSourceUpdateSourceInfoPref = + ((ActionButtonsPreference)mSourceInfoContainer.findPreference( + KEY_UPDATE_SOURCE_INFO)) + .setButton1Text(R.string.update_sourceinfo_btn_txt) + .setButton1Enabled(false) + .setButton1OnClickListener((view)->onUpdateBroadcastSourceInfoPressed()) + .setButton2Text(R.string.remove_sourceinfo_btn_txt) + .setButton2Icon(R.drawable.ic_settings_close) + .setButton2Enabled(false) + .setButton2OnClickListener((view)->onRemoveBroadcastSourceInfoPressed()); + refresh(); + } + + @Override + public void onDeviceAttributesChanged() { + //update the Local variable If the receiverState is + //updated with some values + final Map srcInfos = + mVendorCachedDevice.getAllBleBroadcastreceiverStates(); + if (srcInfos == null) { + return; + } + for (Map.Entry entry: srcInfos.entrySet()) { + Integer index = entry.getKey(); + BleBroadcastSourceInfo sourceInfo = entry.getValue(); + String toastString = null; + if (index == mSourceInfoIndex) { + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":matching source Info"); + if (sourceInfo.isEmptyEntry()) { + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":source info seem to be removed"); + toastString = "Source Info Removal"; + mBleBroadcastSourceInfo = sourceInfo; + } + else if (sourceInfo.equals(mBleBroadcastSourceInfo) != true) { + //toast Message + mBleBroadcastSourceInfo = sourceInfo; + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":Update in Broadcast Source Information"); + toastString = "Source Info Update"; + } else { + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":No Update to Source Information values"); + } + } else { + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":Ignore this case"); + } + if (toastString != null) { + Toast toast = Toast.makeText(mContext, toastString, Toast.LENGTH_SHORT); + toast.show(); + } + } + refresh(); + } + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + String key = preference.getKey(); + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":onPreferenceChange" + newValue); + if (key.equals(KEY_UPDATE_BCAST_CODE)) { + EditTextPreference pref = (EditTextPreference)preference; + String code = (String)newValue; + //Use different flag for Broadcast pin + isBroadcastPINUpdated = true; + mBroadcastCode = (String)newValue; + } else if (key.equals(KEY_SOURCE_AUDIO_STATE)) { + BroadcastScanAssistanceUtils.debug(TAG, ">>Checked:" +newValue); + CharSequence[] getEntriesSeqence = + ((MultiSelectListPreference)preference).getEntries(); + Set valueSet = ((MultiSelectListPreference)preference).getValues(); + + String[] selectedStrings = new String[((Set) newValue).size()]; + + //noinspection unchecked + int j =0; + for (String value : (Set) newValue) { + selectedStrings[j] = value; + for (int i=0; i>Pin code updated: " + pref.getText()); + //Use different flag for Broadcast pin + mIsValueChanged = false; + isBroadcastPINUpdated = true; + mBroadcastCode = pref.getText(); + } else { + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":unhandled preference"); + mIsValueChanged = false; + } + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":onPreferenceClick" + mBleBroadcastSourceInfo); + mIsButtonRefreshOnly = true; + refresh(); + return true; + } + + @Override + public void onPause() { + super.onPause(); + mCachedDevice.unregisterCallback(this); + } + + @Override + public void onResume() { + super.onResume(); + mCachedDevice.registerCallback(this); + } + + String getEncryptionStatusString(int encryptionStatus) { + + switch(encryptionStatus) { + case BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_INVALID: + return "ENCRYPTION STATE UNKNOWN"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_UNENCRYPTED: + return "UNENCRYPTED STREAMING"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED: + return "PIN UPDATE NEEDED"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_DECRYPTING: + return "DECRYPTING SUCCESSFULLY"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_BADCODE: + return "INCORRECT BROADCAST PIN"; + } + return "ENCRYPTION STATE UNKNOWN"; + } + + String getMetadataSyncStatusString(int metadataSyncStatus) { + + switch(metadataSyncStatus) { + case BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IDLE: + return "IDLE"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_INVALID: + return "UNKNOWN"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC: + return "IN SYNC"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_NO_PAST: + return "NO PAST"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_SYNCINFO_REQ: + return "SYNCINFO NEEDED"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_SYNC_FAIL: + return "SYNC FAIL"; + } + return "UNKNOWN"; + } + + String getAudioSyncStatusString(int audioSyncStatus) { + + switch(audioSyncStatus) { + case BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_INVALID: + return "UNKNOWN"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED: + return "NOT IN SYNC"; + case BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED: + return "IN SYNC"; + } + return "UNKNOWN"; + } + + private boolean isPinUpdatedNeeded() { + boolean ret = false; + + if (BroadcastScanAssistanceUtils.isLocalDevice(mBleBroadcastSourceInfo.getSourceDevice())) { + BroadcastScanAssistanceUtils.debug(TAG, "Local Device, Dont allow User to update PWD"); + return false; + } + if (mBleBroadcastSourceInfo.getEncryptionStatus() + == BleBroadcastSourceInfo.BROADCAST_ASSIST_ENC_STATE_PIN_NEEDED) { + ret = true; + } + + BroadcastScanAssistanceUtils.debug(TAG, "isPinUpdatedNeeded return" + ret); + return ret; + } + + /** + * Refreshes the state of the switches for all profiles, possibly adding or removing switches as + * needed. + */ + @Override + protected void refresh() { + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":refresh: " + mBleBroadcastSourceInfo + " mSourceIndex" + mSourceInfoIndex); + mSourceIdPref.setSummary( + String.valueOf(mBleBroadcastSourceInfo.getSourceId())); + + BluetoothDevice dev = mBleBroadcastSourceInfo.getSourceDevice(); + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + String s = null; + if (dev != null && adapter != null) { + if (adapter.getAddress().equals(dev.getAddress())) + { + s = adapter.getName() + "(Self)"; + } else { + s = dev.getAlias(); + } + if (s == null) { + s = String.valueOf(dev.getAddress()); + } + } + if (s == null || s.equals(EMPTY_BD_ADDRESS)) { + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":NULL source device"); + s = "EMPTY_ENTRY"; + } + mSourceDevicePref.setSummary(s); + mSourceEncStatusPref.setSummary( + getEncryptionStatusString( + mBleBroadcastSourceInfo.getEncryptionStatus()) + ); + + if (mBleBroadcastSourceInfo.isEmptyEntry()) { + BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":Source Information seem to be Empty"); + if (mPAsyncCtrlNeeded) { + mSourceMetadataSyncSwitchPref.setEnabled(false); + } + mSourceAudioSyncSwitchPref.setEnabled(false); + mSourceUpdateBcastCodePref.setEnabled(false); + //Disable 'remove and update source Info' if It is empty entry + mSourceUpdateSourceInfoPref.setButton1Enabled(false); + mSourceUpdateSourceInfoPref.setButton2Enabled(false); + mSourceAudioSyncStatusPref.setEnabled(false); + mIsValueChanged = false; + } else { + //enable the Input controls + if (mPAsyncCtrlNeeded) { + mSourceMetadataSyncSwitchPref.setEnabled(true); + } + mSourceAudioSyncSwitchPref.setEnabled(true); + mSourceUpdateBcastCodePref.setEnabled(isPinUpdatedNeeded()); + + if (mIsButtonRefreshOnly != true) { + mSourceMetadataSyncStatusPref.setSummary( + getMetadataSyncStatusString(mBleBroadcastSourceInfo.getMetadataSyncState()) + ); + mSourceAudioSyncStatusPref.setSummary( + getAudioSyncStatusString(mBleBroadcastSourceInfo.getAudioSyncState()) + ); + mBisIndicies = mBleBroadcastSourceInfo.getBroadcastChannelsSyncStatus(); + if (mBisIndicies != null) { + String[] bisNames = new String[mBisIndicies.size()]; + boolean[] bisStatuses = new boolean[mBisIndicies.size()]; + Set hashSet = new HashSet(); + for (int i=0; i createPreferenceControllers(Context context) { + ArrayList controllers = new ArrayList<>(); + + if (mCachedDevice != null && mBleBroadcastSourceInfo != null) { + Lifecycle lifecycle = getSettingsLifecycle(); + controllers.add(new BleBroadcastSourceInfoDetailsController(context, this, mBleBroadcastSourceInfo, + mCachedDevice, mSourceInfoIndex, lifecycle)); + } + return controllers; + } +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoPreference.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoPreference.java new file mode 100644 index 00000000000..50c4636a93e --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoPreference.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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. + */ + +package com.android.settings.bluetooth; + +import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; + +import android.app.settings.SettingsEnums; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BleBroadcastSourceInfo; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.UserManager; +import android.text.Html; +import android.text.TextUtils; +import android.util.Pair; +import android.util.TypedValue; +import android.view.View; +import android.widget.ImageView; +import android.util.Log; + +import androidx.annotation.IntDef; +import androidx.annotation.VisibleForTesting; +import androidx.appcompat.app.AlertDialog; +import androidx.preference.Preference; +import androidx.preference.PreferenceViewHolder; + +import com.android.settings.R; +import com.android.settings.overlay.FeatureFactory; +import com.android.settings.widget.GearPreference; +import com.android.settingslib.bluetooth.BluetoothUtils; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.Integer; +import java.lang.String; +/** + * BleBroadcastSourceInfoPreference is the preference type used to display each + * Broadcast Source information stored in the Remote Scan delegator. + */ +public final class BleBroadcastSourceInfoPreference extends GearPreference implements + CachedBluetoothDevice.Callback { + private static final String TAG = "BleBroadcastSourceInfoPreference"; + + private static String EMPTY_BD_ADDR = "00:00:00:00:00:00"; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({SortType.TYPE_DEFAULT, + SortType.TYPE_FIFO}) + public @interface SortType { + int TYPE_DEFAULT = 1; + int TYPE_FIFO = 2; + } + + private final CachedBluetoothDevice mCachedDevice; + private BleBroadcastSourceInfo mBleSourceInfo; + private final Integer mIndex; + private final long mCurrentTime; + private final int mType; + + ///private String contentDescription = null; + //@VisibleForTesting + //boolean mNeedNotifyHierarchyChanged = false; + /* Talk-back descriptions for various BT icons */ + Resources mResources; + + public BleBroadcastSourceInfoPreference(Context context, CachedBluetoothDevice device, + BleBroadcastSourceInfo sourceInfo, + Integer index, @SortType int type) { + super(context, null); + mResources = getContext().getResources(); + mIndex = index; + + mCachedDevice = device; + mBleSourceInfo = sourceInfo; + mCachedDevice.registerCallback(this); + mCurrentTime = System.currentTimeMillis(); + mType = type; + + onDeviceAttributesChanged(); + } + + + @Override + protected boolean shouldHideSecondTarget() { + return (mBleSourceInfo == null); + } + + @Override + protected int getSecondTargetResId() { + return R.layout.preference_widget_gear; + } + + CachedBluetoothDevice getCachedDevice() { + return mCachedDevice; + } + + public BleBroadcastSourceInfo getBleBroadcastSourceInfo() { + return mBleSourceInfo; + } + + public void setBleBroadcastSourceInfo(BleBroadcastSourceInfo srcInfo) { + mBleSourceInfo = srcInfo; + //refresh + onDeviceAttributesChanged(); + } + + Integer getSourceInfoIndex() { + return mIndex; + } + + @Override + protected void onPrepareForRemoval() { + super.onPrepareForRemoval(); + mCachedDevice.unregisterCallback(this); + } + + String formSyncSummaryString(BleBroadcastSourceInfo srcInfo) { + String metadataStatus = "Metadata Synced"; + String audioSyncStatus = "Audio Synced"; + + if (srcInfo.getMetadataSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC) { + metadataStatus = "Metadata Synced"; + } else { + metadataStatus = "Metadata not synced"; + } + + if (srcInfo.getAudioSyncState() == BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED) { + audioSyncStatus = "Audio Synced"; + } else { + audioSyncStatus = "Audio not synced"; + } + return metadataStatus + ", " + audioSyncStatus; + } + + public void onDeviceAttributesChanged() { + BluetoothDevice dev = mBleSourceInfo.getSourceDevice(); + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + String s = null; + if (dev != null && adapter != null) { + if (adapter.getAddress().equals(dev.getAddress())) + { + s = adapter.getName() + "(Self)"; + } else { + s = dev.getAlias(); + } + if (s == null) { + s = String.valueOf(dev.getAddress()); + } + } + if (s == null || s.equals(EMPTY_BD_ADDR)) { + BroadcastScanAssistanceUtils.debug(TAG, "seem to be an entry source Info"); + s = "EMPTY ENTRY"; + } + setTitle(s); + setIcon(R.drawable.ic_media_stream); + if (!mBleSourceInfo.isEmptyEntry()) { + //Show the status only If it is not an Empty Entry + setSummary(formSyncSummaryString(mBleSourceInfo)); + } else { + setSummary(""); + } + setVisible(true); + + // This could affect ordering, so notify that + notifyHierarchyChanged(); + } + + + + @Override + public boolean equals(Object o) { + if ((o == null) || !(o instanceof BleBroadcastSourceInfoPreference)) { + BroadcastScanAssistanceUtils.debug(TAG, "Not an Instance of BleBroadcastSourceInfoPreference:"); + return false; + } + BleBroadcastSourceInfo otherSrc = ((BleBroadcastSourceInfoPreference) o).mBleSourceInfo; + BroadcastScanAssistanceUtils.debug(TAG, "Comparing: " + mBleSourceInfo); + BroadcastScanAssistanceUtils.debug(TAG, "TO: " + otherSrc); + boolean ret = (mBleSourceInfo.getSourceId() == otherSrc.getSourceId()); + BroadcastScanAssistanceUtils.debug(TAG, "equals returns: " + ret); + + return ret; + } + + @Override + public int hashCode() { + return mBleSourceInfo.hashCode(); + } + + @Override + public int compareTo(Preference another) { + if (!(another instanceof BleBroadcastSourceInfoPreference)) { + // Rely on default sort + return super.compareTo(another); + } + + switch (mType) { + case SortType.TYPE_DEFAULT: + BroadcastScanAssistanceUtils.debug(TAG, ">>compareTo"); + return mIndex > ((BleBroadcastSourceInfoPreference) another).getSourceInfoIndex() ? 1 : -1; + case SortType.TYPE_FIFO: + return mCurrentTime > ((BleBroadcastSourceInfoPreference) another).mCurrentTime ? 1 : -1; + default: + return super.compareTo(another); + } + } + + void onClicked() { + Context context = getContext(); + + final MetricsFeatureProvider metricsFeatureProvider = + FeatureFactory.getFactory(context).getMetricsFeatureProvider(); + + } +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoPreferenceCallback.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoPreferenceCallback.java new file mode 100644 index 00000000000..8f9a4fb50fc --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoPreferenceCallback.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * 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. + */ + +package com.android.settings.bluetooth; + +import androidx.preference.Preference; + +/** + * Callback to add or remove {@link Preference} in Ble broadcast source info + * entries. + */ +public interface BleBroadcastSourceInfoPreferenceCallback { + /** + * Called when a Ble broadcast sourc Information is added + * @param preference present the device + */ + void onBroadcastSourceInfoAdded(Preference preference); + + /** + * Called when a Ble broadast source Information is removed + * @param preference present the device + */ + void onBroadcastSourceInfoRemoved(Preference preference); +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoUpdater.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoUpdater.java new file mode 100644 index 00000000000..6baa20a687a --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoUpdater.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * 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. + */ +package com.android.settings.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BleBroadcastSourceInfo; +import android.content.Context; +import java.util.Iterator; +import android.os.Bundle; +import android.util.Log; + +import androidx.preference.Preference; + +import com.android.settings.R; +import com.android.settings.connecteddevice.DevicePreferenceCallback; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.widget.GearPreference; +import com.android.settingslib.bluetooth.BluetoothCallback; +import com.android.settingslib.bluetooth.BluetoothDeviceFilter; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.VendorCachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.settingslib.bluetooth.LocalBluetoothManager; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.lang.Integer; + +/** + * Update the Ble broadcast source Info preference entries. It retrieves the Bluetooth broadcast source + * information using CachedBluetoothDevice object from setting library + * {@link BluetoothCallback}. It notifies the upper level whether to add/remove the preference + * through {@link BleBroadcastSourceInfoPreferenceCallback} + * + * In {@link BleBroadcastSourceInfoUpdater}, it uses {@link BluetoothDeviceFilter.Filter} to detect + * whether the {@link CachedBluetoothDevice} is relevant. + */ +public abstract class BleBroadcastSourceInfoUpdater implements CachedBluetoothDevice.Callback, + BluetoothCallback { + private static final String TAG = "BleBroadcastSourceInfoUpdater"; + private static final boolean DBG = false; + + protected final BleBroadcastSourceInfoPreferenceCallback mBleSourceInfoPreferenceCallback; + protected final Map mPreferenceMap; + protected Context mPrefContext; + protected DashboardFragment mFragment; + protected final CachedBluetoothDevice mCachedDevice; + protected final VendorCachedBluetoothDevice mVendorCachedDevice; + private LocalBluetoothManager mLocalManager; + + final GearPreference.OnGearClickListener mSourceInfoEntryListener = pref -> { + launchSourceInfoDetails(pref); + }; + + public BleBroadcastSourceInfoUpdater(Context context, DashboardFragment fragment, + BleBroadcastSourceInfoPreferenceCallback aBleSourceInfoPreferenceCallback, + CachedBluetoothDevice device) { + this(fragment, aBleSourceInfoPreferenceCallback ,device); + } + + BleBroadcastSourceInfoUpdater(DashboardFragment fragment, + BleBroadcastSourceInfoPreferenceCallback aBleSourceInfoPreferenceCallback, + CachedBluetoothDevice device) { + mCachedDevice = device; + LocalBluetoothManager mgr = Utils.getLocalBtManager(mPrefContext); + LocalBluetoothProfileManager profileManager = mgr.getProfileManager(); + mVendorCachedDevice = VendorCachedBluetoothDevice.getVendorCachedBluetoothDevice(device, profileManager); + mFragment = fragment; + mBleSourceInfoPreferenceCallback = aBleSourceInfoPreferenceCallback; + mPreferenceMap = new HashMap(); + mLocalManager = Utils.getLocalBtManager(mPrefContext); + mLocalManager.getEventManager().registerCallback(this); + } + + /** + * Register the bluetooth event callback and update the list + */ + public void registerCallback() { + mCachedDevice.registerCallback(this); + forceUpdate(); + } + + /** + * Unregister the bluetooth event callback + */ + public void unregisterCallback() { + mCachedDevice.unregisterCallback(this); + } + + @Override + public void onBluetoothStateChanged(int bluetoothState) { + BroadcastScanAssistanceUtils.debug(TAG, "onBluetoothStateChanged"); + if (bluetoothState == BluetoothAdapter.STATE_OFF) { + removeAllBleBroadcastSourceInfosFromPreference(); + } + //forceUpdate(); + } + + /** + * Force to update the list of bluetooth devices + */ + public void forceUpdate() { + if (mCachedDevice != null && + mVendorCachedDevice.getNumberOfBleBroadcastReceiverStates() > 0) { + final Map srcInfos = + mVendorCachedDevice.getAllBleBroadcastreceiverStates(); + if (srcInfos == null) { + Log.e(TAG, "srcInfos is null"); + return; + } + for (Map.Entry entry: srcInfos.entrySet()) { + update(entry.getKey(), entry.getValue()); + } + } else { + BroadcastScanAssistanceUtils.debug(TAG, "remove all the preferences as there are no rcvr states"); + removeAllBleBroadcastSourceInfosFromPreference(); + } + } + + public void removeAllBleBroadcastSourceInfosFromPreference() { + Iterator> entries = mPreferenceMap.entrySet().iterator(); + while (entries.hasNext()) { + //for (Map.Entry entry: mPreferenceMap.entrySet()) { + Map.Entry entry = entries.next(); + //removePreference(entry.getKey(), entry.getValue()); + mBleSourceInfoPreferenceCallback.onBroadcastSourceInfoRemoved(entry.getValue()); + } + mPreferenceMap.clear(); + } + + @Override + public void onDeviceAttributesChanged() { + BroadcastScanAssistanceUtils.debug(TAG, "onDeviceAttributesChanged"); + forceUpdate(); + } + + /** + * Set the context to generate the {@link Preference}, so it could get the correct theme. + */ + public void setPrefContext(Context context) { + mPrefContext = context; + } + + /** + * Update whether to show {@link CachedBluetoothDevice} in the list. + */ + protected void update(Integer index, BleBroadcastSourceInfo sourceInfo) { + addPreference(index, sourceInfo); + } + + /** + * Add the {@link Preference} that represents the {@code cachedDevice} + */ + protected void addPreference(Integer index, BleBroadcastSourceInfo sourceInfo) { + final BluetoothDevice device = sourceInfo.getSourceDevice(); + final byte sourceId = sourceInfo.getSourceId(); + if (mPreferenceMap.containsKey(index) == false) { + BroadcastScanAssistanceUtils.debug(TAG, "source info addition"); + BleBroadcastSourceInfoPreference sourceInfoPreference = + new BleBroadcastSourceInfoPreference(mPrefContext, + mCachedDevice, + sourceInfo, + index, + BleBroadcastSourceInfoPreference.SortType.TYPE_DEFAULT); + sourceInfoPreference.setOnGearClickListener(mSourceInfoEntryListener); + if (this instanceof Preference.OnPreferenceClickListener) { + sourceInfoPreference.setOnPreferenceClickListener( + (Preference.OnPreferenceClickListener)this); + } + BroadcastScanAssistanceUtils.debug(TAG, "source info newly added: " + index); + mPreferenceMap.put(index, sourceInfoPreference); + mBleSourceInfoPreferenceCallback.onBroadcastSourceInfoAdded(sourceInfoPreference); + } else { + BleBroadcastSourceInfoPreference pref = (BleBroadcastSourceInfoPreference)mPreferenceMap.get(index); + BleBroadcastSourceInfo currentSi = pref.getBleBroadcastSourceInfo(); + if (currentSi != null && currentSi.equals(sourceInfo)) { + BroadcastScanAssistanceUtils.debug(TAG, "No change in SI" + index); + } else { + BroadcastScanAssistanceUtils.debug(TAG, "source info Updated: " + index); + pref.setBleBroadcastSourceInfo (sourceInfo); + + /*mBleSourceInfoPreferenceCallback.onBroadcastSourceInfoRemoved(mPreferenceMap.get(index)); + mPreferenceMap.remove(index); + + BleBroadcastSourceInfoPreference sourceInfoPreference = + new BleBroadcastSourceInfoPreference(mPrefContext, + mCachedDevice, + sourceInfo, + index, + BleBroadcastSourceInfoPreference.SortType.TYPE_DEFAULT); + sourceInfoPreference.setOnGearClickListener(mSourceInfoEntryListener); + if (this instanceof Preference.OnPreferenceClickListener) { + sourceInfoPreference.setOnPreferenceClickListener( + (Preference.OnPreferenceClickListener)this); + } + BroadcastScanAssistanceUtils.debug(TAG, "source info added again: " + index); + mPreferenceMap.put(index, sourceInfoPreference); + mBleSourceInfoPreferenceCallback.onBroadcastSourceInfoAdded(sourceInfoPreference);*/ + } + } + } + + /** + * Remove the {@link Preference} that represents the {@code cachedDevice} + */ + protected void removePreference(int index, Preference pref) { + if (mPreferenceMap.containsKey(index)) { + mBleSourceInfoPreferenceCallback.onBroadcastSourceInfoRemoved(mPreferenceMap.get(index)); + mPreferenceMap.remove(index); + } + } + + /** + * Get {@link CachedBluetoothDevice} from {@link Preference} and it is used to init + * {@link SubSettingLauncher} to launch {@link BluetoothDeviceDetailsFragment} + */ + protected void launchSourceInfoDetails(Preference preference) { + final BleBroadcastSourceInfo srcInfo = + ((BleBroadcastSourceInfoPreference) preference).getBleBroadcastSourceInfo(); + if (srcInfo == null) { + return; + } + final int index = ((BleBroadcastSourceInfoPreference) preference).getSourceInfoIndex(); + final Bundle args = new Bundle(); + args.putString(BleBroadcastSourceInfoDetailsFragment.KEY_DEVICE_ADDRESS, + mCachedDevice.getAddress()); + args.putParcelable(BleBroadcastSourceInfoDetailsFragment.KEY_SOURCE_INFO, + srcInfo); + args.putInt(BleBroadcastSourceInfoDetailsFragment.KEY_SOURCE_INFO_INDEX, + index); + + new SubSettingLauncher(mFragment.getContext()) + .setDestination(BleBroadcastSourceInfoDetailsFragment.class.getName()) + .setArguments(args) + .setTitleRes(R.string.source_info_details_title) + .setSourceMetricsCategory(mFragment.getMetricsCategory()) + .launch(); + } +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastEnableController.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastEnableController.java new file mode 100644 index 00000000000..df84afd814b --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastEnableController.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ + +package com.android.settings.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothBroadcast; +import android.content.Context; +import android.util.Log; +import android.os.SystemProperties; +import android.os.Handler; +import android.os.Message; + +import androidx.preference.PreferenceScreen; +import androidx.preference.SwitchPreference; +import com.android.settingslib.RestrictedSwitchPreference; +import com.android.settings.core.TogglePreferenceController; +import com.android.settingslib.bluetooth.BluetoothCallback; +import com.android.settingslib.bluetooth.BluetoothUtils; +import com.android.settingslib.bluetooth.BroadcastProfile; +import com.android.settingslib.bluetooth.LocalBluetoothProfile; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; +import com.android.settingslib.core.lifecycle.events.OnDestroy; +import com.android.settings.connecteddevice.BluetoothDashboardFragment; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import androidx.annotation.Keep; + +@Keep +public class BluetoothBroadcastEnableController extends TogglePreferenceController + implements LifecycleObserver, OnResume, OnPause, OnDestroy, BluetoothCallback { + + public static final String TAG = "BluetoothBroadcastEnableController"; + public static final int BROADCAST_AUDIO_MASK = 0x04; + public static final String BLUETOOTH_LE_AUDIO_MASK_PROP = "persist.vendor.service.bt.adv_audio_mask"; + public static final String KEY_BROADCAST_ENABLE = "bluetooth_screen_broadcast_enable"; + private RestrictedSwitchPreference mPreference = null; + private BluetoothAdapter mBluetoothAdapter; + private boolean mState = false; + private boolean reset_pending = false; + private Context mContext; + private BroadcastProfile mBapBroadcastProfile = null; + private boolean isBluetoothLeBroadcastAudioSupported = false; + private boolean mCallbacksRegistered = false; + private LocalBluetoothManager mManager = null; + public BluetoothBroadcastEnableController(Context context, String key) { + super(context, key); + Log.d(TAG, "Constructor() with key"); + Init(context); + } + + private void Init(Context context) { + mContext = context; + int leAudioMask = SystemProperties.getInt(BLUETOOTH_LE_AUDIO_MASK_PROP, 0); + isBluetoothLeBroadcastAudioSupported = ((leAudioMask & BROADCAST_AUDIO_MASK) == BROADCAST_AUDIO_MASK); + if(isBluetoothLeBroadcastAudioSupported){ + mManager = Utils.getLocalBtManager(context); + mBapBroadcastProfile = (BroadcastProfile) mManager.getProfileManager().getBroadcastProfile(); + if (!mCallbacksRegistered) { + Log.d(TAG, "Registering EventManager callbacks"); + mCallbacksRegistered = true; + mManager.getEventManager().registerCallback(this); + } + } + Log.d(TAG, "Init done"); + } + + private void updateState(boolean newState) { + Log.d(TAG, "updateState req " + Boolean.toString(newState)); + if (newState != mState) { + if (mPreference != null) mPreference.setEnabled(false); + Log.d(TAG, "updateState to " + Boolean.toString(newState)); + mBapBroadcastProfile.setBroadcastMode(newState); + } + } + + private void onStateChanged(boolean newState) { + Log.d(TAG, "onStateChanged " + Boolean.toString(newState)); + mState = newState; + if (mPreference != null) mPreference.setChecked(mState); + if (mState == false && reset_pending == true) { + reset_pending = false; + updateState(true); + } + } + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + Log.d(TAG, "BT state, msg = " + Integer.toString(msg.what)); + switch (msg.what) { + case BluetoothAdapter.STATE_ON: + if (mPreference != null) mPreference.setEnabled(true); + mBapBroadcastProfile = (BroadcastProfile) mManager.getProfileManager().getBroadcastProfile(); + break; + case BluetoothAdapter.STATE_TURNING_ON: + case BluetoothAdapter.STATE_OFF: + case BluetoothAdapter.STATE_TURNING_OFF: + reset_pending = false; + onStateChanged(false); + mBapBroadcastProfile = null; + if (mPreference != null) mPreference.setEnabled(false); + break; + } + } + }; + + @Override + public void onBluetoothStateChanged(int newBtState) { + Log.d(TAG, "onBluetoothStateChanged" + Integer.toString(newBtState)); + + switch (newBtState) { + case BluetoothAdapter.STATE_ON: + mHandler.sendMessageDelayed(mHandler.obtainMessage(newBtState), 200); + break; + case BluetoothAdapter.STATE_TURNING_ON: + case BluetoothAdapter.STATE_OFF: + case BluetoothAdapter.STATE_TURNING_OFF: + mHandler.sendMessage(mHandler.obtainMessage(newBtState)); + break; + } + } + + @Override + public void onBroadcastStateChanged(int newBapState) { + Log.d(TAG, "onBroadcastStateChanged" + Integer.toString(newBapState)); + + switch (newBapState) { + case BluetoothBroadcast.STATE_ENABLED: + if (mPreference != null) mPreference.setEnabled(true); + onStateChanged(true); + break; + case BluetoothBroadcast.STATE_DISABLED: + if (mPreference != null) mPreference.setEnabled(true); + onStateChanged(false); + break; + } + } + + @Override + public void onBroadcastKeyGenerated() { + Log.d(TAG, "onBroadcastKeyGenerated"); + // Encryption key got updated. Reset BAP? + if (mState == true) { + reset_pending = true; + updateState(false); + } + } + + @Override + public String getPreferenceKey() { + Log.d(TAG, "getPreferenceKey"); + return KEY_BROADCAST_ENABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + Log.d(TAG, "displayPreference"); + mPreference = screen.findPreference(getPreferenceKey()); + if(isBluetoothLeBroadcastAudioSupported) { + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + onBluetoothStateChanged(mBluetoothAdapter.getState()); + if ((mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) && + (mBapBroadcastProfile.isProfileReady())) { + int bapState = mBapBroadcastProfile.getBroadcastStatus(); + Log.d(TAG, "get status done"); + if ((bapState == BluetoothBroadcast.STATE_ENABLED) || + (bapState == BluetoothBroadcast.STATE_STREAMING)) + onStateChanged(true); + else + onStateChanged(false); + } + } else { + mPreference.setVisible(false); + } + } + + @Override + public boolean isChecked() { + return mState; + } + + @Override + public boolean setChecked(boolean isChecked) { + updateState(isChecked); + return true; + } + + @Override + public int getAvailabilityStatus() { + Log.d(TAG, "getAvailabilityStatus"); + if(isBluetoothLeBroadcastAudioSupported) { + return AVAILABLE; + } else { + return UNSUPPORTED_ON_DEVICE; + } + } + + @Override + public boolean hasAsyncUpdate() { + Log.d(TAG, "hasAsyncUpdate"); + return true; + } + + @Override + public boolean isPublicSlice() { + Log.d(TAG, "isPublicSlice"); + return true; + } + + @Override + public void onResume() { + Log.d(TAG, "onResume"); + } + + @Override + public void onPause() { + Log.d(TAG, "onPause"); + } + + @Override + public void onDestroy() { + Log.d(TAG, "onDestory"); + mCallbacksRegistered = false; + if (mManager != null) + mManager.getEventManager().unregisterCallback(this); + } +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastPinController.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastPinController.java new file mode 100644 index 00000000000..2ba78ddc29b --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastPinController.java @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ + + +package com.android.settings.bluetooth; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import android.app.settings.SettingsEnums; +import android.bluetooth.BluetoothBroadcast; +import android.content.Context; +import android.content.pm.PackageManager; +import android.util.Log; +import android.view.View; +import android.view.LayoutInflater; +import android.text.TextUtils; +import android.widget.TextView; +import android.bluetooth.BluetoothAdapter; +import android.os.Handler; +import android.os.SystemProperties; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; +import androidx.fragment.app.Fragment; +import androidx.preference.PreferenceScreen; + +import com.android.settingslib.bluetooth.BluetoothCallback; +import com.android.settingslib.bluetooth.BluetoothUtils; +import com.android.settingslib.bluetooth.BroadcastProfile; +import com.android.settingslib.bluetooth.LocalBluetoothProfile; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.settingslib.RestrictedPreference; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; +import com.android.settingslib.core.lifecycle.events.OnDestroy; +import com.android.settingslib.widget.LayoutPreference; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settings.R; +import com.android.settings.core.instrumentation.InstrumentedDialogFragment; +import androidx.annotation.Keep; + +/** + * Controller that shows Pin for BLE Broadcast Audio + */ +@Keep +public class BluetoothBroadcastPinController extends BasePreferenceController + implements OnDestroy, BluetoothCallback { + public static final String TAG = "BluetoothBroadcastPinController"; + public static final int BROADCAST_AUDIO_MASK = 0x04; + public static final String BLUETOOTH_LE_AUDIO_MASK_PROP = "persist.vendor.service.bt.adv_audio_mask"; + public static final String KEY_BROADCAST_AUDIO_PIN = "bluetooth_screen_broadcast_pin_configure"; + + private BluetoothAdapter mBluetoothAdapter; + private Fragment mFragment = null; + private MetricsFeatureProvider mMetricsFeatureProvider; + @VisibleForTesting + RestrictedPreference mPreference; + private Context mContext; + + private boolean isBluetoothLeBroadcastAudioSupported = false; + private boolean mCallbacksRegistered = false; + private LocalBluetoothManager mManager = null; + private Handler mHandler; + private Runnable mRunnable = new Runnable() { + @Override + public void run() { + onBroadcastKeyGenerated(); + } + }; + + public BluetoothBroadcastPinController(Context context) { + super(context, KEY_BROADCAST_AUDIO_PIN); + int leAudioMask = SystemProperties.getInt(BLUETOOTH_LE_AUDIO_MASK_PROP, 0); + isBluetoothLeBroadcastAudioSupported = ((leAudioMask & BROADCAST_AUDIO_MASK) == BROADCAST_AUDIO_MASK); + Log.d(TAG, "Constructor()"); + mContext = context; + mHandler = new Handler(); + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if(isBluetoothLeBroadcastAudioSupported) { + mManager = Utils.getLocalBtManager(context); + if (!mCallbacksRegistered) { + Log.d(TAG, "Registering EventManager callbacks"); + mCallbacksRegistered = true; + mManager.getEventManager().registerCallback(this); + } + } + } + + public BluetoothBroadcastPinController(Context context, PreferenceFragmentCompat fragment, String prefKey) { + super(context, KEY_BROADCAST_AUDIO_PIN); + Log.d(TAG, "PinController()" + prefKey); + mFragment = fragment; + mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); + } + + @VisibleForTesting + public void setFragment(Fragment fragment) { + Log.d(TAG, "setFragment"); + mFragment = fragment; + } + + @Override + public int getAvailabilityStatus() { + Log.d(TAG, "getAvailabilityStatus"); + if(isBluetoothLeBroadcastAudioSupported) { + return AVAILABLE; + } else { + return UNSUPPORTED_ON_DEVICE; + } + } + + @Override + public String getPreferenceKey() { + return KEY_BROADCAST_AUDIO_PIN; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + Log.d(TAG, "displayPreference"); + mPreference = screen.findPreference(getPreferenceKey()); + if(isBluetoothLeBroadcastAudioSupported) { + onBroadcastKeyGenerated(); + } else { + mPreference.setVisible(false); + } + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + Log.d(TAG, "PinController: handlePreferenceTreeClick"); + if (KEY_BROADCAST_AUDIO_PIN.equals(preference.getKey())) { + Log.d(TAG, "PinController: handlePreferenceTreeClick true"); + new BluetoothBroadcastPinFragment() + .show(mFragment.getFragmentManager(), "PinFragment"); + return true; + } + + return false; + } + + @Override + public void onBluetoothStateChanged(int newBtState) { + Log.d(TAG, "onBluetoothStateChanged" + Integer.toString(newBtState)); + int delay = 0; + switch (newBtState) { + case BluetoothAdapter.STATE_ON: + delay = 200; + case BluetoothAdapter.STATE_OFF: + mHandler.postDelayed(mRunnable, delay); + break; + } + } + + private String convertBytesToString(byte[] pin) { + if (pin.length != 16) { + Log.e (TAG, "Not 16 bytes ++++++++++++"); + return ""; + } + byte[] temp = new byte[16]; + int i = 0, j = 0; + // Reverse the pin and discard the padding + for (i = 0; i < 16; i++) { + if (pin[15-i] == 0) break; + temp[j++] = pin[15-i]; + } + String str; + if (j == 0) + str = new String(""); // unencrypted + else + str = new String(Arrays.copyOfRange(temp,0,j), StandardCharsets.UTF_8); + Log.d(TAG, "Pin: " + str); + return str; + } + + @Override + public void onBroadcastKeyGenerated() { + Log.d(TAG, "onBroadcastKeyGenerated"); + String summary = "Broadcast code: "; + String keyStr = "Unavailable"; + + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + LocalBluetoothProfileManager profileManager = mManager.getProfileManager(); + BroadcastProfile bapProfile = (BroadcastProfile) profileManager.getBroadcastProfile(); + if ((mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) && + (bapProfile.isProfileReady())) { + byte[] key = bapProfile.getEncryptionKey(); + // Key can only be 16 byte long + if (key.length == 16) { + for(int i = 0; i mRadioButtonIds = new ArrayList<>(); + private List mRadioButtonStrings = new ArrayList<>(); + + private int getDialogTitle() { + return R.string.bluetooth_broadcast_pin_configure_dialog; + } + + private void updatePinConfiguration() { + Log.d(TAG, "updatePinConfiguration with " + Integer.toString(mUserSelectedPinConfiguration)); + if (mUserSelectedPinConfiguration == -1) { + Log.e(TAG, "no pin selected"); + return; + } + // Call lower layer to generate new pin + LocalBluetoothManager mManager = Utils.getLocalBtManager(mContext); + LocalBluetoothProfileManager profileManager = mManager.getProfileManager(); + BroadcastProfile bapProfile = (BroadcastProfile) profileManager.getBroadcastProfile(); + if (mUserSelectedPinConfiguration != 0) + bapProfile.setEncryption(true, mUserSelectedPinConfiguration, false); + else + bapProfile.setEncryption(false, mUserSelectedPinConfiguration, false); + } + + @Override + public void onAttach(Context context) { + Log.d(TAG, "onAttach"); + super.onAttach(context); + mContext = context; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + Log.d(TAG, "onCreate"); + super.onCreate(savedInstanceState); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + Log.d(TAG, "onActivityCreated"); + super.onActivityCreated(savedInstanceState); + //Dialog mDialog = onCreateDialog(new Bundle()); + //this.show(this.getActivity().getSupportFragmentManager(), "PinFragment"); + } + + /* + public void show() { + Log.e(TAG, "show"); + this.show(this.getActivity().getSupportFragmentManager(), "PinFragment"); + } + */ + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + //String deviceName = getDeviceName(); + Log.d(TAG, "onCreateDialog - enter"); + if (savedInstanceState != null) { + Log.e(TAG, "savedInstanceState != null"); + } + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) + .setTitle(getDialogTitle()) + .setView(createDialogView()) + .setPositiveButton(R.string.okay, (dialog, which) -> { + //setDeviceName(mDeviceNameView.getText().toString().trim()); + updatePinConfiguration(); + }) + .setNegativeButton(android.R.string.cancel, null); + mAlertDialog = builder.create(); + Log.d(TAG, "onCreateDialog - exit"); + return mAlertDialog; + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.BLUETOOTH_FRAGMENT; + } + + @Override + public void onSaveInstanceState(Bundle outState) { + Log.d(TAG, "onSaveInstanceState"); + } + + private int getRadioButtonGroupId() { + return R.id.bluetooth_broadcast_pin_config_radio_group; + } + + private void setCurrentPin(String pin) { + mCurrentPin = pin; + } + + private String getCurrentPin() { + return mCurrentPin; + } + + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + Log.d(TAG, "Index changed to " + checkedId); + // radioButton = (RadioButton) view.findViewById(checkedId); + int index = mRadioButtonIds.indexOf(checkedId); + Log.d(TAG, "index"); + String[] stringArrayValues = getContext().getResources().getStringArray( + R.array.bluetooth_broadcast_pin_config_values); + mUserSelectedPinConfiguration = Integer.parseInt(stringArrayValues[index]); + Log.d(TAG, "Selected Pin Configuration " + Integer.toString(mUserSelectedPinConfiguration)); + } + + private View createDialogView() { + Log.d(TAG, "onCreateDialogView - enter"); + final LayoutInflater layoutInflater = (LayoutInflater)getActivity() + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View view = layoutInflater.inflate(R.xml.bluetooth_broadcast_pin_config, null); + + final RadioGroup radioGroup = (RadioGroup) view.findViewById(getRadioButtonGroupId()); + if (radioGroup == null) { + Log.e (TAG, "Not able to find RadioGroup"); + return null; + } + radioGroup.clearCheck(); + radioGroup.setOnCheckedChangeListener(this); + + // Fill up the Radio Group + mRadioButtonIds.add(R.id.bluetooth_broadcast_pin_unencrypted); + mRadioButtonIds.add(R.id.bluetooth_broadcast_pin_4); + mRadioButtonIds.add(R.id.bluetooth_broadcast_pin_16); + String[] stringArray = getContext().getResources().getStringArray( + R.array.bluetooth_broadcast_pin_config_titles); + for (int i = 0; i < stringArray.length; i++) { + mRadioButtonStrings.add(stringArray[i]); + } + RadioButton radioButton; + for (int i = 0; i < mRadioButtonStrings.size(); i++) { + radioButton = (RadioButton) view.findViewById(mRadioButtonIds.get(i)); + if (radioButton == null) { + Log.e(TAG, "Unable to show dialog by no radio button:" + mRadioButtonIds.get(i)); + return null; + } + radioButton.setText(mRadioButtonStrings.get(i)); + radioButton.setEnabled(true); + } + + mCurrentPinView = (TextView) view.findViewById(R.id.bluetooth_broadcast_current_pin); + //mCurrentPinView.setText("Current Pin is " + getCurrentPin()); + Log.d(TAG, "onCreateDialogView - exit"); + return view; + } + + @Override + public void onDestroy() { + super.onDestroy(); + Log.d(TAG, "onDestroy"); + mAlertDialog = null; + mOkButton = null; + mCurrentPinView = null; + mRadioButtonIds = new ArrayList<>(); + mRadioButtonStrings = new ArrayList<>(); + mUserSelectedPinConfiguration = -1; + } + + @Override + public void onResume() { + super.onResume(); + Log.d(TAG, "onResume"); + if (mOkButton == null) { + if (mAlertDialog != null) { + mOkButton = mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE); + mOkButton.setEnabled(true); + } else { + Log.d(TAG, "onResume: mAlertDialog is null"); + } + } + } +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastSourceInfoEntries.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastSourceInfoEntries.java new file mode 100644 index 00000000000..110d9b7b1cb --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastSourceInfoEntries.java @@ -0,0 +1,52 @@ +/* + *Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + * Copyright (C) 2017 The Android Open Source Project + * + * 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. + */ +package com.android.settings.bluetooth; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BleBroadcastSourceInfo; +import android.content.Context; +import android.util.Log; + +import androidx.preference.Preference; + +import com.android.settings.bluetooth.BleBroadcastSourceInfoPreferenceCallback; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; + +/** + * Maintain and update saved bluetooth devices(bonded but not connected) + */ +public class BluetoothBroadcastSourceInfoEntries extends BleBroadcastSourceInfoUpdater + implements Preference.OnPreferenceClickListener { + private static final String TAG = "BluetoothBroadcastSourceInfoEntries"; + + + public BluetoothBroadcastSourceInfoEntries(Context context, DashboardFragment fragment, + BleBroadcastSourceInfoPreferenceCallback bleBroadcastSourceInfoPreferenceCallback, + CachedBluetoothDevice device) { + super(context, fragment, bleBroadcastSourceInfoPreferenceCallback, device); + } + + @Override + public boolean onPreferenceClick(Preference preference) { + final BleBroadcastSourceInfo srcInfo = ((BleBroadcastSourceInfoPreference) preference) + .getBleBroadcastSourceInfo(); + BroadcastScanAssistanceUtils.debug(TAG, "onPreferenceClick: " + srcInfo); + return true; + } +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothDetailsAddSourceButtonController.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothDetailsAddSourceButtonController.java new file mode 100644 index 00000000000..536bc1bfb82 --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothDetailsAddSourceButtonController.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * 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. + */ + +package com.android.settings.bluetooth; + +import android.content.Context; + +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.widget.ActionButtonsPreference; +import com.android.settingslib.bluetooth.BCProfile; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import android.util.Log; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; +import com.android.settings.core.SubSettingLauncher; +import android.app.settings.SettingsEnums; +import android.os.Bundle; +import android.bluetooth.BluetoothProfile; +/** + * This class adds two buttons: one to connect/disconnect from a device (depending on the current + * connected state), and one to "Search for LE audio Broadcast sources" around. + */ +public class BluetoothDetailsAddSourceButtonController extends BluetoothDetailsController + implements CachedBluetoothDevice.Callback { + private static final String KEY_ACTION_BUTTONS = "sync_helper_buttons"; + private static final String TAG = "BluetoothDetailsAddSourceButtonController"; + private boolean mIsConnected = false; + + private ActionButtonsPreference mActionButtons; + protected LocalBluetoothProfileManager mProfileManager; + private LocalBluetoothManager mLocalBluetoothManager; + private BCProfile mBCProfile = null; + + + public BluetoothDetailsAddSourceButtonController(Context context, PreferenceFragmentCompat fragment, + CachedBluetoothDevice device, Lifecycle lifecycle) { + super(context, fragment, device, lifecycle); + device.registerCallback(this); + + } + + private void onAddLESourcePressed() { + final Bundle args = new Bundle(); + args.putString(BluetoothSADetail.KEY_DEVICE_ADDRESS, + mCachedDevice.getDevice().getAddress()); + args.putShort(BluetoothSADetail.KEY_GROUP_OP, + (short)0); + + new SubSettingLauncher(mContext) + .setDestination(BluetoothSADetail.class.getName()) + .setArguments(args) + .setTitleRes(R.string.bluetooth_search_broadcasters) + .setSourceMetricsCategory(SettingsEnums.BLUETOOTH_DEVICE_PICKER) + .launch(); + } + + @Override + public void onDeviceAttributesChanged() { + refresh(); + } + + @Override + protected void init(PreferenceScreen screen) { + BroadcastScanAssistanceUtils.debug(TAG, "init"); + mLocalBluetoothManager = Utils.getLocalBtManager(mContext); + mProfileManager = mLocalBluetoothManager.getProfileManager(); + mBCProfile = (BCProfile)mProfileManager.getBCProfile(); + + mActionButtons = ((ActionButtonsPreference) screen.findPreference( + getPreferenceKey())) + .setButton1Text(R.string.add_source_button_text) + .setButton1Icon(R.drawable.ic_add_24dp) + .setButton1OnClickListener((view) -> onAddLESourcePressed()) + .setButton1Enabled(false) + ; + } + + @Override + protected void refresh() { + BroadcastScanAssistanceUtils.debug(TAG, "refresh"); + if (mBCProfile != null) { + mIsConnected = mBCProfile.getConnectionStatus(mCachedDevice.getDevice()) == BluetoothProfile.STATE_CONNECTED; + } + if (mIsConnected) { + mActionButtons + .setButton1Enabled(true); + } else { + BroadcastScanAssistanceUtils.debug(TAG, "Bass is not connected for thsi device>>"); + mActionButtons + .setButton1Enabled(false); + } + } + + @Override + public String getPreferenceKey() { + return KEY_ACTION_BUTTONS; + } + +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothSADetail.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothSADetail.java new file mode 100644 index 00000000000..174332cf268 --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothSADetail.java @@ -0,0 +1,655 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * 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. + */ + +package com.android.settings.bluetooth; + +import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH; + +import android.app.settings.SettingsEnums; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.os.Bundle; +import android.util.Log; +import android.widget.Toast; +import android.text.Html; +import android.widget.EditText; +import android.widget.RadioButton; +import android.view.View; +import android.widget.RadioGroup; +import android.bluetooth.le.ScanResult; +import android.bluetooth.IBluetoothManager; +import java.util.Arrays; +import java.util.Map; +import java.util.List; +import android.bluetooth.le.ScanRecord; +import android.app.Activity; + +import androidx.appcompat.app.AlertDialog; +import android.content.DialogInterface; +import android.text.TextUtils; + +import com.android.settings.R; +import com.android.settingslib.bluetooth.BluetoothDeviceFilter; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.VendorCachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.settingslib.bluetooth.BCProfile; + +import android.bluetooth.BleBroadcastAudioScanAssistManager; +import android.bluetooth.BleBroadcastAudioScanAssistCallback; +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.BleBroadcastSourceChannel; + +import com.android.settingslib.search.Indexable; +import com.android.settingslib.widget.FooterPreference; +import androidx.preference.Preference; +import android.widget.ListView; +import android.text.BidiFormatter; +import android.widget.ArrayAdapter; +import android.widget.AdapterView; +import android.widget.CheckedTextView; + +/** + * BluetoothSADetail is a page to scan bluetooth devices and pair them. + */ +public class BluetoothSADetail extends DeviceListPreferenceFragment implements + Indexable { + private static final String TAG = "BluetoothSADetail"; + private static final boolean DBG = true; + + public static final String KEY_DEVICE_ADDRESS = "device_address"; + public static final String KEY_GROUP_OP = "group_op"; + static final String KEY_AVAIL_LE_AUDIO_SOURCES = "available_audio_sources"; + static final String KEY_FOOTER_PREF = "footer_preference"; + static final String SCAN_DEL_NAME = "Scan Delegator"; + + BluetoothProgressCategory mAvailableDevicesCategory; + FooterPreference mFooterPreference; + + private AlertDialog mScanAssistDetailsDialog; + private boolean mInitialScanStarted; + private CachedBluetoothDevice mCachedDevice; + Preference mScanDelegatorName; + String mSyncState; + BleBroadcastAudioScanAssistManager mScanAssistManager; + protected LocalBluetoothProfileManager mProfileManager; + String mBroadcastCode; + Context mContext; + CachedBluetoothDevice clickedDevice = null; + String mBroadcastPinCode = null; + boolean mScanning = true; + boolean mGroupOperation = false; + AlertDialog mCommonMsgDialog = null; + + private String getBluetoothName(BluetoothDevice dev) { + String aliasName = null; + if (dev == null) { + aliasName = SCAN_DEL_NAME; + } else { + aliasName = dev.getAlias(); + aliasName = TextUtils.isEmpty(aliasName) ? dev.getAddress() : aliasName; + if (aliasName == null) { + aliasName = SCAN_DEL_NAME; + } + } + BroadcastScanAssistanceUtils.debug(TAG, "getBluetoothName returns" + aliasName); + return aliasName; + } + + private int getSourceSelectionErrMessage(int status) { + int errorMessage = R.string.bluetooth_source_selection_error_message; + switch (status) { + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_COLOCATED_SRC_UNAVAILABLE: + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_SOURCE_UNAVAILABLE: + errorMessage = R.string.bluetooth_source_selection_error_src_unavail_message; + break; + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_SELECTED: + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_ID: + errorMessage = R.string.bluetooth_source_selection_error_message; + break; + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_DUPLICATE_ADDITION : + errorMessage = R.string.bluetooth_source_dup_addition_error_message; + break; + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_NO_EMPTY_SLOT : + errorMessage = R.string.bluetooth_source_no_empty_slot_error_message; + break; + } + return errorMessage; + } + + private int getSourceAdditionErrMessage(int status) { + int errorMessage = R.string.bluetooth_source_addition_error_message; + switch (status) { + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_DUPLICATE_ADDITION : + errorMessage = R.string.bluetooth_source_dup_addition_error_message; + break; + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_NO_EMPTY_SLOT : + errorMessage = R.string.bluetooth_source_no_empty_slot_error_message; + break; + } + return errorMessage; + } + + private int getSourceRemovalErrMessage(int status) { + int errorMessage = R.string.bluetooth_source_removal_error_message; + switch (status) { + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_FATAL : + errorMessage = R.string.bluetooth_source_removal_error_message; + break; + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_GROUP_OP : + errorMessage = R.string.bluetooth_source_remove_invalid_group_op; + break; + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_ID : + errorMessage = R.string.bluetooth_source_remove_invalid_src_id; + break; + } + return errorMessage; + } + + private int getSourceUpdateErrMessage(int status) { + int errorMessage = R.string.bluetooth_source_update_error_message; + switch (status) { + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_FATAL : + errorMessage = R.string.bluetooth_source_update_error_message; + break; + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_GROUP_OP : + errorMessage = R.string.bluetooth_source_update_invalid_group_op; + break; + case BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_ID : + errorMessage = R.string.bluetooth_source_update_invalid_src_id; + break; + } + return errorMessage; + } + + BleBroadcastAudioScanAssistCallback mScanAssistCallback = new BleBroadcastAudioScanAssistCallback() { + DialogInterface.OnClickListener commonMessageListener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + BroadcastScanAssistanceUtils.debug(TAG, ">>OK clicked"); + if (mCommonMsgDialog != null) { + mCommonMsgDialog.dismiss(); + } + finish(); + } + }; + public void onBleBroadcastSourceFound(ScanResult res) { + BroadcastScanAssistanceUtils.debug(TAG, "onBleBroadcastSourceFound" + res.getDevice()); + + CachedBluetoothDevice cachedDevice = mLocalManager.getCachedDeviceManager().findDevice(res.getDevice()); + + if (cachedDevice != null) { + BroadcastScanAssistanceUtils.debug(TAG, "seems like CachedDevice entry already present for this device"); + } else { + //Create a Device entry for this, + //If this is randon Address, there would new CachedDevice Entry for this random address Instance + //However this wont have the name + cachedDevice = mLocalManager.getCachedDeviceManager().addDevice(res.getDevice()); + //udate the Name for this device from ADV: HACK + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (res.getDevice().getAddress().equals(adapter.getAddress())) { + BroadcastScanAssistanceUtils.debug(TAG, "Self DEVICE:"); + } else { + ScanRecord rec = res.getScanRecord(); + if (rec != null && rec.getDeviceName() != null) { + String s = rec.getDeviceName(); + BroadcastScanAssistanceUtils.debug(TAG,"setting name as " + s); + cachedDevice.setName(s); + } + } + } + + BluetoothDevicePreference pref = mDevicePreferenceMap.get(cachedDevice); + if (pref != null) { + //If the Prefernce alread Created, just update the + //Scan Result + //pref.SetScanResult(res); + BroadcastScanAssistanceUtils.debug(TAG, "Preference is already present" + res.getDevice()); + return; + } + // Prevent updates while the list shows one of the state messages + if (mBluetoothAdapter.getState() != BluetoothAdapter.STATE_ON) return; + //if (mFilter.matches(cachedDevice.getDevice())) { + createDevicePreference(cachedDevice); + //} + // + VendorCachedBluetoothDevice vDev = VendorCachedBluetoothDevice.getVendorCachedBluetoothDevice(cachedDevice, mProfileManager); + vDev.setScanResult(res); + }; + + public void onBleBroadcastSourceSelected(BluetoothDevice rcvr, int status, + List broadcastSourceIndicies) { + BroadcastScanAssistanceUtils.debug(TAG, "onBleBroadcastSourceSelected" + status + "sel indicies:" + broadcastSourceIndicies); + if (status == BleBroadcastAudioScanAssistCallback.BASS_STATUS_SUCCESS) { + launchSyncAndBroadcastIndexOptions(broadcastSourceIndicies); + } else { + String aliasName = getBluetoothName(rcvr); + mCommonMsgDialog = BroadcastScanAssistanceUtils.showScanAssistError(mContext, rcvr.getName(), + getSourceSelectionErrMessage(status), commonMessageListener); + + } + }; + + public void onBleBroadcastAudioSourceAdded(BluetoothDevice rcvr, + byte srcId, + int status) { + + BroadcastScanAssistanceUtils.debug(TAG, "onBleBroadcastAudioSourceAdded: rcvr:" + rcvr + + "status:" + status + "srcId" + srcId); + if (status == BleBroadcastAudioScanAssistCallback.BASS_STATUS_SUCCESS) { + //Show Dialog + if (mGroupOperation) { + String aliasName = getBluetoothName(rcvr); + mCommonMsgDialog = BroadcastScanAssistanceUtils.showScanAssistError(mContext, aliasName, + R.string.bluetooth_source_added_message, commonMessageListener); + } + if(mBroadcastPinCode != null) { + if (status == BleBroadcastAudioScanAssistCallback.BASS_STATUS_SUCCESS + && mScanAssistManager != null) { + mScanAssistManager.setBroadcastCode(srcId,mBroadcastPinCode, mGroupOperation); + } + mBroadcastPinCode = null; + } + finish(); + } else { + String aliasName = getBluetoothName(rcvr); + mCommonMsgDialog = BroadcastScanAssistanceUtils.showScanAssistError(mContext, aliasName, + getSourceAdditionErrMessage(status), commonMessageListener); + } + }; + + public void onBleBroadcastAudioSourceUpdated(BluetoothDevice rcvr, + byte srcId, + int status) { + BroadcastScanAssistanceUtils.debug(TAG, "onBleBroadcastAudioSourceUpdated: rcvr:" + rcvr + + "status:" + status + "srcId" + srcId); + if (status != BleBroadcastAudioScanAssistCallback.BASS_STATUS_SUCCESS) { + String aliasName = getBluetoothName(rcvr); + mCommonMsgDialog = BroadcastScanAssistanceUtils.showScanAssistError(mContext, aliasName, + getSourceUpdateErrMessage(status), commonMessageListener); + } + }; + + public void onBleBroadcastPinUpdated(BluetoothDevice rcvr, + byte srcId, + int status) { + + BroadcastScanAssistanceUtils.debug(TAG, "onBleBroadcastPinUpdated: rcvr:" + rcvr + + "status:" + status + "srcId" + srcId); + if (status != BleBroadcastAudioScanAssistCallback.BASS_STATUS_SUCCESS) { + String aliasName = getBluetoothName(rcvr); + mCommonMsgDialog = BroadcastScanAssistanceUtils.showScanAssistError(mContext, aliasName, + R.string.bluetooth_source_setpin_error_message, commonMessageListener); + } + }; + public void onBleBroadcastAudioSourceRemoved(BluetoothDevice rcvr, + byte srcId, + int status) { + BroadcastScanAssistanceUtils.debug(TAG, "onBleBroadcastAudioSourceRemoved: rcvr:" + rcvr + + "status:" + status + "srcId" + srcId); + if (status != BleBroadcastAudioScanAssistCallback.BASS_STATUS_SUCCESS) { + String aliasName = getBluetoothName(rcvr); + mCommonMsgDialog = BroadcastScanAssistanceUtils.showScanAssistError(mContext, aliasName, + getSourceRemovalErrMessage(status), commonMessageListener); + } + }; + }; + + + public BluetoothSADetail() { + super(DISALLOW_CONFIG_BLUETOOTH); + } + + void createDevicePreference(CachedBluetoothDevice cachedDevice) { + if (mDeviceListGroup == null) { + Log.w(TAG, "Trying to create a device preference before the list group/category " + + "exists!"); + return; + } + + String key = cachedDevice.getDevice().getAddress(); + BluetoothDevicePreference preference = (BluetoothDevicePreference) getCachedPreference(key); + + if (preference == null) { + preference = new BluetoothDevicePreference(getPrefContext(), cachedDevice, + true/*mShowDevicesWithoutNames*/, BluetoothDevicePreference.SortType.TYPE_FIFO); + preference.setKey(key); + //Set hideSecondTarget is true if it's bonded device. + //preference.hideSecondTarget(true); + mDeviceListGroup.addPreference(preference); + } + + initDevicePreference(preference); + Log.w(TAG, "adding" + cachedDevice + "to the Pref map"); + mDevicePreferenceMap.put(cachedDevice, preference); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mInitialScanStarted = false; + } + + @Override + public void onDeviceAdded(CachedBluetoothDevice cachedDevice) { + //Do nothing + } + + @Override + public void onStart() { + BroadcastScanAssistanceUtils.debug(TAG, "OnStart Called"); + super.onStart(); + if (mLocalManager == null){ + Log.e(TAG, "Bluetooth is not supported on this device"); + return; + } + updateContent(mBluetoothAdapter.getState()); + mAvailableDevicesCategory.setProgress(mBluetoothAdapter.isDiscovering()); + if (mScanAssistManager == null) { + if (mProfileManager == null) { + mProfileManager = mLocalManager.getProfileManager(); + } + BCProfile bcProfile = (BCProfile)mProfileManager.getBCProfile(); + mScanAssistManager = bcProfile.getBSAManager( + mCachedDevice.getDevice(), mScanAssistCallback); + if (mScanAssistManager == null) { + Log.e(TAG, "On Start: not able to instantiate scanAssistManager"); + //return; + } + } + } + + @Override + public void onAttach(Context context) { + BroadcastScanAssistanceUtils.debug(TAG, "OnAttach Called"); + super.onAttach(context); + mContext = context; + String deviceAddress = getArguments().getString(KEY_DEVICE_ADDRESS); + mGroupOperation = getArguments().getShort(KEY_GROUP_OP) == (short)1; + BluetoothDevice remoteDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice( + deviceAddress); + if (mLocalManager == null) { + Log.e(TAG, "Local mgr is NULL"); + mLocalManager = Utils.getLocalBtManager(getActivity()); + if (mLocalManager == null) { + Log.e(TAG, "Bluetooth is not supported on this device"); + return; + } + } + mCachedDevice = mLocalManager.getCachedDeviceManager().findDevice(remoteDevice); + if (mCachedDevice == null) { + //goBack(); + return; + } else { + mProfileManager = mLocalManager.getProfileManager(); + BCProfile bcProfile = (BCProfile)mProfileManager.getBCProfile(); + mScanAssistManager = bcProfile.getBSAManager( + mCachedDevice.getDevice(), mScanAssistCallback); + if (mScanAssistManager == null) { + Log.e(TAG, "not able to instantiate scanAssistManager"); + //return; + } + } + } + + + @Override + public void onStop() { + super.onStop(); + if (mLocalManager == null){ + Log.e(TAG, "Bluetooth is not supported on this device"); + return; + } + // Make the device only visible to connected devices. + disableScanning(); + //clear the preference map onStop + mDevicePreferenceMap.clear(); + mScanAssistManager = null; + } + + @Override + void initPreferencesFromPreferenceScreen() { + mScanDelegatorName = findPreference("bt_bcast_rcvr_device"); + mScanDelegatorName.setSelectable(false); + if (mCachedDevice == null) { + mScanDelegatorName.setSummary(SCAN_DEL_NAME); + } else { + mScanDelegatorName.setSummary(getBluetoothName(mCachedDevice.getDevice())); + } + mAvailableDevicesCategory = (BluetoothProgressCategory) findPreference(KEY_AVAIL_LE_AUDIO_SOURCES); + mFooterPreference = (FooterPreference) findPreference(KEY_FOOTER_PREF); + mFooterPreference.setSelectable(false); + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.BLUETOOTH_PAIRING; + } + + @Override + void enableScanning() { + // Clear all device states before first scan + if (!mInitialScanStarted) { + if (mAvailableDevicesCategory != null) { + removeAllDevices(); + } + mLocalManager.getCachedDeviceManager().clearNonBondedDevices(); + mInitialScanStarted = true; + } + //Call to Scan for LE Audio Sources + if (mScanAssistManager != null) { + BroadcastScanAssistanceUtils.debug(TAG, "call searchforLeAudioBroadcasters"); + mScanAssistManager.searchforLeAudioBroadcasters(); + } + } + + @Override + void disableScanning() { + if (mScanAssistManager != null && mScanning == true) { + BroadcastScanAssistanceUtils.debug(TAG, "call stopSearchforLeAudioBroadcasters"); + mScanAssistManager.stopSearchforLeAudioBroadcasters(); + mScanning = false; + } + } + + private int getSyncStateFromSelection (String s) { + int ret = -1; + if (s == null) { + BroadcastScanAssistanceUtils.debug(TAG, "getSyncStateFromSelection:Invalid Input"); + } else { + if (mSyncState.equals("Sync Metadata")) { + ret = BleBroadcastAudioScanAssistManager.SYNC_METADATA; + } else { + ret = BleBroadcastAudioScanAssistManager.SYNC_METADATA_AUDIO; + } + } + return ret; + } + + void launchSyncAndBroadcastIndexOptions(List broadcastSourceIndicies) { + Context context = getContext(); + + final View dialogView; + String title, message; + Activity activity = getActivity(); + if (isAdded() && activity != null) { + dialogView = getLayoutInflater().inflate(R.layout.select_source_prompt, null); + String name = null; + if (clickedDevice != null) { + name = clickedDevice.getName(); + } + if (TextUtils.isEmpty(name)) { + name = context.getString(R.string.bluetooth_device); + } + if (mGroupOperation) { + message = context.getString(R.string.bluetooth_grp_source_selection_options_detail, name); + } else { + message = context.getString(R.string.bluetooth_source_selection_options_detail, name); + } + title = context.getString(R.string.bluetooth_source_selection_options_detail_title); + + /* + //BIS Selection choice + ListView bisSelectionList; + bisSelectionList = dialogView.findViewById(R.id.lv); + bisSelectionList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + ArrayAdapter arrayAdapter = + new ArrayAdapter(context, android.R.layout.simple_list_item_multiple_choice , broadcastSourceIndicies); + + bisSelectionList.setAdapter(arrayAdapter); + + bisSelectionList.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + BroadcastScanAssistanceUtils.debug(TAG, "onItemClick: " +position); + CheckedTextView v = (CheckedTextView) view; + boolean currentCheck = v.isChecked(); + BleBroadcastSourceChannel bisIndex = (BleBroadcastSourceChannel) bisSelectionList.getItemAtPosition(position); + bisIndex.setStatus(currentCheck); + } + }); + */ + DialogInterface.OnClickListener cancelAddSourceListener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + BroadcastScanAssistanceUtils.debug(TAG, ">>Cancel clicked"); + finish(); + } + }; + + DialogInterface.OnClickListener addSourceListener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + /* + Radio Buttons + final RadioGroup group = dialogView.findViewById(R.id.syncStateOptions); + int selectedId = group.getCheckedRadioButtonId(); + RadioButton radioSelectedButton = (RadioButton) dialogView.findViewById(selectedId); + mSyncState = radioSelectedButton.getText().toString(); + BroadcastScanAssistanceUtils.debug(TAG, "mSyncState: " + mSyncState); + */ + if (clickedDevice == null) { + Log.w(TAG, "Ignore as there is no clicked device"); + } + if (clickedDevice.getAddress().equals(mBluetoothAdapter.getAddress())) { + BroadcastScanAssistanceUtils.debug(TAG, ">>Local Adapter"); + mBroadcastPinCode = null; + } else { + EditText broadcastPIN = dialogView.findViewById(R.id.broadcastPINcode); + mBroadcastPinCode = broadcastPIN.getText().toString(); + BroadcastScanAssistanceUtils.debug(TAG, "broadcastPinCode: " + mBroadcastPinCode); + if (TextUtils.isEmpty(mBroadcastPinCode)) { + BroadcastScanAssistanceUtils.debug(TAG, "Empty broacast PinCode"); + mBroadcastPinCode = null; + } + } + if (mScanAssistManager != null && clickedDevice != null) { + mScanAssistManager.addBroadcastSource(clickedDevice.getDevice(), + /*getSyncStateFromSelection(mSyncState)*/ + BleBroadcastAudioScanAssistManager.SYNC_METADATA_AUDIO, + broadcastSourceIndicies, mGroupOperation); + } + } + }; + EditText broadcastPIN = dialogView.findViewById(R.id.broadcastPINcode); + if (clickedDevice != null && clickedDevice.getAddress().equals(mBluetoothAdapter.getAddress())) { + BroadcastScanAssistanceUtils.debug(TAG, "Local Adapter"); + mBroadcastPinCode = null; + broadcastPIN.setVisibility(View.INVISIBLE); + if (mGroupOperation) { + message = context.getString(R.string.bluetooth_col_grp_source_selection_options_detail, name); + } else { + message = context.getString(R.string.bluetooth_col_source_selection_options_detail, name); + } + } + mScanAssistDetailsDialog = BroadcastScanAssistanceUtils.showScanAssistDetailsDialog(context, + mScanAssistDetailsDialog, addSourceListener, cancelAddSourceListener, title, + Html.fromHtml(message), dialogView); + } + } + + @Override + void onDevicePreferenceClick(BluetoothDevicePreference btPreference) { + disableScanning(); + clickedDevice = btPreference.getBluetoothDevice(); + VendorCachedBluetoothDevice vDevice = VendorCachedBluetoothDevice.getVendorCachedBluetoothDevice(clickedDevice, mProfileManager); + if (mScanAssistManager != null) { + BroadcastScanAssistanceUtils.debug(TAG, "calling selectAudioSource"); + mScanAssistManager.selectBroadcastSource(vDevice.getScanResult(), mGroupOperation); + } + } + void updateContent(int bluetoothState) { + switch (bluetoothState) { + case BluetoothAdapter.STATE_ON: + mDevicePreferenceMap.clear(); + //mBluetoothAdapter.enable(); + + addDeviceCategory(mAvailableDevicesCategory, + R.string.bluetooth_preference_found_media_devices, + BluetoothDeviceFilter.ALL_FILTER, false); + updateFooterPreference(mFooterPreference); + //mAlwaysDiscoverable.start(); + enableScanning(); + break; + + case BluetoothAdapter.STATE_OFF: + finish(); + break; + } + } + + @Override + void updateFooterPreference(Preference myDevicePreference) { + final BidiFormatter bidiFormatter = BidiFormatter.getInstance(); + myDevicePreference.setTitle(getString( + R.string.bluetooth_footer_mac_message, + bidiFormatter.unicodeWrap(mCachedDevice.getAddress()))); + } + + @Override + public void onBluetoothStateChanged(int bluetoothState) { + super.onBluetoothStateChanged(bluetoothState); + updateContent(bluetoothState); + if (bluetoothState == BluetoothAdapter.STATE_ON) { + showBluetoothTurnedOnToast(); + } + } + + + + @Override + public int getHelpResource() { + return R.string.help_url_bluetooth; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.bluetooth_search_bcast_sources; + } + + @Override + public String getDeviceListKey() { + return KEY_AVAIL_LE_AUDIO_SOURCES; + } + + void showBluetoothTurnedOnToast() { + Toast.makeText(getContext(), R.string.connected_device_bluetooth_turned_on_toast, + Toast.LENGTH_SHORT).show(); + } +} diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BroadcastScanAssistanceUtils.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BroadcastScanAssistanceUtils.java new file mode 100644 index 00000000000..9883e70ae89 --- /dev/null +++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BroadcastScanAssistanceUtils.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * 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. + */ + +package com.android.settings.bluetooth; + +import android.app.settings.SettingsEnums; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.content.DialogInterface; +import android.provider.Settings; +import android.util.Log; +import android.widget.Toast; +import android.text.InputType; +import android.view.View; + +import androidx.annotation.VisibleForTesting; +import androidx.appcompat.app.AlertDialog; + +import com.android.settings.R; +import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.bluetooth.BluetoothUtils; +import com.android.settingslib.bluetooth.BluetoothUtils.ErrorListener; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.bluetooth.LocalBluetoothManager.BluetoothManagerCallback; +import android.bluetooth.BluetoothAdapter; + +/** + * BroadcastScanAssistanceUtils is a helper class that contains constants for various + * Android resource IDs, debug logging flags, and static methods + * for creating BASS dialogs. + */ +public final class BroadcastScanAssistanceUtils { + + private static final String TAG = "BroadcastScanAsssitanceBroadcastScanAssistanceUtils"; + static final boolean BASS_DBG = Log.isLoggable(TAG, Log.VERBOSE); + + private BroadcastScanAssistanceUtils() { + } + + static void debug(String TAG, String msg) { + if (BASS_DBG) { + Log.d(TAG, msg); + } + } + + static boolean isLocalDevice(BluetoothDevice dev) { + boolean ret = false; + if (dev != null) { + BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); + ret = btAdapter.getAddress().equals(dev.getAddress()); + } + Log.d(TAG, "isLocalBroadcastSource returns" +ret); + return ret; + } + + static AlertDialog showScanAssistError(Context context, String name, int messageResId, + DialogInterface.OnClickListener okListener) { + return showScanAssistError(context, name, messageResId, Utils.getLocalBtManager(context), okListener); + } + + private static AlertDialog showScanAssistError(Context context, String name, int messageResId, + LocalBluetoothManager manager, DialogInterface.OnClickListener okListener) { + String message = context.getString(messageResId, name); + Context activity = manager.getForegroundActivity(); + AlertDialog dialog = null; + if (manager.isForegroundActivity()) { + try { + dialog = new AlertDialog.Builder(activity) + .setTitle(R.string.bluetooth_error_title) + .setMessage(message) + .setPositiveButton(android.R.string.ok, okListener) + .show(); + } catch (Exception e) { + Log.e(TAG, "Cannot show error dialog.", e); + return null; + } + return dialog; + } else { + Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); + return dialog; + } + } + // Create (or recycle existing) and show disconnect dialog. + static AlertDialog showScanAssistDetailsDialog(Context context, + AlertDialog dialog, + DialogInterface.OnClickListener addSourceListener, + DialogInterface.OnClickListener cancelAddSourceListener, + CharSequence title, CharSequence message, + View customView + ) { + if (dialog == null) { + dialog = new AlertDialog.Builder(context) + .setPositiveButton(android.R.string.ok, addSourceListener) + .setNegativeButton(android.R.string.cancel, cancelAddSourceListener) + .setView(customView) + .create(); + } else { + if (dialog.isShowing()) { + dialog.dismiss(); + } + // use disconnectListener for the correct profile(s) + //CharSequence okText = context.getText(android.R.string.ok); + //dialog.setButton(DialogInterface.BUTTON_POSITIVE, + // okText, addSourceListener); + } + dialog.setTitle(title); + dialog.setMessage(message); + dialog.show(); + return dialog; + } + + static AlertDialog showAssistanceGroupOptionsDialog(Context context, + AlertDialog dialog, + DialogInterface.OnClickListener groupOpListener, + DialogInterface.OnClickListener singleDevListener, + CharSequence title, CharSequence message) { + if (dialog == null) { + Log.d(TAG, "showAssistanceGroupOptionsDialog creation"); + dialog = new AlertDialog.Builder(context) + .setPositiveButton(R.string.yes, groupOpListener) + .setNegativeButton(R.string.no, singleDevListener) + .create(); + } else { + if (dialog.isShowing()) { + dialog.dismiss(); + } + // use disconnectListener for the correct profile(s) + //CharSequence okText = context.getText(android.R.string.yes); + //dialog.setButton(DialogInterface.BUTTON_POSITIVE, + // okText, groupOpListener); + } + dialog.setTitle(title); + dialog.setMessage(message); + dialog.show(); + return dialog; + } +} diff --git a/le_audio/system/bt/binder/Android.bp b/le_audio/system/bt/binder/Android.bp new file mode 100644 index 00000000000..c918b4235e6 --- /dev/null +++ b/le_audio/system/bt/binder/Android.bp @@ -0,0 +1,10 @@ +// AIDL interface between libbluetooth-binder and framework.jar +filegroup { + name: "libbluetooth-binder-aidl-adva", + srcs: [ + "android/bluetooth/IBluetoothSyncHelper.aidl", + "android/bluetooth/IBleBroadcastAudioScanAssistCallback.aidl", + "android/bluetooth/IBluetoothBroadcast.aidl", + ], +} + diff --git a/le_audio/system/bt/binder/android/bluetooth/BleBroadcastSourceChannel.aidl b/le_audio/system/bt/binder/android/bluetooth/BleBroadcastSourceChannel.aidl new file mode 100644 index 00000000000..d15b26c0596 --- /dev/null +++ b/le_audio/system/bt/binder/android/bluetooth/BleBroadcastSourceChannel.aidl @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ + +package android.bluetooth; + +parcelable BleBroadcastSourceChannel; + diff --git a/le_audio/system/bt/binder/android/bluetooth/BleBroadcastSourceInfo.aidl b/le_audio/system/bt/binder/android/bluetooth/BleBroadcastSourceInfo.aidl new file mode 100644 index 00000000000..c3ac2102e12 --- /dev/null +++ b/le_audio/system/bt/binder/android/bluetooth/BleBroadcastSourceInfo.aidl @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ +package android.bluetooth; + +parcelable BleBroadcastSourceInfo; + diff --git a/le_audio/system/bt/binder/android/bluetooth/IBleBroadcastAudioScanAssistCallback.aidl b/le_audio/system/bt/binder/android/bluetooth/IBleBroadcastAudioScanAssistCallback.aidl new file mode 100644 index 00000000000..158dd3ecd93 --- /dev/null +++ b/le_audio/system/bt/binder/android/bluetooth/IBleBroadcastAudioScanAssistCallback.aidl @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ + +package android.bluetooth; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.BleBroadcastSourceChannel; +import android.bluetooth.le.ScanResult; + +/** @hide */ +interface IBleBroadcastAudioScanAssistCallback { + void onBleBroadcastSourceFound(in ScanResult scanres); + void onBleBroadcastAudioSourceSelected(in BluetoothDevice device, + in int status, + in List + broadcastSourceChannels); + + void onBleBroadcastAudioSourceAdded(in BluetoothDevice rcvr, + in byte srcId, + in int status); + void onBleBroadcastAudioSourceUpdated(in BluetoothDevice rcvr, + in byte srcId, + in int status); + + void onBleBroadcastPinUpdated(in BluetoothDevice rcvr, + in byte srcId, + in int status); + void onBleBroadcastAudioSourceRemoved(in BluetoothDevice rcvr, + in byte srcId, + in int status); +} diff --git a/le_audio/system/bt/binder/android/bluetooth/IBluetoothBroadcast.aidl b/le_audio/system/bt/binder/android/bluetooth/IBluetoothBroadcast.aidl new file mode 100644 index 00000000000..258557f3970 --- /dev/null +++ b/le_audio/system/bt/binder/android/bluetooth/IBluetoothBroadcast.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ +package android.bluetooth; + +import android.bluetooth.BluetoothDevice; + +/** + * APIs for Bluetooth Broadcast service + * + * @hide + */ +interface IBluetoothBroadcast { + // Public API + boolean SetBroadcast(in boolean enable, in String packageName); + boolean SetEncryption(in boolean enable, in int enc_len, + in boolean use_existing, in String packageName); + byte[] GetEncryptionKey(in String packageName); + int GetBroadcastStatus(in String packageName); +} diff --git a/le_audio/system/bt/binder/android/bluetooth/IBluetoothSyncHelper.aidl b/le_audio/system/bt/binder/android/bluetooth/IBluetoothSyncHelper.aidl new file mode 100644 index 00000000000..ce518622d38 --- /dev/null +++ b/le_audio/system/bt/binder/android/bluetooth/IBluetoothSyncHelper.aidl @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + */ +package android.bluetooth; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BleBroadcastSourceInfo; +import android.bluetooth.IBleBroadcastAudioScanAssistCallback; +import android.bluetooth.le.ScanResult; + +/** + * APIs for Bluetooth Bluetooth Scan offloader service + * + * @hide + */ +interface IBluetoothSyncHelper { + // Public API + boolean connect(in BluetoothDevice device); + boolean disconnect(in BluetoothDevice device); + List getConnectedDevices(); + List getDevicesMatchingConnectionStates(in int[] states); + int getConnectionState(in BluetoothDevice device); + boolean setConnectionPolicy(in BluetoothDevice device, int connectionPolicy); + int getConnectionPolicy(in BluetoothDevice device); + boolean startScanOffload (in BluetoothDevice device, + in boolean groupOp); + boolean stopScanOffload (in BluetoothDevice device, + in boolean groupOp); + + void registerAppCallback(in BluetoothDevice device, + in IBleBroadcastAudioScanAssistCallback cb); + void unregisterAppCallback(in BluetoothDevice device, + in IBleBroadcastAudioScanAssistCallback cb); + + boolean searchforLeAudioBroadcasters (in BluetoothDevice device); + boolean stopSearchforLeAudioBroadcasters(in BluetoothDevice device); + + boolean addBroadcastSource(in BluetoothDevice device, + in BleBroadcastSourceInfo srcInfo, + in boolean groupOp + ); + boolean selectBroadcastSource(in BluetoothDevice device, + in ScanResult scanRes, + in boolean groupOp + ); + boolean updateBroadcastSource(in BluetoothDevice device, + in BleBroadcastSourceInfo srcInfo, + in boolean groupOp + ); + boolean setBroadcastCode (in BluetoothDevice device, + in BleBroadcastSourceInfo srcInfo, + in boolean groupOp + ); + boolean removeBroadcastSource (in BluetoothDevice device, + in byte SourceId, + in boolean groupOp + ); + List getAllBroadcastSourceInformation( + in BluetoothDevice device); +} diff --git a/le_audio/system/bt/bta/Android.bp b/le_audio/system/bt/bta/Android.bp new file mode 100644 index 00000000000..9e300891e7d --- /dev/null +++ b/le_audio/system/bt/bta/Android.bp @@ -0,0 +1,70 @@ +cc_defaults { + name: "fluoride_bta_defaults_qti_adva", + defaults: ["fluoride_defaults_qti"], + local_include_dirs: [ + "include", + ], + include_dirs: [ + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/bta/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/bta/ag", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/btcore/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/hci/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/internal_include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/stack/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/stack/btm", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/udrv/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/vnd/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/utils/include", + "vendor/qcom/opensource/commonsys/bluetooth_ext/system_bt_ext", + "vendor/qcom/opensource/commonsys/bluetooth_ext/vhal/include", + "vendor/qcom/opensource/commonsys/bluetooth_ext/system_bt_ext/bta/include", + "vendor/qcom/opensource/commonsys/bluetooth_ext/system_bt_ext/btif/include", + "vendor/qcom/opensource/commonsys-intf/bluetooth/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/device/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/btif/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/bta/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/bta/sys", + "vendor/qcom/opensource/commonsys/bluetooth_lea/packages/modules/Bluetooth/system", + "vendor/qcom/opensource/commonsys/bluetooth_lea/packages/modules/Bluetooth/system/bta/include", + "vendor/qcom/opensource/commonsys/bluetooth_lea/vhal/include", + "vendor/qcom/opensource/commonsys/bluetooth_lea/packages/modules/Bluetooth/system/bta/bap", + "vendor/qcom/opensource/commonsys/bluetooth_lea/packages/modules/Bluetooth/system/btif/include", + "packages/modules/Bluetooth/system/common/" + ], + shared_libs: [ + "libcutils", + ], + header_libs: ["libbluetooth_headers"], + cflags: [ + "-DBUILDCFG", + "-DADV_AUDIO_FEATURE=1", + ], +} + +// BTA static library for target +// ======================================================== +cc_library_static { + name: "libbt-bta_qti_adva", + defaults: ["fluoride_bta_defaults_qti_adva"], + enabled: false, + srcs: [ + "csip/bta_csip_act.cc", + "csip/bta_csip_api.cc", + "csip/bta_csip_main.cc", + "csip/bta_csip_utils.cc", + "bap/ascs_client.cc", + "bap/pacs_client.cc", + "bap/gattc_ops_queue.cc", + "bap/gatts_ops_queue.cc", + "bap/uclient_main.cc", + "bap/uclient_strm_mgr.cc", + "bap/uclient_strm_tracker.cc", + "bap/connected_iso.cc", + "bap/uclient_alarm.cc", + "vcp/bta_vcp_controller.cc", + "dm/bta_dm_adv_audio.cc", + "mcp/bta_mcp_main.cc", + "cc/bta_cc_main.cc", + ], +} diff --git a/le_audio/system/bt/bta/bap/ascs_client.cc b/le_audio/system/bt/bta/bap/ascs_client.cc new file mode 100644 index 00000000000..1be5f7cc263 --- /dev/null +++ b/le_audio/system/bt/bta/bap/ascs_client.cc @@ -0,0 +1,1731 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * 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. + */ + +#include "bta_gatt_api.h" +#include "bta_ascs_client_api.h" +#include "gattc_ops_queue.h" +#include +#include +#include +#include +#include "stack/btm/btm_int.h" +#include "device/include/controller.h" + +#include +#include "btif/include/btif_bap_config.h" +#include "osi/include/log.h" +#include "btif_util.h" + +namespace bluetooth { +namespace bap { +namespace ascs { + +using base::Closure; +using bluetooth::bap::GattOpsQueue; + +Uuid ASCS_UUID = Uuid::FromString("184E"); +Uuid ASCS_SINK_ASE_UUID = Uuid::FromString("2BC4"); +Uuid ASCS_SRC_ASE_UUID = Uuid::FromString("2BC5"); +Uuid ASCS_ASE_CP_UUID = Uuid::FromString("2BC6"); + +class AscsClientImpl; +AscsClientImpl* instance; + +typedef uint8_t codec_type_t[5]; + +enum class ProfleOP { + CONNECT, + DISCONNECT +}; + +struct ProfileOperation { + uint16_t client_id; + ProfleOP type; +}; + +enum class DevState { + IDLE = 0, + CONNECTING, + CONNECTED, + DISCONNECTING +}; + +void ascs_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data); +void encryption_callback(const RawAddress*, tGATT_TRANSPORT, void*, + tBTM_STATUS); + +std::map resp_codes = { + {0x01, "Un Supported Opcode"}, + {0x02, "Invalid Length"}, + {0x03, "Invalid ASE ID"}, + {0x04, "Invalid ASE SM Transition"}, + {0x05, "Invalid ASE Direction"}, + {0x06, "Un Supported Audio Capabilities"}, + {0x07, "Un Supported Config Param"}, + {0x08, "Rejected Config Param"}, + {0x09, "Invalid Config Param"}, + {0x0A, "Un Supported Metadata"}, + {0x0B, "Rejected Metadata"}, + {0x0C, "Invalid Metadata"}, + {0x0D, "InSufficient Resources"}, + {0x0E, "Unspecified Error"}, +}; + +std::map reason_codes = { + {0x01, "Codec ID"}, + {0x02, "Codec Specific Config"}, + {0x03, "SDU Interval"}, + {0x04, "Framing"}, + {0x05, "PHY"}, + {0x06, "Maximum SDU Size"}, + {0x07, "RTN"}, + {0x08, "MTL"}, + {0x09, "PD"}, + {0x0A, "Invalid ASE CIS Mapping"}, +}; + +std::vector sink_ase_value_list, src_ase_value_list; +AseParams ase; + +struct AscsDevice { + RawAddress address; + /* This is true only during first connection to profile, until we store the + * device */ + bool first_connection; + bool service_changed_rcvd; + + uint16_t conn_id; + std::vector sink_ase_list; + std::vector src_ase_list; + uint16_t ase_cp_handle; + uint16_t ase_cp_ccc_handle; + uint16_t srv_changed_ccc_handle; + bool discovery_completed; + uint8_t num_ases_read; + bool notifications_enabled; + DevState state; + bool is_congested; + std::vector profile_queue; + std::vector connected_client_list; //list client requested for connection + AscsDevice(const RawAddress& address) : address(address) {} +}; + +class AscsDevices { + public: + void Add(AscsDevice device) { + if (FindByAddress(device.address) != nullptr) return; + + devices.push_back(device); + } + + void Remove(const RawAddress& address) { + for (auto it = devices.begin(); it != devices.end();) { + if (it->address != address) { + ++it; + continue; + } + + it = devices.erase(it); + return; + } + } + + AscsDevice* FindByAddress(const RawAddress& address) { + auto iter = std::find_if(devices.begin(), devices.end(), + [&address](const AscsDevice& device) { + return device.address == address; + }); + + return (iter == devices.end()) ? nullptr : &(*iter); + } + + AscsDevice* FindByConnId(uint16_t conn_id) { + auto iter = std::find_if(devices.begin(), devices.end(), + [&conn_id](const AscsDevice& device) { + return device.conn_id == conn_id; + }); + + return (iter == devices.end()) ? nullptr : &(*iter); + } + + size_t size() { return (devices.size()); } + + std::vector devices; +}; + +class AscsClientImpl : public AscsClient { + public: + ~AscsClientImpl() override = default; + + AscsClientImpl() : gatt_client_id(BTA_GATTS_INVALID_IF) {}; + + bool Register(AscsClientCallbacks *callback) { + LOG(WARNING) << __func__ << callback; + // looks for client is already registered + bool is_client_registered = false; + for (auto it : callbacks) { + AscsClientCallbacks *pac_callback = it.second; + if(callback == pac_callback) { + is_client_registered = true; + break; + } + } + + LOG(WARNING) << __func__ ; + + if(is_client_registered) { + LOG(WARNING) << __func__ << " already registered"; + return false; + } + + if(gatt_client_id == BTA_GATTS_INVALID_IF) { + BTA_GATTC_AppRegister( + ascs_gattc_callback, + base::Bind( + [](AscsClientCallbacks *callback, uint8_t client_id, uint8_t status) { + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Can't start ASCS profile - no gatt " + "clients left!"; + return; + } + + if (instance) { + LOG(WARNING) << " ASCS gatt_client_id " + << instance->gatt_client_id; + instance->gatt_client_id = client_id; + instance->callbacks.insert(std::make_pair( + ++instance->ascs_client_id, callback)); + callback->OnAscsInitialized(0, instance->ascs_client_id); + } + }, + callback), true); + } else { + instance->callbacks.insert(std::make_pair( + ++instance->ascs_client_id, callback)); + callback->OnAscsInitialized(0, instance->ascs_client_id); + } + return true; + } + + bool Deregister (uint16_t client_id) { + bool status = false; + auto it = callbacks.find(client_id); + if (it != callbacks.end()) { + callbacks.erase(it); + if(callbacks.empty()) { + // deregister with GATT + LOG(WARNING) << __func__ << " Gatt de-register from ascs"; + BTA_GATTC_AppDeregister(gatt_client_id); + gatt_client_id = BTA_GATTS_INVALID_IF; + } + status = true; + } + return status; + } + + uint8_t GetClientCount () { + return callbacks.size(); + } + + void Connect(uint16_t client_id, const RawAddress& address, + bool is_direct) override { + LOG(WARNING) << __func__ << " " << address; + AscsDevice *dev = ascsDevices.FindByAddress(address); + ProfileOperation op; + op.client_id = client_id; + op.type = ProfleOP::CONNECT; + + if(dev == nullptr) { + AscsDevice pac_dev(address); + ascsDevices.Add(pac_dev); + dev = ascsDevices.FindByAddress(address); + } + if (dev == nullptr) { + LOG(ERROR) << __func__ << "dev is null"; + return; + } + + switch(dev->state) { + case DevState::IDLE: { + BTA_GATTC_Open(gatt_client_id, address, is_direct, + GATT_TRANSPORT_LE, false); + dev->state = DevState::CONNECTING; + dev->profile_queue.push_back(op); + } break; + case DevState::CONNECTING: { + dev->profile_queue.push_back(op); + } break; + case DevState::CONNECTED: { + auto iter = std::find_if(dev->connected_client_list.begin(), + dev->connected_client_list.end(), + [&client_id](uint16_t id) { + return id == client_id; + }); + + if(iter == dev->connected_client_list.end()) + dev->connected_client_list.push_back(client_id); + + auto it = callbacks.find(client_id); + if (it != callbacks.end()) { + AscsClientCallbacks *callback = it->second; + callback->OnConnectionState(address, GattState::CONNECTED); + } + } break; + case DevState::DISCONNECTING: { + dev->profile_queue.push_back(op); + } break; + } + } + + void Disconnect(uint16_t client_id, const RawAddress& address) override { + LOG(WARNING) << __func__ << " " << address; + AscsDevice* dev = ascsDevices.FindByAddress(address); + if (!dev) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + + ProfileOperation op; + op.client_id = client_id; + op.type = ProfleOP::DISCONNECT; + + switch(dev->state) { + case DevState::CONNECTING: { + auto iter = std::find_if(dev->profile_queue.begin(), + dev->profile_queue.end(), + [&client_id]( ProfileOperation entry) { + return ((entry.type == ProfleOP::CONNECT) && + (entry.client_id == client_id)); + }); + // If it is the last client requested for disconnect + if(iter != dev->profile_queue.end() && + dev->profile_queue.size() == 1) { + if (dev->conn_id) { + // Removes all registrations for connection. + BTA_GATTC_CancelOpen(dev->conn_id, address, false); + GattOpsQueue::Clean(dev->conn_id); + BTA_GATTC_Close(dev->conn_id); + dev->profile_queue.push_back(op); + dev->state = DevState::DISCONNECTING; + } else { + // clear the connection queue and + // move the state to DISCONNECTING to better track + dev->profile_queue.clear(); + dev->state = DevState::DISCONNECTING; + dev->profile_queue.push_back(op); + } + } else { + // remove the connection entry from the list + // as the same client has requested for disconnection + dev->profile_queue.erase(iter); + } + } break; + case DevState::CONNECTED: { + auto iter = std::find_if(dev->connected_client_list.begin(), + dev->connected_client_list.end(), + [&client_id]( uint16_t stored_client_id) { + return stored_client_id == client_id; + }); + // if it is the last client requested for disconnection + if(iter != dev->connected_client_list.end() && + dev->connected_client_list.size() == 1) { + if (dev->conn_id) { + // Removes all registrations for connection. + BTA_GATTC_CancelOpen(dev->conn_id, address, false); + GattOpsQueue::Clean(dev->conn_id); + BTA_GATTC_Close(dev->conn_id); + dev->profile_queue.push_back(op); + dev->state = DevState::DISCONNECTING; + } + } else { + // remove the client from connected_client_list + dev->connected_client_list.erase(iter); + // remove the pending gatt ops( not the ongoing one ) + // initiated from client which requested disconnect + // TODO and send callback as disconnected + } + } break; + case DevState::DISCONNECTING: { + dev->profile_queue.push_back(op); + } break; + default: + break; + } + } + + void StartDiscovery(uint16_t client_id, const RawAddress& address) override { + AscsDevice* dev = ascsDevices.FindByAddress(address); + if (!dev) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + LOG(WARNING) << __func__ << " " << address; + + switch(dev->state) { + case DevState::CONNECTED: { + auto iter = std::find_if(dev->connected_client_list.begin(), + dev->connected_client_list.end(), + [&client_id]( uint16_t stored_client_id) { + LOG(WARNING) << __func__ << client_id << stored_client_id; + return stored_client_id == client_id; + }); + // check if the client present in the connected client list + if(iter == dev->connected_client_list.end()) { + break; + } + // check if the discovery is already finished + // send back the same results to the other client + if(dev->discovery_completed && dev->notifications_enabled) { + sink_ase_value_list.clear(); + src_ase_value_list.clear(); + auto iter = callbacks.find(client_id); + if (iter != callbacks.end()) { + for (auto it : dev->sink_ase_list) { + memcpy(&ase, (void *) &it.ase_params, sizeof(ase)); + sink_ase_value_list.push_back(ase); + } + for (auto it : dev->src_ase_list) { + memcpy(&ase, (void *) &it.ase_params, sizeof(ase)); + src_ase_value_list.push_back(ase); + } + + AscsClientCallbacks *callback = iter->second; + // send out the callback as service discovery completed + callback->OnSearchComplete(0, dev->address, + sink_ase_value_list, + src_ase_value_list); + } + break; + } + // reset it + dev->num_ases_read = 0x00; + dev->discovery_completed = false; + dev->notifications_enabled = false; + // queue the request to GATT queue module + GattOpsQueue::ServiceSearch(client_id, dev->conn_id, &ASCS_UUID); + } break; + default: + break; + } + } + + void CodecConfig(uint16_t client_id, const RawAddress& address, + std::vector codec_configs) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + std::vector vect_val; + uint8_t opcode = static_cast (AseOpId::CODEC_CONFIG); + uint8_t num_ases = codec_configs.size(); + if (!dev || (dev->state != DevState::CONNECTED)) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + + LOG(INFO) << __func__ << ": BD Addr : " << address + << ": Num ASEs :" << loghex(num_ases); + + vect_val.insert(vect_val.end(), &opcode, &opcode + 1); + vect_val.insert(vect_val.end(), &num_ases, &num_ases + 1); + + auto it = codec_configs.begin(); + while (it != codec_configs.end()) { + vect_val.insert(vect_val.end(), &it->ase_id, &it->ase_id + 1); + vect_val.insert(vect_val.end(), &it->tgt_latency, &it->tgt_latency + 1); + vect_val.insert(vect_val.end(), &it->tgt_phy, &it->tgt_phy + 1); + vect_val.insert(vect_val.end(), it->codec_id, + ((uint8_t *)it->codec_id) + sizeof(codec_type_t)); + + vect_val.insert(vect_val.end(), &it->codec_params_len, + &it->codec_params_len + 1); + vect_val.insert(vect_val.end(), it->codec_params.begin(), + it->codec_params.end()); + + LOG(INFO) << ": ASE Id = " << loghex(it->ase_id); + LOG(INFO) << ": Target Latency = " << loghex(it->tgt_latency); + LOG(INFO) << ": target Phy = " << loghex(it->tgt_phy); + LOG(INFO) << ": Codec ID = " << loghex(it->codec_id[0]); + it++; + } + + GattOpsQueue::WriteCharacteristic(client_id, dev->conn_id, + dev->ase_cp_handle, vect_val, + GATT_WRITE, nullptr, nullptr); + } + + void QosConfig(uint16_t client_id, const RawAddress& address, + std::vector qos_configs) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + std::vector vect_val; + uint8_t opcode = static_cast (AseOpId::QOS_CONFIG); + uint8_t num_ases = qos_configs.size(); + if (!dev || (dev->state != DevState::CONNECTED)) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + LOG(INFO) << __func__ << ": BD Addr : " << address + << ": Num ASEs :" << loghex(num_ases); + + vect_val.insert(vect_val.end(), &opcode, &opcode + 1); + vect_val.insert(vect_val.end(), &num_ases, &num_ases + 1); + + auto it = qos_configs.begin(); + while (it != qos_configs.end()) { + vect_val.insert(vect_val.end(), &it->ase_id, &it->ase_id + 1); + vect_val.insert(vect_val.end(), &it->cig_id, &it->cig_id + 1); + vect_val.insert(vect_val.end(), &it->cis_id, &it->cis_id + 1); + + vect_val.insert(vect_val.end(), it->sdu_interval, + (uint8_t *)it->sdu_interval + sizeof(sdu_interval_t)); + + // test change it->framing = 0xFF; + vect_val.insert(vect_val.end(), &it->framing, &it->framing + 1); + vect_val.insert(vect_val.end(), &it->phy, &it->phy + 1); + + vect_val.insert(vect_val.end(), (uint8_t *) &it->max_sdu_size, + (uint8_t *)&it->max_sdu_size + sizeof(uint16_t)); + + vect_val.insert(vect_val.end(), &it->retrans_number, + &it->retrans_number + 1); + + vect_val.insert(vect_val.end(), (uint8_t *) &it->trans_latency, + (uint8_t *)&it->trans_latency + sizeof(uint16_t)); + + vect_val.insert(vect_val.end(), it->present_delay, + (uint8_t *)it->present_delay + sizeof(presentation_delay_t)); + + LOG(INFO) << ": ASE Id = " << loghex(it->ase_id); + LOG(INFO) << ": Cig Id = " << loghex(it->cig_id); + LOG(INFO) << ": Cis Id = " << loghex(it->cis_id); + LOG(INFO) << ": SDU interval =" + << " " << loghex(it->sdu_interval[0]) + << " " << loghex(it->sdu_interval[1]) + << " " << loghex(it->sdu_interval[2]); + LOG(INFO) << ": Framing = " << loghex(it->framing); + LOG(INFO) << ": Phy = " << loghex(it->phy); + LOG(INFO) << ": Max SDU size = " << loghex(it->max_sdu_size); + LOG(INFO) << ": RTN = " << loghex(it->retrans_number); + LOG(INFO) << ": MTL = " << loghex(it->trans_latency); + LOG(INFO) << ": PD =" + << " " << loghex(it->present_delay[0]) + << " " << loghex(it->present_delay[1]) + << " " << loghex(it->present_delay[2]); + it++; + } + + GattOpsQueue::WriteCharacteristic(client_id, dev->conn_id, + dev->ase_cp_handle, vect_val, + GATT_WRITE, nullptr, nullptr); + } + + void Enable(uint16_t client_id, const RawAddress& address, + std::vector enable_ops) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + std::vector vect_val; + uint8_t opcode = static_cast (AseOpId::ENABLE); + uint8_t num_ases = enable_ops.size(); + if (!dev || (dev->state != DevState::CONNECTED)) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + LOG(INFO) << __func__ << ": BD Addr : " << address + << ": Num ASEs :" << loghex(num_ases); + + vect_val.insert(vect_val.end(), &opcode, &opcode + 1); + vect_val.insert(vect_val.end(), &num_ases, &num_ases + 1); + + auto it = enable_ops.begin(); + while (it != enable_ops.end()) { + LOG(INFO) << ": ASE Id = " << loghex(it->ase_id); + vect_val.insert(vect_val.end(), &it->ase_id, &it->ase_id + 1); + // test change it->meta_data_len = 0xFF; + vect_val.insert(vect_val.end(), &it->meta_data_len, + &it->meta_data_len + 1); + vect_val.insert(vect_val.end(), it->meta_data.begin(), + it->meta_data.end()); + it++; + } + + GattOpsQueue::WriteCharacteristic(client_id, dev->conn_id, + dev->ase_cp_handle, vect_val, + GATT_WRITE, nullptr, nullptr); + } + + void StartReady(uint16_t client_id, const RawAddress& address, + std::vector start_ready_ops) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + std::vector vect_val; + uint8_t opcode = static_cast (AseOpId::START_READY); + uint8_t num_ases = start_ready_ops.size(); + if (!dev || (dev->state != DevState::CONNECTED)) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + LOG(INFO) << __func__ << ": BD Addr : " << address + << ": Num ASEs :" << loghex(num_ases); + + vect_val.insert(vect_val.end(), &opcode, &opcode + 1); + vect_val.insert(vect_val.end(), &num_ases, &num_ases + 1); + + auto it = start_ready_ops.begin(); + while (it != start_ready_ops.end()) { + LOG(INFO) << ": ASE Id = " << loghex(it->ase_id); + vect_val.insert(vect_val.end(), &it->ase_id, &it->ase_id + 1); + it++; + } + + GattOpsQueue::WriteCharacteristic(client_id, dev->conn_id, + dev->ase_cp_handle, vect_val, + GATT_WRITE, nullptr, nullptr); + } + + void Disable(uint16_t client_id, const RawAddress& address, + std::vector disable_ops) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + std::vector vect_val; + uint8_t opcode = static_cast (AseOpId::DISABLE); + uint8_t num_ases = disable_ops.size(); + if (!dev || (dev->state != DevState::CONNECTED)) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + LOG(INFO) << __func__ << ": BD Addr : " << address + << ": Num ASEs :" << loghex(num_ases); + + vect_val.insert(vect_val.end(), &opcode, &opcode + 1); + vect_val.insert(vect_val.end(), &num_ases, &num_ases + 1); + + auto it = disable_ops.begin(); + while (it != disable_ops.end()) { + LOG(INFO) << ": ASE Id = " << loghex(it->ase_id); + vect_val.insert(vect_val.end(), &it->ase_id, &it->ase_id + 1); + it++; + } + + GattOpsQueue::WriteCharacteristic(client_id, dev->conn_id, + dev->ase_cp_handle, vect_val, + GATT_WRITE, nullptr, nullptr); + } + + void StopReady(uint16_t client_id, const RawAddress& address, + std::vector stop_ready_ops) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + std::vector vect_val; + uint8_t opcode = static_cast (AseOpId::STOP_READY); + uint8_t num_ases = stop_ready_ops.size(); + if (!dev || (dev->state != DevState::CONNECTED)) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + LOG(INFO) << __func__ << ": BD Addr : " << address + << ": Num ASEs :" << loghex(num_ases); + + vect_val.insert(vect_val.end(), &opcode, &opcode + 1); + vect_val.insert(vect_val.end(), &num_ases, &num_ases + 1); + + auto it = stop_ready_ops.begin(); + while (it != stop_ready_ops.end()) { + LOG(INFO) << ": ASE Id = " << loghex(it->ase_id); + vect_val.insert(vect_val.end(), &it->ase_id, &it->ase_id + 1); + it++; + } + + GattOpsQueue::WriteCharacteristic(client_id, dev->conn_id, + dev->ase_cp_handle, vect_val, + GATT_WRITE, nullptr, nullptr); + } + + void Release(uint16_t client_id, const RawAddress& address, + std::vector release_ops) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + std::vector vect_val; + uint8_t opcode = static_cast (AseOpId::RELEASE); + uint8_t num_ases = release_ops.size(); + if (!dev || (dev->state != DevState::CONNECTED)) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + LOG(INFO) << __func__ << ": BD Addr : " << address + << ": Num ASEs :" << loghex(num_ases); + + vect_val.insert(vect_val.end(), &opcode, &opcode + 1); + vect_val.insert(vect_val.end(), &num_ases, &num_ases + 1); + + auto it = release_ops.begin(); + while (it != release_ops.end()) { + LOG(INFO) << ": ASE Id = " << loghex(it->ase_id); + vect_val.insert(vect_val.end(), &it->ase_id, &it->ase_id + 1); + it++; + } + + GattOpsQueue::WriteCharacteristic(client_id, dev->conn_id, + dev->ase_cp_handle, vect_val, + GATT_WRITE, nullptr, nullptr); + } + + void UpdateStream(uint16_t client_id, const RawAddress& address, + std::vector metadata_ops) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + std::vector vect_val; + uint8_t opcode = static_cast (AseOpId::UPDATE_META_DATA); + uint8_t num_ases = metadata_ops.size(); + if (!dev || (dev->state != DevState::CONNECTED)) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + LOG(INFO) << __func__ << ": BD Addr : " << address + << ": Num ASEs :" << loghex(num_ases); + + vect_val.insert(vect_val.end(), &opcode, &opcode + 1); + vect_val.insert(vect_val.end(), &num_ases, &num_ases + 1); + + auto it = metadata_ops.begin(); + while (it != metadata_ops.end()) { + LOG(INFO) << ": ASE Id = " << loghex(it->ase_id); + vect_val.insert(vect_val.end(), &it->ase_id, &it->ase_id + 1); + vect_val.insert(vect_val.end(), &it->meta_data_len, + &it->meta_data_len + 1); + vect_val.insert(vect_val.end(), it->meta_data.begin(), + it->meta_data.end()); + it++; + } + + GattOpsQueue::WriteCharacteristic(client_id, dev->conn_id, + dev->ase_cp_handle, vect_val, + GATT_WRITE, nullptr, nullptr); + } + + bool GetAseParams(const RawAddress& address, uint8_t ase_id, + AseParams *ase_params) { + bool ase_found = false; + AscsDevice* dev = ascsDevices.FindByAddress(address); + if (!dev) { + LOG(INFO) << "Device not connected to profile" << address; + return false; + } + + // first look for sink ASEs + for (auto it = dev->sink_ase_list.begin(); + it != dev->sink_ase_list.end(); it++) { + if (it->ase_params.ase_id == ase_id) { + *ase_params = it->ase_params; + ase_found = true; + break; + } + } + if(ase_found) return ase_found; + + for (auto it = dev->src_ase_list.begin(); + it != dev->src_ase_list.end(); it++) { + if (it->ase_params.ase_id == ase_id) { + *ase_params = it->ase_params; + ase_found = true; + break; + } + } + return ase_found; + } + + bool GetAseHandle(const RawAddress& address, uint8_t ase_id, + uint16_t *ase_handle) { + bool ase_found = false; + AscsDevice* dev = ascsDevices.FindByAddress(address); + if (!dev) { + LOG(INFO) << "Device not connected to profile" << address; + return false; + } + + // first look for sink ASEs + for (auto it = dev->sink_ase_list.begin(); + it != dev->sink_ase_list.end(); it++) { + if (it->ase_params.ase_id == ase_id) { + *ase_handle = it->ase_handle; + ase_found = true; + break; + } + } + if(ase_found) return ase_found; + + for (auto it = dev->src_ase_list.begin(); + it != dev->src_ase_list.end(); it++) { + if (it->ase_params.ase_id == ase_id) { + *ase_handle = it->ase_handle; + ase_found = true; + break; + } + } + return ase_found; + } + + void GetAseState(uint16_t client_id, const RawAddress& address, + uint8_t ase_id) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + if (!dev) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + LOG(WARNING) << __func__ << " " << address; + + switch(dev->state) { + case DevState::CONNECTED: { + uint16_t ase_handle; + auto iter = std::find_if(dev->connected_client_list.begin(), + dev->connected_client_list.end(), + [&client_id]( uint16_t stored_client_id) { + return stored_client_id == client_id; + }); + // check if the client present in the connected client list + if(iter == dev->connected_client_list.end()) { + break; + } + + // check if the discovery is already finished + // send back the same results to the other client + if(dev->discovery_completed && dev->notifications_enabled) { + auto iter = callbacks.find(client_id); + AseParams ase_params; + if(iter != callbacks.end() && + GetAseParams(address, ase_id, &ase_params)) { + AscsClientCallbacks *callback = iter->second; + callback->OnAseState(dev->address, ase_params); + } + break; + } + + if(GetAseHandle(address, ase_id, &ase_handle)) { + // queue the request to GATT queue module + GattOpsQueue::ReadCharacteristic(client_id, dev->conn_id, + ase_handle, + AscsClientImpl::OnReadAseStateStatic, nullptr); + } + } break; + default: + LOG(WARNING) << __func__ << "un-handled event"; + break; + } + } + + void OnGattConnected(tGATT_STATUS status, uint16_t conn_id, + tGATT_IF client_if, RawAddress address, + tBTA_TRANSPORT transport, uint16_t mtu) { + + AscsDevice* dev = ascsDevices.FindByAddress(address); + if (!dev) { + /* When device is quickly disabled and enabled in settings, this case + * might happen */ + LOG(ERROR) << "Closing connection to non ascs device, address=" + << address; + BTA_GATTC_Close(conn_id); + return; + } + LOG(INFO) << __func__ << ": BD Addr : " << address + << ": Status : " << loghex(status); + + if(dev->state == DevState::CONNECTING) { + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Failed to connect to ASCS device"; + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if(it->type == ProfleOP::CONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + AscsClientCallbacks *callback = iter->second; + callback->OnConnectionState(address, GattState::DISCONNECTED); + } + dev->profile_queue.erase(it); + } else { + it++; + } + } + dev->state = DevState::IDLE; + ascsDevices.Remove(address); + return; + } + } else if(dev->state == DevState::DISCONNECTING) { + // TODO will this happens ? + // it could have called the cancel open to expect the + // open cancelled event + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Failed to connect to ASCS device"; + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if(it->type == ProfleOP::DISCONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + AscsClientCallbacks *callback = iter->second; + callback->OnConnectionState(address, GattState::DISCONNECTED); + } + dev->profile_queue.erase(it); + } else { + it++; + } + } + dev->state = DevState::IDLE; + ascsDevices.Remove(address); + return; + } else { + // gatt connected successfully + // if the disconnect entry is found we need to initiate the + // gatt disconnect. may be a race condition just after sending + // cancel open gatt connected event received + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if(it->type == ProfleOP::DISCONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + // Removes all registrations for connection. + BTA_GATTC_CancelOpen(dev->conn_id, address, false); + GattOpsQueue::Clean(dev->conn_id); + BTA_GATTC_Close(dev->conn_id); + break; + } + } else { + it++; + } + } + return; + } + } else { + // return unconditinally + return; + } + + // success scenario code + dev->conn_id = conn_id; + + tACL_CONN* p_acl = btm_bda_to_acl(address, BT_TRANSPORT_LE); + if (p_acl != nullptr && + controller_get_interface()->supports_ble_2m_phy() && + HCI_LE_2M_PHY_SUPPORTED(p_acl->peer_le_features)) { + LOG(INFO) << address << " set preferred PHY to 2M"; + BTM_BleSetPhy(address, PHY_LE_2M, PHY_LE_2M, 0); + } + + /* verify bond */ + uint8_t sec_flag = 0; + BTM_GetSecurityFlagsByTransport(address, &sec_flag, BT_TRANSPORT_LE); + + if (sec_flag & BTM_SEC_FLAG_ENCRYPTED) { + /* if link has been encrypted */ + OnEncryptionComplete(address, true); + return; + } + + if (sec_flag & BTM_SEC_FLAG_LKEY_KNOWN) { + /* if bonded and link not encrypted */ + sec_flag = BTM_BLE_SEC_ENCRYPT; + LOG(WARNING) << "trying to encrypt now"; + BTM_SetEncryption(address, BTA_TRANSPORT_LE, encryption_callback, nullptr, + sec_flag); + return; + } + + /* otherwise let it go through */ + OnEncryptionComplete(address, true); + } + + void OnGattDisconnected(tGATT_STATUS status, uint16_t conn_id, + tGATT_IF client_if, RawAddress remote_bda, + tBTA_GATT_REASON reason) { + AscsDevice* dev = ascsDevices.FindByAddress(remote_bda); + if (!dev) { + LOG(ERROR) << "Skipping unknown device disconnect, conn_id=" + << loghex(conn_id); + return; + } + + LOG(INFO) << __func__ << ": BD Addr : " << remote_bda + << ", Status : " << loghex(status) + << ", state: " << static_cast(dev->state); + + switch(dev->state) { + case DevState::CONNECTING: { + // sudden disconnection + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if(it->type == ProfleOP::CONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + AscsClientCallbacks *callback = iter->second; + callback->OnConnectionState(remote_bda, GattState::DISCONNECTED); + } + it = dev->profile_queue.erase(it); + } else { + it++; + } + } + } break; + case DevState::CONNECTED: { + // sudden disconnection + for (auto it = dev->connected_client_list.begin(); + it != dev->connected_client_list.end();) { + // get the callback and update the upper layers + auto iter = callbacks.find(*it); + if (iter != callbacks.end()) { + AscsClientCallbacks *callback = iter->second; + callback->OnConnectionState(remote_bda, GattState::DISCONNECTED); + } + it = dev->connected_client_list.erase(it); + } + } break; + case DevState::DISCONNECTING: { + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if(it->type == ProfleOP::DISCONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + AscsClientCallbacks *callback = iter->second; + callback->OnConnectionState(remote_bda, GattState::DISCONNECTED); + } + it = dev->profile_queue.erase(it); + } else { + it++; + } + } + + for (auto it = dev->connected_client_list.begin(); + it != dev->connected_client_list.end();) { + // get the callback and update the upper layers + it = dev->connected_client_list.erase(it); + } + // check if the connection queue is not empty + // if not initiate the Gatt connection + } break; + default: + break; + } + + if (dev->conn_id) { + GattOpsQueue::Clean(dev->conn_id); + BTA_GATTC_Close(dev->conn_id); + dev->conn_id = 0; + } + + dev->state = DevState::IDLE; + ascsDevices.Remove(remote_bda); + } + + void OnConnectionUpdateComplete(uint16_t conn_id, tBTA_GATTC* p_data) { + AscsDevice* dev = ascsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(ERROR) << "Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + } + + void OnEncryptionComplete(const RawAddress& address, bool success) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + if (!dev) { + LOG(ERROR) << "Skipping unknown device" << address; + return; + } + + if(dev->state != DevState::CONNECTING) { + LOG(ERROR) << "received in wrong state" << address; + return; + } + + LOG(INFO) << __func__ << ": BD Addr : " << address + << ": Status : " << loghex(success); + + // encryption failed + if (!success) { + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if(it->type == ProfleOP::CONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + AscsClientCallbacks *callback = iter->second; + callback->OnConnectionState(address, GattState::DISCONNECTED); + } + // change the type to disconnect + it->type = ProfleOP::DISCONNECT; + } else { + it++; + } + } + dev->state = DevState::DISCONNECTING; + // Removes all registrations for connection. + BTA_GATTC_CancelOpen(dev->conn_id, address, false); + BTA_GATTC_Close(dev->conn_id); + } else { + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if(it->type == ProfleOP::CONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + dev->connected_client_list.push_back(it->client_id); + AscsClientCallbacks *callback = iter->second; + callback->OnConnectionState(address, GattState::CONNECTED); + } + dev->profile_queue.erase(it); + } else { + it++; + } + } + dev->state = DevState::CONNECTED; + } + } + + void OnServiceChangeEvent(const RawAddress& address) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + if (!dev) { + LOG(ERROR) << "Skipping unknown device" << address; + return; + } + LOG(INFO) << __func__ << ": address=" << address; + dev->first_connection = true; + dev->service_changed_rcvd = true; + GattOpsQueue::Clean(dev->conn_id); + } + + void OnServiceDiscDoneEvent(const RawAddress& address) { + AscsDevice* dev = ascsDevices.FindByAddress(address); + if (!dev) { + LOG(ERROR) << "Skipping unknown device" << address; + return; + } + if (dev->service_changed_rcvd) { + // queue the request to GATT queue module with dummu client id + GattOpsQueue::ServiceSearch(0XFF, dev->conn_id, &ASCS_UUID); + } + } + + void OnServiceSearchComplete(uint16_t conn_id, tGATT_STATUS status) { + AscsDevice* dev = ascsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(ERROR) << "Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + + LOG(INFO) << __func__ << ": BD Addr : " << dev->address + << ": Status : " << loghex(status); + + uint16_t client_id = GattOpsQueue::ServiceSearchComplete(conn_id, + status); + auto iter = callbacks.find(client_id); + if (status != GATT_SUCCESS) { + /* close connection and report service discovery complete with error */ + LOG(ERROR) << "Service discovery failed"; + if (iter != callbacks.end()) { + AscsClientCallbacks *callback = iter->second; + std::vector ase_value_list; + callback->OnSearchComplete(0xFF, dev->address, ase_value_list, + ase_value_list); + } + return; + } + + const std::vector* services = BTA_GATTC_GetServices(conn_id); + + const gatt::Service* service = nullptr; + if (services) { + for (const gatt::Service& tmp : *services) { + if (tmp.uuid == Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER)) { + LOG(INFO) << "Found UUID_SERVCLASS_GATT_SERVER, handle=" + << loghex(tmp.handle); + const gatt::Service* service_changed_service = &tmp; + find_server_changed_ccc_handle(conn_id, service_changed_service); + } else if (tmp.uuid == ASCS_UUID) { + LOG(INFO) << "Found ASCS service, handle=" << loghex(tmp.handle); + service = &tmp; + } + } + } else { + LOG(ERROR) << "no services found for conn_id: " << conn_id; + return; + } + + if (!service) { + LOG(ERROR) << "No ASCS service found"; + if (iter != callbacks.end()) { + AscsClientCallbacks *callback = iter->second; + std::vector ase_value_list; + callback->OnSearchComplete(0xFF, dev->address, ase_value_list, + ase_value_list); + } + return; + } + + for (const gatt::Characteristic& charac : service->characteristics) { + if (charac.uuid == ASCS_SINK_ASE_UUID || + charac.uuid == ASCS_SRC_ASE_UUID) { + Ase ase_info; + ase_info.ase_handle = charac.value_handle; + GattOpsQueue::ReadCharacteristic( + client_id, conn_id, charac.value_handle, + AscsClientImpl::OnReadOnlyPropertiesReadStatic, nullptr); + + ase_info.ase_ccc_handle = + find_ccc_handle(conn_id, charac.value_handle); + + if(charac.uuid == ASCS_SINK_ASE_UUID) { + dev->sink_ase_list.push_back(ase_info); + } else if(charac.uuid == ASCS_SRC_ASE_UUID) { + dev->src_ase_list.push_back(ase_info); + } + if(ase_info.ase_ccc_handle) { + /* Register and enable the Audio Status Notification */ + tGATT_STATUS register_status; + register_status = BTA_GATTC_RegisterForNotifications( + conn_id, dev->address, ase_info.ase_handle); + if (register_status != GATT_SUCCESS) { + LOG(ERROR) << __func__ + << ": BTA_GATTC_RegisterForNotifications failed, status=" + << loghex(register_status); + } + std::vector value(2); + uint8_t* ptr = value.data(); + UINT16_TO_STREAM(ptr, GATT_CHAR_CLIENT_CONFIG_NOTIFICATION); + GattOpsQueue::WriteDescriptor( + client_id, conn_id, ase_info.ase_ccc_handle, + std::move(value), GATT_WRITE, nullptr, nullptr); + } + } else if (charac.uuid == ASCS_ASE_CP_UUID) { + dev->ase_cp_handle = charac.value_handle; + + dev->ase_cp_ccc_handle = + find_ccc_handle(conn_id, charac.value_handle); + if(dev->ase_cp_ccc_handle) { + /* Register and enable the Audio Status Notification */ + tGATT_STATUS register_status; + register_status = BTA_GATTC_RegisterForNotifications( + conn_id, dev->address, dev->ase_cp_handle); + if (register_status != GATT_SUCCESS) { + LOG(ERROR) << __func__ + << ": BTA_GATTC_RegisterForNotifications failed, status=" + << loghex(register_status); + } + std::vector value(2); + uint8_t* ptr = value.data(); + UINT16_TO_STREAM(ptr, GATT_CHAR_CLIENT_CONFIG_NOTIFICATION); + GattOpsQueue::WriteDescriptor( + client_id, conn_id, dev->ase_cp_ccc_handle, + std::move(value), GATT_WRITE, nullptr, nullptr); + } + } else { + LOG(WARNING) << "Unknown characteristic found:" << charac.uuid; + } + } + + dev->notifications_enabled = true; + + if (dev->service_changed_rcvd) { + dev->service_changed_rcvd = false; + } + } + + const char* GetAseState(uint8_t event) { + switch (event) { + CASE_RETURN_STR(ASE_STATE_IDLE) + CASE_RETURN_STR(ASE_STATE_CODEC_CONFIGURED) + CASE_RETURN_STR(ASE_STATE_QOS_CONFIGURED) + CASE_RETURN_STR(ASE_STATE_ENABLING) + CASE_RETURN_STR(ASE_STATE_STREAMING) + CASE_RETURN_STR(ASE_STATE_DISABLING) + CASE_RETURN_STR(ASE_STATE_RELEASING) + default: + return "Unknown State"; + } + } + + const char* GetAseDirection(uint8_t event) { + switch (event) { + CASE_RETURN_STR(ASE_DIRECTION_SINK) + CASE_RETURN_STR(ASE_DIRECTION_SOURCE) + default: + return "Unknown Direction"; + } + } + + void ParseAseParams(uint8_t *p, AseParams *ase_params, uint8_t ase_dir) { + STREAM_TO_UINT8(ase_params->ase_id, p); + STREAM_TO_UINT8(ase_params->ase_state, p); + LOG(INFO) << __func__ + << ": ASE Id = " << loghex(ase_params->ase_id) + << ": ASE State = " << GetAseState(ase_params->ase_state) + << ": ASE Direction = " << GetAseDirection(ase_dir); + switch(ase_params->ase_state) { + case ASE_STATE_CODEC_CONFIGURED: { + AseCodecConfigParams *codec_config = + &ase_params->codec_config_params; + STREAM_TO_UINT8(codec_config->framing, p); + STREAM_TO_UINT8(codec_config->pref_phy, p); + + STREAM_TO_UINT8(codec_config->pref_rtn, p); + STREAM_TO_UINT16(codec_config->mtl, p); + STREAM_TO_ARRAY(&(codec_config->pd_min), p, + static_cast (sizeof(presentation_delay_t))); + STREAM_TO_ARRAY(&(codec_config->pd_max), p, + static_cast (sizeof(presentation_delay_t))); + STREAM_TO_ARRAY(&(codec_config->pref_pd_min), p, + static_cast (sizeof(presentation_delay_t))); + STREAM_TO_ARRAY(&(codec_config->pref_pd_max), p, + static_cast (sizeof(presentation_delay_t))); + STREAM_TO_ARRAY(&(codec_config->codec_id), + p, static_cast (sizeof(codec_type_t))); + STREAM_TO_UINT8(codec_config->codec_params_len, p); + if(codec_config->codec_params_len) { + codec_config->codec_params.resize(codec_config->codec_params_len); + STREAM_TO_ARRAY(codec_config->codec_params.data(), + p, codec_config->codec_params_len); + } + LOG(INFO) << ": Framing = " << loghex(codec_config->framing); + LOG(INFO) << ": Pref Phy = " << loghex(codec_config->pref_phy); + LOG(INFO) << ": Pref RTN = " << loghex(codec_config->pref_rtn); + LOG(INFO) << ": MTL = " << loghex(codec_config->mtl); + LOG(INFO) << ": PD Min =" + << " " << loghex(codec_config->pd_min[0]) + << " " << loghex(codec_config->pd_min[1]) + << " " << loghex(codec_config->pd_min[2]); + LOG(INFO) << ": PD Max =" + << " " << loghex(codec_config->pd_max[0]) + << " " << loghex(codec_config->pd_max[1]) + << " " << loghex(codec_config->pd_max[2]); + LOG(INFO) << ": Pref PD Min =" + << " " << loghex(codec_config->pref_pd_min[0]) + << " " << loghex(codec_config->pref_pd_min[1]) + << " " << loghex(codec_config->pref_pd_min[2]); + LOG(INFO) << ": Pref PD Max =" + << " " << loghex(codec_config->pref_pd_max[0]) + << " " << loghex(codec_config->pref_pd_max[1]) + << " " << loghex(codec_config->pref_pd_max[2]); + + LOG(INFO) << ": Codec ID = " << loghex(codec_config->codec_id[0]); + } break; + case ASE_STATE_QOS_CONFIGURED: { + AseQosConfigParams *qos_config = &ase_params->qos_config_params; + STREAM_TO_UINT8(qos_config->cig_id, p); + STREAM_TO_UINT8(qos_config->cis_id, p); + STREAM_TO_ARRAY(&(qos_config->sdu_interval), p, + static_cast (sizeof(sdu_interval_t))); + STREAM_TO_UINT8(qos_config->framing, p); + STREAM_TO_UINT8(qos_config->phy, p); + STREAM_TO_UINT16(qos_config->max_sdu_size, p); + STREAM_TO_UINT8(qos_config->rtn, p); + STREAM_TO_UINT16(qos_config->mtl, p); + STREAM_TO_ARRAY(&(qos_config->pd), p, + static_cast (sizeof(presentation_delay_t))); + + LOG(INFO) << ": Cig Id = " << loghex(qos_config->cig_id); + LOG(INFO) << ": Cis Id = " << loghex(qos_config->cis_id); + LOG(INFO) << ": SDU interval =" + << " " << loghex(qos_config->sdu_interval[0]) + << " " << loghex(qos_config->sdu_interval[1]) + << " " << loghex(qos_config->sdu_interval[2]); + LOG(INFO) << ": Framing = " << loghex(qos_config->framing); + LOG(INFO) << ": Phy = " << loghex(qos_config->phy); + LOG(INFO) << ": Max SDU size = " << loghex(qos_config->max_sdu_size); + LOG(INFO) << ": RTN = " << loghex(qos_config->rtn); + LOG(INFO) << ": MTL = " << loghex(qos_config->mtl); + LOG(INFO) << ": PD =" + << " " << loghex(qos_config->pd[0]) + << " " << loghex(qos_config->pd[1]) + << " " << loghex(qos_config->pd[2]); + } break; + case ASE_STATE_ENABLING: + case ASE_STATE_STREAMING: + case ASE_STATE_DISABLING: { + AseGenericParams *gen_params = &ase_params->generic_params; + STREAM_TO_UINT8(gen_params->cig_id, p); + STREAM_TO_UINT8(gen_params->cis_id, p); + STREAM_TO_UINT8(gen_params->meta_data_len, p); + if(gen_params->meta_data_len) { + gen_params->meta_data.resize(gen_params->meta_data_len); + STREAM_TO_ARRAY(gen_params->meta_data.data(), + p, gen_params->meta_data_len); + } + LOG(INFO) << ": Cig Id = " << loghex(gen_params->cig_id); + LOG(INFO) << ": Cis Id = " << loghex(gen_params->cis_id); + } break; + } + } + + void ParseAseNotification(uint16_t conn_id, + uint16_t handle, uint16_t len, uint8_t* value ) { + uint8_t *p = value; + bool ase_found = false; + AscsDevice* dev = ascsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(INFO) << __func__ + << ": Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + + for (auto it = dev->sink_ase_list.begin(); + it != dev->sink_ase_list.end(); it++) { + if (it->ase_handle == handle) { + LOG(INFO) << __func__ << ": BD Addr : " << dev->address; + ParseAseParams(p, &it->ase_params, ASE_DIRECTION_SINK); + for (auto iter : callbacks) { + AscsClientCallbacks *ascs_callback = iter.second; + ascs_callback->OnAseState(dev->address, it->ase_params); + } + ase_found = true; + break; + } + } + if(!ase_found) { + for (auto it = dev->src_ase_list.begin(); + it != dev->src_ase_list.end(); it++) { + if (it->ase_handle == handle) { + LOG(INFO) << __func__ << ": BD Addr : " << dev->address; + ParseAseParams(p, &it->ase_params,ASE_DIRECTION_SOURCE); + for (auto iter : callbacks) { + AscsClientCallbacks *ascs_callback = iter.second; + ascs_callback->OnAseState(dev->address, it->ase_params); + } + ase_found = true; + break; + } + } + } + } + + void OnNotificationEvent(uint16_t conn_id, uint16_t handle, uint16_t len, + uint8_t* value) { + uint8_t* p = value; + AscsDevice* dev = ascsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(INFO) << __func__ + << ": Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + + // check if the notification is for ASEs + if( dev->ase_cp_handle == handle) { // control point notification + AseCpNotification cp_notification; + STREAM_TO_UINT8(cp_notification.ase_opcode, p); + STREAM_TO_UINT8(cp_notification.num_ases, p); + uint8_t num_ases = cp_notification.num_ases; + std::vector ase_cp_notify_list; + AseOpStatus status; + bool notify = false; + + while(num_ases--) { + STREAM_TO_UINT8(status.ase_id, p); + STREAM_TO_UINT8(status.resp_code, p); + STREAM_TO_UINT8(status.reason, p); + if(status.resp_code) { + LOG(ERROR) << __func__ + << ": ASE Id = " << loghex(status.ase_id) + << ": Resp code = " << resp_codes[status.resp_code]; + if(status.reason) { + LOG(ERROR) << ": Reason = " << reason_codes[status.reason]; + } + notify = true; + } + + ase_cp_notify_list.push_back(status); + } + if(notify) { + for (auto iter : callbacks) { + AscsClientCallbacks *ascs_callback = iter.second; + LOG(ERROR) << __func__ << " ASE Operation failed"; + ascs_callback->OnAseOpFailed(dev->address, + (AseOpId) cp_notification.ase_opcode, + ase_cp_notify_list); + } + } + } else { + ParseAseNotification(conn_id, handle, len, value); + } + } + + + void OnCongestionEvent(uint16_t conn_id, bool congested) { + AscsDevice* dev = ascsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(INFO) << __func__ + << ": Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + + LOG(WARNING) << __func__ << ": conn_id:" << loghex(conn_id) + << ", congested: " << congested; + dev->is_congested = congested; + GattOpsQueue::CongestionCallback(conn_id, congested); + } + + void OnReadAseState(uint16_t client_id, + uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, uint8_t* value, + void* data) { + + AscsDevice* dev = ascsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(ERROR) << __func__ << "unknown conn_id=" << loghex(conn_id); + return; + } + LOG(WARNING) << __func__; + + // check if the notification is for ASEs + ParseAseNotification(conn_id, handle, len, value); + } + + void OnReadOnlyPropertiesRead(uint16_t client_id, + uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t *value, void* data) { + AscsDevice* dev = ascsDevices.FindByConnId(conn_id); + uint8_t *p = value; + if (!dev) { + LOG(ERROR) << __func__ << "unknown conn_id=" << loghex(conn_id); + return; + } + + for (auto it = dev->sink_ase_list.begin(); + it != dev->sink_ase_list.end(); it++) { + if (it->ase_handle == handle) { + dev->num_ases_read++; + ParseAseParams(p, &it->ase_params, ASE_DIRECTION_SINK); + break; + } + } + + for (auto it = dev->src_ase_list.begin(); + it != dev->src_ase_list.end(); it++) { + if (it->ase_handle == handle) { + dev->num_ases_read++; + ParseAseParams(p, &it->ase_params, ASE_DIRECTION_SOURCE); + break; + } + } + + LOG(INFO) << __func__ << ": num_ases_read : " + << loghex(dev->num_ases_read); + + if(dev->num_ases_read == (dev->sink_ase_list.size() + + dev->src_ase_list.size())) { + sink_ase_value_list.clear(); + src_ase_value_list.clear(); + dev->discovery_completed = true; + // Now update using service discovery callback + auto iter = callbacks.find(client_id); + if (iter != callbacks.end()) { + for (auto it : dev->sink_ase_list) { + memcpy(&ase, (void *) &it.ase_params, sizeof(ase)); + sink_ase_value_list.push_back(ase); + } + for (auto it : dev->src_ase_list) { + memcpy(&ase, (void *) &it.ase_params, sizeof(ase)); + src_ase_value_list.push_back(ase); + } + AscsClientCallbacks *callback = iter->second; + // check if all ascs characteristics are read + // send out the callback as service discovery completed + callback->OnSearchComplete(0, dev->address, + sink_ase_value_list, + src_ase_value_list); + } + } + } + + static void OnReadOnlyPropertiesReadStatic(uint16_t client_id, + uint16_t conn_id, + tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + if (instance) + instance->OnReadOnlyPropertiesRead(client_id, conn_id, status, handle, + len, value, data); + } + + static void OnReadAseStateStatic(uint16_t client_id, + uint16_t conn_id, + tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + if (instance) + instance->OnReadAseState(client_id, conn_id, status, handle, + len, value, data); + } + + private: + uint8_t gatt_client_id = BTA_GATTS_INVALID_IF; + uint16_t ascs_client_id = 0; + AscsDevices ascsDevices; + // client id to callbacks mapping + std::map callbacks; + + void find_server_changed_ccc_handle(uint16_t conn_id, + const gatt::Service* service) { + AscsDevice* ascsDevice = ascsDevices.FindByConnId(conn_id); + if (!ascsDevice) { + LOG(ERROR) << "Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + for (const gatt::Characteristic& charac : service->characteristics) { + if (charac.uuid == Uuid::From16Bit(GATT_UUID_GATT_SRV_CHGD)) { + ascsDevice->srv_changed_ccc_handle = + find_ccc_handle(conn_id, charac.value_handle); + if (!ascsDevice->srv_changed_ccc_handle) { + LOG(ERROR) << __func__ + << ": cannot find service changed CCC descriptor"; + continue; + } + LOG(INFO) << __func__ << " service_changed_ccc=" + << loghex(ascsDevice->srv_changed_ccc_handle); + break; + } + } + } + + // Find the handle for the client characteristics configuration of a given + // characteristics + uint16_t find_ccc_handle(uint16_t conn_id, uint16_t char_handle) { + const gatt::Characteristic* p_char = + BTA_GATTC_GetCharacteristic(conn_id, char_handle); + + if (!p_char) { + LOG(WARNING) << __func__ << ": No such characteristic: " << char_handle; + return 0; + } + + for (const gatt::Descriptor& desc : p_char->descriptors) { + if (desc.uuid == Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)) + return desc.handle; + } + + return 0; + } +}; + +const char* get_gatt_event_name(uint32_t event) { + switch (event) { + CASE_RETURN_STR(BTA_GATTC_DEREG_EVT) + CASE_RETURN_STR(BTA_GATTC_OPEN_EVT) + CASE_RETURN_STR(BTA_GATTC_CLOSE_EVT) + CASE_RETURN_STR(BTA_GATTC_SEARCH_CMPL_EVT) + CASE_RETURN_STR(BTA_GATTC_NOTIF_EVT) + CASE_RETURN_STR(BTA_GATTC_ENC_CMPL_CB_EVT) + CASE_RETURN_STR(BTA_GATTC_CONN_UPDATE_EVT) + CASE_RETURN_STR(BTA_GATTC_SRVC_CHG_EVT) + CASE_RETURN_STR(BTA_GATTC_SRVC_DISC_DONE_EVT) + CASE_RETURN_STR(BTA_GATTC_CONGEST_EVT) + default: + return "Unknown Event"; + } +} + +void ascs_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { + if (p_data == nullptr || !instance) return; + + LOG(INFO) << __func__ << ": Event : " << get_gatt_event_name(event); + + switch (event) { + case BTA_GATTC_DEREG_EVT: + break; + + case BTA_GATTC_OPEN_EVT: { + tBTA_GATTC_OPEN& o = p_data->open; + instance->OnGattConnected(o.status, o.conn_id, o.client_if, o.remote_bda, + o.transport, o.mtu); + break; + } + + case BTA_GATTC_CLOSE_EVT: { + tBTA_GATTC_CLOSE& c = p_data->close; + instance->OnGattDisconnected(c.status, c.conn_id, c.client_if, + c.remote_bda, c.reason); + } break; + + case BTA_GATTC_SEARCH_CMPL_EVT: + instance->OnServiceSearchComplete(p_data->search_cmpl.conn_id, + p_data->search_cmpl.status); + break; + + case BTA_GATTC_NOTIF_EVT: + if (!p_data->notify.is_notify || + p_data->notify.len > GATT_MAX_ATTR_LEN) { + LOG(ERROR) << __func__ << ": rejected BTA_GATTC_NOTIF_EVT. is_notify=" + << p_data->notify.is_notify + << ", len=" << p_data->notify.len; + break; + } + instance->OnNotificationEvent(p_data->notify.conn_id, + p_data->notify.handle, p_data->notify.len, + p_data->notify.value); + break; + + case BTA_GATTC_ENC_CMPL_CB_EVT: + instance->OnEncryptionComplete(p_data->enc_cmpl.remote_bda, true); + break; + + case BTA_GATTC_CONN_UPDATE_EVT: + instance->OnConnectionUpdateComplete(p_data->conn_update.conn_id, p_data); + break; + + case BTA_GATTC_SRVC_CHG_EVT: + instance->OnServiceChangeEvent(p_data->remote_bda); + break; + + case BTA_GATTC_SRVC_DISC_DONE_EVT: + instance->OnServiceDiscDoneEvent(p_data->remote_bda); + break; + case BTA_GATTC_CONGEST_EVT: + instance->OnCongestionEvent(p_data->congest.conn_id, + p_data->congest.congested); + break; + default: + break; + } +} + +void encryption_callback(const RawAddress* address, tGATT_TRANSPORT, void*, + tBTM_STATUS status) { + if (instance) { + instance->OnEncryptionComplete(*address, + status == BTM_SUCCESS ? true : false); + } +} + +void AscsClient::Init(AscsClientCallbacks* callbacks) { + if (instance) { + instance->Register(callbacks); + } else { + instance = new AscsClientImpl(); + instance->Register(callbacks); + } +} + +void AscsClient::CleanUp(uint16_t client_id) { + if(instance->GetClientCount()) { + instance->Deregister(client_id); + if(!instance->GetClientCount()) { + delete instance; + instance = nullptr; + } + } +} + +AscsClient* AscsClient::Get() { + CHECK(instance); + return instance; +} + +} // namespace ascs +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/bap/connected_iso.cc b/le_audio/system/bt/bta/bap/connected_iso.cc new file mode 100644 index 00000000000..8f2c99f115f --- /dev/null +++ b/le_audio/system/bt/bta/bap/connected_iso.cc @@ -0,0 +1,1542 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +#include "bta_bap_uclient_api.h" +#include "btm_int.h" +#include +#include "state_machine.h" +#include "stack/include/btm_ble_api_types.h" +#include "bt_trace.h" +#include "btif_util.h" +#include "osi/include/properties.h" + +namespace bluetooth { +namespace bap { +namespace cis { + +typedef struct { + uint8_t status; + uint16_t cis_handle; + uint8_t reason; +} tBTM_BLE_CIS_DISCONNECTED_EVT_PARAM; + +typedef struct { + uint8_t status; + uint16_t conn_handle; +} tBTM_BLE_CIS_DATA_PATH_EVT_PARAM; + +typedef struct { +uint8_t status; +uint8_t cig_id; +} tBTM_BLE_SET_CIG_REMOVE_PARAM; + +struct CIS; +class CisInterfaceCallbacks; +using bluetooth::bap::cis::CisInterfaceCallbacks; + +struct tIsoSetUpDataPath { + uint16_t conn_handle; + uint8_t data_path_direction; + uint8_t data_path_id; +}; + +struct tIsoRemoveDataPath { + uint16_t conn_handle; + uint8_t data_path_direction; +}; + +enum IsoHciEvent { + CIG_CONFIGURE_REQ = 0, + CIG_CONFIGURED_EVT, + CIS_CREATE_REQ, + CIS_STATUS_EVT, + CIS_ESTABLISHED_EVT, + CIS_DISCONNECT_REQ, + CIS_DISCONNECTED_EVT, + CIG_REMOVE_REQ, + CIG_REMOVED_EVT, + SETUP_DATA_PATH_REQ, + SETUP_DATA_PATH_DONE_EVT, + REMOVE_DATA_PATH_REQ, + REMOVE_DATA_PATH_DONE_EVT, + CIS_CREATE_REQ_DUMMY +}; + +struct DataPathNode { + IsoHciEvent type; + union { + tIsoSetUpDataPath setup_datapath; + tIsoRemoveDataPath rmv_datapath; + }; +}; + +class CisStateMachine : public bluetooth::common::StateMachine { + public: + enum { + kStateIdle, + kStateSettingDataPath, + kStateReady, + kStateEstablishing, + kStateDestroying, + kStateEstablished, + }; + + class StateIdle : public State { + public: + StateIdle(CisStateMachine& sm) + : State(sm, kStateIdle), cis_(sm.GetCis()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Idle"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + CIS &cis_; + }; + + class StateSettingDataPath : public State { + public: + StateSettingDataPath(CisStateMachine& sm) + : State(sm, kStateSettingDataPath), cis_(sm.GetCis()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "SettingDataPath"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + CIS &cis_; + }; + + class StateReady : public State { + public: + StateReady(CisStateMachine& sm) + : State(sm, kStateReady), cis_(sm.GetCis()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Ready"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + CIS &cis_; + }; + + class StateDestroying : public State { + public: + StateDestroying(CisStateMachine& sm) + : State(sm, kStateDestroying), cis_(sm.GetCis()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Destroying"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + CIS &cis_; + }; + + class StateEstablishing : public State { + public: + StateEstablishing(CisStateMachine& sm) + : State(sm, kStateEstablishing), cis_(sm.GetCis()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Establishing"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + CIS &cis_; + }; + + class StateEstablished : public State { + public: + StateEstablished(CisStateMachine& sm) + : State(sm, kStateEstablished), cis_(sm.GetCis()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Established"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + CIS &cis_; + }; + + CisStateMachine(CIS &cis) : + cis(cis) { + state_idle_ = new StateIdle(*this); + state_setting_data_path_ = new StateSettingDataPath(*this); + state_ready_ = new StateReady(*this); + state_destroying_ = new StateDestroying(*this); + state_establishing_ = new StateEstablishing(*this); + state_established_ = new StateEstablished(*this); + + AddState(state_idle_); + AddState(state_setting_data_path_); + AddState(state_ready_); + AddState(state_destroying_); + AddState(state_establishing_); + AddState(state_established_); + + SetInitialState(state_idle_); + } + + CIS &GetCis() { return cis; } + + const char* GetEventName(uint32_t event) { + switch (event) { + CASE_RETURN_STR(CIG_CONFIGURE_REQ) + CASE_RETURN_STR(CIG_CONFIGURED_EVT) + CASE_RETURN_STR(CIS_CREATE_REQ) + CASE_RETURN_STR(CIS_STATUS_EVT) + CASE_RETURN_STR(CIS_ESTABLISHED_EVT) + CASE_RETURN_STR(CIS_DISCONNECT_REQ) + CASE_RETURN_STR(CIS_DISCONNECTED_EVT) + CASE_RETURN_STR(CIG_REMOVE_REQ) + CASE_RETURN_STR(CIG_REMOVED_EVT) + CASE_RETURN_STR(SETUP_DATA_PATH_REQ) + CASE_RETURN_STR(SETUP_DATA_PATH_DONE_EVT) + CASE_RETURN_STR(REMOVE_DATA_PATH_REQ) + CASE_RETURN_STR(REMOVE_DATA_PATH_DONE_EVT) + CASE_RETURN_STR(CIS_CREATE_REQ_DUMMY) + default: + return "Unknown Event"; + } + } + + private: + CIS &cis; + StateIdle *state_idle_; + StateSettingDataPath *state_setting_data_path_; + StateReady *state_ready_; + StateDestroying *state_destroying_; + StateEstablishing *state_establishing_; + StateEstablished *state_established_; +}; + +struct CIS { + uint8_t cig_id; + uint8_t cis_id; + uint16_t cis_handle; + bool to_air_setup_done; + bool from_air_setup_done; + uint8_t datapath_status; + uint8_t disc_direction; + uint8_t direction; // input or output or both + CisInterfaceCallbacks *cis_callback; + RawAddress peer_bda; + CISConfig cis_config; + CisStateMachine cis_sm; + CisState cis_state; + std::list datapath_queue; + + CIS(uint8_t cig_id, uint8_t cis_id, uint8_t direction, + CisInterfaceCallbacks* callback): + cig_id(cig_id), cis_id(cis_id), direction(direction), + cis_callback(callback), + cis_sm(*this) { + to_air_setup_done = false; + from_air_setup_done = false; + } +}; + +struct CreateCisNode { + uint8_t cig_id; + std::vector cis_ids; + std::vector cis_handles; + RawAddress peer_bda; +}; + +struct CIG { + CIGConfig cig_config; + CigState cig_state; + std::map clients_list; // address and count + std::map cis_list; // cis id to CIS +}; + +class CisInterfaceImpl; +CisInterfaceImpl *instance; + +static void hci_cig_param_callback(tBTM_BLE_SET_CIG_RET_PARAM *param); +static void hci_cig_param_test_callback(tBTM_BLE_SET_CIG_PARAM_TEST_RET *param); +static void hci_cig_remove_param_callback(uint8_t status, uint8_t cig_id); +static void hci_cis_create_status_callback( uint8_t status); +static void hci_cis_create_callback(tBTM_BLE_CIS_ESTABLISHED_EVT_PARAM *param); +static void hci_cis_setup_datapath_callback( uint8_t status, + uint16_t conn_handle); +static void hci_cis_disconnect_callback(uint8_t status, uint16_t cis_handle, + uint8_t reason); + +void CisStateMachine::StateIdle::OnEnter() { + LOG(INFO) << __func__ << ": CIS State : " << GetState(); +} + +void CisStateMachine::StateIdle::OnExit() { + +} + +bool CisStateMachine::StateIdle::ProcessEvent(uint32_t event, void* p_data) { + LOG(INFO) <<__func__ <<": CIS State = " << GetState() + <<": Event = " << cis_.cis_sm.GetEventName(event); + LOG(INFO) <<__func__ <<": CIS Id = " << loghex(cis_.cis_id); + LOG(INFO) <<__func__ <<": CIS Handle = " << loghex(cis_.cis_handle); + + bool cis_status = true; + switch (event) { + case SETUP_DATA_PATH_REQ: { + tIsoSetUpDataPath *data_path_info = (tIsoSetUpDataPath *) p_data; + tBTM_BLE_SET_ISO_DATA_PATH_PARAM p_params; + p_params.conn_handle = cis_.cis_handle; + p_params.data_path_dir = data_path_info->data_path_direction >> 1; + p_params.data_path_id = data_path_info->data_path_id; + p_params.codec_id[0] = 0x06; + memset(&p_params.codec_id[1], 0x00, sizeof(p_params.codec_id) - 1); + memset(&p_params.cont_delay, 0x00, sizeof(p_params.cont_delay)); + p_params.codec_config_length = 0x00; + p_params.codec_config = nullptr; + p_params.p_cb = &hci_cis_setup_datapath_callback; + if(BTM_BleSetIsoDataPath(&p_params) == HCI_SUCCESS) { + cis_.cis_sm.TransitionTo(CisStateMachine::kStateSettingDataPath); + DataPathNode node = { + .type = SETUP_DATA_PATH_REQ, + .setup_datapath = { + .conn_handle = cis_.cis_handle, + .data_path_direction = + data_path_info->data_path_direction, + .data_path_id = data_path_info->data_path_id + }, + }; + cis_.datapath_queue.push_back(node); + } + } break; + default: + cis_status = false; + break; + } + return cis_status; +} + +void CisStateMachine::StateSettingDataPath::OnEnter() { + LOG(INFO) << __func__ << ": CIS State : " << GetState(); +} + +void CisStateMachine::StateSettingDataPath::OnExit() { + +} + +bool CisStateMachine::StateSettingDataPath::ProcessEvent(uint32_t event, + void* p_data) { + LOG(INFO) <<__func__ <<": CIS State = " << GetState() + <<": Event = " << cis_.cis_sm.GetEventName(event); + LOG(INFO) <<__func__ <<": CIS Id = " << loghex(cis_.cis_id); + LOG(INFO) <<__func__ <<": CIS Handle = " << loghex(cis_.cis_handle); + + bool cis_status = true; + switch (event) { + case SETUP_DATA_PATH_REQ: { + // add them to the queue + tIsoSetUpDataPath *data_path_info = (tIsoSetUpDataPath *) p_data; + + DataPathNode node = { + .type = SETUP_DATA_PATH_REQ, + .setup_datapath = { + .conn_handle = cis_.cis_handle, + .data_path_direction = + data_path_info->data_path_direction, + .data_path_id = data_path_info->data_path_id + } + }; + + cis_.datapath_queue.push_back(node); + } break; + case SETUP_DATA_PATH_DONE_EVT: { + tBTM_BLE_CIS_DATA_PATH_EVT_PARAM *param = + (tBTM_BLE_CIS_DATA_PATH_EVT_PARAM *) p_data; + cis_.datapath_status = param->status; + + if(!cis_.datapath_queue.empty()) { + if(cis_.datapath_status == ISO_HCI_SUCCESS) { + DataPathNode node = cis_.datapath_queue.front(); + if(node.type == SETUP_DATA_PATH_REQ) { + uint8_t direction = node.setup_datapath.data_path_direction; + if(direction == DIR_TO_AIR) { + cis_.to_air_setup_done = true; + } else if( direction == DIR_FROM_AIR) { + cis_.from_air_setup_done = true; + } + } + } + // remove the entry as it is processed + cis_.datapath_queue.pop_front(); + } + + // check if there are any more entries in queue now + // expect the queue entry to be of setup datapath only + if(!cis_.datapath_queue.empty()) { + DataPathNode node = cis_.datapath_queue.front(); + if(node.type == SETUP_DATA_PATH_REQ) { + tBTM_BLE_SET_ISO_DATA_PATH_PARAM p_params; + p_params.conn_handle = node.setup_datapath.conn_handle; + p_params.data_path_dir = node.setup_datapath.data_path_direction >> 1; + p_params.data_path_id = node.setup_datapath.data_path_id; + p_params.codec_id[0] = 0x06; + memset(&p_params.codec_id[1], 0x00, sizeof(p_params.codec_id) - 1); + memset(&p_params.cont_delay, 0x00, sizeof(p_params.cont_delay)); + p_params.codec_config_length = 0x00; + p_params.codec_config = nullptr; + p_params.p_cb = &hci_cis_setup_datapath_callback; + if(BTM_BleSetIsoDataPath(&p_params) != HCI_SUCCESS) { + LOG(ERROR) << "Setup Datapath Failed"; + cis_.datapath_queue.pop_front(); + cis_.cis_sm.TransitionTo(CisStateMachine::kStateReady); + } + } else { + LOG(ERROR) << "Unexpected entry"; + } + } else { + cis_.cis_sm.TransitionTo(CisStateMachine::kStateReady); + } + } break; + default: + cis_status = false; + break; + } + return cis_status; +} + + +void CisStateMachine::StateReady::OnEnter() { + LOG(INFO) << __func__ << ": CIS State : " << GetState(); + // update the ready state incase of transitioned from states except + // setting up datapath as CIG state event is sufficient for transition + // from setting up data path to ready. + if(cis_.cis_sm.PreviousStateId() != CisStateMachine::kStateSettingDataPath) { + cis_.cis_callback->OnCisState(cis_.cig_id, cis_.cis_id, + cis_.direction, + CisState::READY); + } +} + +void CisStateMachine::StateReady::OnExit() { + +} + +bool CisStateMachine::StateReady::ProcessEvent(uint32_t event, void* p_data) { + LOG(INFO) <<__func__ <<": CIS State = " << GetState() + <<": Event = " << cis_.cis_sm.GetEventName(event); + LOG(INFO) <<__func__ <<": CIS Id = " << loghex(cis_.cis_id); + LOG(INFO) <<__func__ <<": CIS Handle = " << loghex(cis_.cis_handle); + + bool cis_status = true; + switch (event) { + case CIS_CREATE_REQ: { + tBTM_BLE_ISO_CREATE_CIS_CMD_PARAM cmd_data; + CreateCisNode *pNode = (CreateCisNode *) p_data; + cmd_data.cis_count = pNode->cis_ids.size(); + cmd_data.p_cb = &hci_cis_create_status_callback; + cmd_data.p_evt_cb = &hci_cis_create_callback; + tACL_CONN* acl = btm_bda_to_acl(pNode->peer_bda, BT_TRANSPORT_LE); + if(!acl) { + BTIF_TRACE_DEBUG("%s create_cis return ", __func__); + return false; + } + for (auto i: pNode->cis_handles) { + + tBTM_BLE_CHANNEL_MAP map = { .cis_conn_handle = i, + .acl_conn_handle = acl->hci_handle }; + cmd_data.link_conn_handles.push_back(map); + } + if(BTM_BleCreateCis(&cmd_data, &hci_cis_disconnect_callback) + == HCI_SUCCESS) + cis_.cis_sm.TransitionTo(CisStateMachine::kStateEstablishing); + } break; + case CIS_CREATE_REQ_DUMMY: { + cis_.cis_sm.TransitionTo(CisStateMachine::kStateEstablishing); + } break; + default: + cis_status = false; + break; + } + return cis_status; +} + + +void CisStateMachine::StateDestroying::OnEnter() { + LOG(INFO) << __func__ << ": CIS State : " << GetState(); + cis_.cis_callback->OnCisState(cis_.cig_id, cis_.cis_id, + cis_.direction, + CisState::DESTROYING); +} + +void CisStateMachine::StateDestroying::OnExit() { + +} + +bool CisStateMachine::StateDestroying::ProcessEvent(uint32_t event, + void* p_data) { + LOG(INFO) <<__func__ <<": CIS State = " << GetState() + <<": Event = " << cis_.cis_sm.GetEventName(event); + LOG(INFO) <<__func__ <<": CIS Id = " << loghex(cis_.cis_id); + LOG(INFO) <<__func__ <<": CIS Handle = " << loghex(cis_.cis_handle); + + bool cis_status = true; + switch (event) { + case CIS_DISCONNECTED_EVT: { + tBTM_BLE_CIS_DISCONNECTED_EVT_PARAM *param = + (tBTM_BLE_CIS_DISCONNECTED_EVT_PARAM *) p_data; + if(param->status != ISO_HCI_SUCCESS) { + LOG(ERROR) <<__func__ << " cis disconnection failed"; + cis_.cis_sm.TransitionTo(cis_.cis_sm.PreviousStateId()); + } else { + cis_.cis_sm.TransitionTo(CisStateMachine::kStateReady); + } + } break; + default: + cis_status = false; + break; + } + return cis_status; +} + + +void CisStateMachine::StateEstablishing::OnEnter() { + LOG(INFO) << __func__ << ": CIS State : " << GetState(); + cis_.cis_callback->OnCisState(cis_.cig_id, cis_.cis_id, + cis_.direction, + CisState::ESTABLISHING); +} + +void CisStateMachine::StateEstablishing::OnExit() { + +} + +bool CisStateMachine::StateEstablishing::ProcessEvent(uint32_t event, + void* p_data) { + LOG(INFO) <<__func__ <<": CIS State = " << GetState() + <<": Event = " << cis_.cis_sm.GetEventName(event); + LOG(INFO) <<__func__ <<": CIS Id = " << loghex(cis_.cis_id); + LOG(INFO) <<__func__ <<": CIS Handle = " << loghex(cis_.cis_handle); + + bool cis_status = true; + switch (event) { + case CIS_STATUS_EVT: { + uint8_t status = *((uint8_t *)(p_data)); + if(status != ISO_HCI_SUCCESS) { + cis_.cis_sm.TransitionTo(CisStateMachine::kStateReady); + } + } break; + case CIS_ESTABLISHED_EVT: { + tBTM_BLE_CIS_ESTABLISHED_EVT_PARAM *param = + (tBTM_BLE_CIS_ESTABLISHED_EVT_PARAM *) p_data; + if(param->status != ISO_HCI_SUCCESS) { + cis_.cis_sm.TransitionTo(CisStateMachine::kStateReady); + } else { + cis_.cis_sm.TransitionTo(CisStateMachine::kStateEstablished); + + } + } break; + default: + cis_status = false; + break; + } + return cis_status; +} + +void CisStateMachine::StateEstablished::OnEnter() { + LOG(INFO) << __func__ << ": CIS State : " << GetState(); + cis_.disc_direction = cis_.direction; + cis_.cis_callback->OnCisState(cis_.cig_id, cis_.cis_id, + cis_.direction, + CisState::ESTABLISHED); +} + +void CisStateMachine::StateEstablished::OnExit() { + +} + +bool CisStateMachine::StateEstablished::ProcessEvent(uint32_t event, + void* p_data) { + LOG(INFO) <<__func__ <<": CIS State = " << GetState() + <<": Event = " << cis_.cis_sm.GetEventName(event); + LOG(INFO) <<__func__ <<": CIS Id = " << loghex(cis_.cis_id); + LOG(INFO) <<__func__ <<": CIS Handle = " << loghex(cis_.cis_handle); + + switch (event) { + case CIS_DISCONNECT_REQ: + if(BTM_BleIsoCisDisconnect(cis_.cis_handle, 0x13 , + &hci_cis_disconnect_callback) == + HCI_SUCCESS) { + cis_.cis_sm.TransitionTo(CisStateMachine::kStateDestroying); + } + break; + case CIS_DISCONNECTED_EVT: { + tBTM_BLE_CIS_DISCONNECTED_EVT_PARAM *param = + (tBTM_BLE_CIS_DISCONNECTED_EVT_PARAM *) p_data; + if(param->status != ISO_HCI_SUCCESS) { + LOG(ERROR) <<__func__ << " cis disconnection failed"; + cis_.cis_sm.TransitionTo(cis_.cis_sm.PreviousStateId()); + } else { + cis_.cis_sm.TransitionTo(CisStateMachine::kStateReady); + } + } break; + default: + break; + } + return true; +} + +class CisInterfaceImpl : public CisInterface { + public: + CisInterfaceImpl(CisInterfaceCallbacks* callback): + callbacks(callback) { } + + ~CisInterfaceImpl() override = default; + + void CleanUp () { + + } + + CigState GetCigState(const uint8_t &cig_id) override { + CIG *cig = GetCig(cig_id); + if (cig != nullptr) { + return cig->cig_state; + } else { + return CigState::IDLE; + } + } + + CisState GetCisState(const uint8_t &cig_id, uint8_t cis_id) override { + return CisState::READY; + } + + uint8_t GetCisCount(const uint8_t &cig_id) override { + return 0; + } + + IsoHciStatus CreateCig(RawAddress client_peer_bda, bool reconfig, + CIGConfig &cig_config, + std::vector &cis_configs) override { + // check if CIG already exists + LOG(INFO) << __func__ << " : CIG Id = " << loghex(cig_config.cig_id); + CIG *cig = GetCig(cig_config.cig_id); + if (cig != nullptr) { + auto it = cig->clients_list.find(client_peer_bda); + if (it == cig->clients_list.end()) { + cig->clients_list.insert(std::make_pair(client_peer_bda, 0x01)); + } else { + if(!reconfig) { + // increment the count + it->second++; + } + } + // check if params are same for group requested + // and for the group alredy exists + if(cig->cig_state == CigState::CREATING) { + return ISO_HCI_IN_PROGRESS; + } else if(IsCigParamsSame(cig_config, cis_configs)) { + if(cig->cig_state == CigState::CREATED) { + return ISO_HCI_SUCCESS; + } + } + } + + // check if the CIS vector length is same as cis count passed + // in CIG confifuration + if(cig_config.cis_count != cis_configs.size()) { + return ISO_HCI_FAILED; + } + + char value[PROPERTY_VALUE_MAX] = {0}; + bool create_cig = false; + property_get("persist.vendor.btstack.get_cig_test_param", value, ""); + uint16_t ft_m_s, ft_s_m, iso_int, clk_accuracy, nse, pdu_m_s, pdu_s_m, bn_m_s, bn_s_m; + int res = sscanf(value, "%hu,%hu,%hu,%hu,%hu,%hu,%hu,%hu,%hu", &ft_m_s, &ft_s_m, &iso_int, + &clk_accuracy, &nse, &pdu_m_s, &pdu_s_m, &bn_m_s, &bn_s_m); + LOG(WARNING) << __func__<< ": FT_M_S: " << loghex(ft_m_s) << ", FT_S_M: " << loghex(ft_s_m) + << ", ISO_Interval: " << loghex(iso_int) << ", slave_clock: " << loghex(clk_accuracy) + << ", NSE: " << loghex(nse) << ", PDU_M_S:" << loghex(pdu_m_s) + << " PDU_S_M:" << loghex(pdu_s_m) << ", BN_M_S: " << loghex(bn_m_s) + << ", BN_S_M: " << loghex(bn_s_m); + if (res == 9) { + tBTM_BLE_SET_CIG_PARAM_TEST p_data_test; + p_data_test.cig_id = cig_config.cig_id; + memcpy(&p_data_test.sdu_int_s_to_m, &cig_config.sdu_interval_s_to_m, + sizeof(p_data_test.sdu_int_s_to_m)); + + memcpy(&p_data_test.sdu_int_m_to_s, &cig_config.sdu_interval_m_to_s, + sizeof(p_data_test.sdu_int_m_to_s)); + + p_data_test.ft_m_to_s = ft_m_s; + p_data_test.ft_s_to_m = ft_s_m; + p_data_test.iso_interval = iso_int; + p_data_test.slave_clock_accuracy = clk_accuracy; + p_data_test.packing = cig_config.packing; + p_data_test.framing = cig_config.framing; + p_data_test.cis_count = cig_config.cis_count; + + for (auto it = cis_configs.begin(); it != cis_configs.end();) { + tBTM_BLE_CIS_TEST_CONFIG cis_config; + cis_config.cis_id = it->cis_id; + cis_config.nse = nse; + cis_config.max_sdu_m_to_s = it->max_sdu_m_to_s; + cis_config.max_sdu_s_to_m = it->max_sdu_s_to_m; + cis_config.max_pdu_m_to_s = it->max_sdu_m_to_s; + cis_config.max_pdu_s_to_m = it->max_sdu_s_to_m; + cis_config.phy_m_to_s = it->phy_m_to_s; + cis_config.phy_s_to_m = it->phy_s_to_m; + cis_config.bn_m_to_s = bn_m_s; + cis_config.bn_s_to_m = 0; + if (cis_config.max_sdu_s_to_m > 0) { + cis_config.bn_s_to_m = bn_s_m; + if (cis_config.max_sdu_m_to_s > 0 && cis_config.nse > 13) { + cis_config.nse = 13; + } + } + p_data_test.cis_config.push_back(cis_config); + it++; + } + p_data_test.p_cb = &hci_cig_param_test_callback; + create_cig = (BTM_BleSetCigParametersTest(&p_data_test) == HCI_SUCCESS); + } else { + tBTM_BLE_ISO_SET_CIG_CMD_PARAM p_data; + p_data.cig_id = cig_config.cig_id; + memcpy(&p_data.sdu_int_s_to_m, &cig_config.sdu_interval_s_to_m, + sizeof(p_data.sdu_int_s_to_m)); + + memcpy(&p_data.sdu_int_m_to_s, &cig_config.sdu_interval_m_to_s, + sizeof(p_data.sdu_int_m_to_s)); + + p_data.slave_clock_accuracy = 0x00; + p_data.packing = cig_config.packing; + p_data.framing = cig_config.framing; + p_data.max_transport_latency_m_to_s = cig_config.max_tport_latency_m_to_s; + p_data.max_transport_latency_s_to_m = cig_config.max_tport_latency_s_to_m; + p_data.cis_count = cig_config.cis_count; + + for (auto it = cis_configs.begin(); it != cis_configs.end();) { + tBTM_BLE_CIS_CONFIG cis_config; + memcpy(&cis_config, &(*it), sizeof(tBTM_BLE_CIS_CONFIG)); + p_data.cis_config.push_back(cis_config); + it++; + } + p_data.p_cb = &hci_cig_param_callback; + create_cig = (BTM_BleSetCigParam(&p_data) == HCI_SUCCESS); + } + if(create_cig) { + // create new CIG and add it to the list + if(cig == nullptr) { + CIG *cig = new (CIG); + cig_list.insert(std::make_pair(cig_config.cig_id, cig)); + cig->cig_config = cig_config; + cig->cig_state = CigState::CREATING; + + for(uint8_t i = 0; i < cig_config.cis_count; i++) { + uint8_t direction = 0; + if(cis_configs[i].max_sdu_m_to_s) direction |= DIR_TO_AIR; + if(cis_configs[i].max_sdu_s_to_m) direction |= DIR_FROM_AIR; + + CIS *cis = new CIS(cig_config.cig_id, cis_configs[i].cis_id, + direction, callbacks); + cis->cis_config = cis_configs[i]; + cig->cis_list.insert(std::make_pair(cis_configs[i].cis_id, cis)); + } + + auto it = cig->clients_list.find(client_peer_bda); + if (it == cig->clients_list.end()) { + cig->clients_list.insert(std::make_pair(client_peer_bda, 0x01)); + } else { + // increment the count + it->second++; + LOG(WARNING) << __func__ << "count " << loghex(it->second); + } + } else { + cig->cig_config = cig_config; + cig->cig_state = CigState::CREATING; + + uint8_t i = 0; + for (auto it = cig->cis_list.begin(); it != cig->cis_list.end();) { + CIS *cis = it->second; + cis->cis_config = cis_configs[i]; + cis->cis_sm.TransitionTo(CisStateMachine::kStateIdle); + it++; i++; + } + + auto it = cig->clients_list.find(client_peer_bda); + if (it == cig->clients_list.end()) { + cig->clients_list.insert(std::make_pair(client_peer_bda, 0x01)); + } + } + return ISO_HCI_IN_PROGRESS; + } else { + return ISO_HCI_FAILED; + } + } + + IsoHciStatus RemoveCig(RawAddress client_peer_bda, uint8_t cig_id) override { + LOG(INFO) <<__func__ << ": CIG Id = " << loghex(cig_id); + // check if the CIG exists + CIG *cig = GetCig(cig_id); + if (cig == nullptr) { + return ISO_HCI_FAILED; + } + + if(cig->cig_state == CigState::IDLE || + cig->cig_state == CigState::CREATING) { + return ISO_HCI_FAILED; + } else if(cig->cig_state == CigState::CREATED) { + + auto it = cig->clients_list.find(client_peer_bda); + if (it == cig->clients_list.end()) { + return ISO_HCI_FAILED; + } else { + // decrement the count + it->second--; + LOG(WARNING) << __func__ << ": Count : " << loghex(it->second); + } + + // check if all clients have voted off then go for CIG removal + uint8_t vote_on_count = 0; + for (auto it = cig->clients_list.begin(); + it != cig->clients_list.end();) { + vote_on_count += it->second; + it++; + } + + if(vote_on_count) { + LOG(WARNING) << __func__ << " : Vote On Count : " + << loghex(vote_on_count); + return ISO_HCI_SUCCESS; + } + + // check if any of the CIS are in established/streaming state + // if so return false as it is not allowed + if(IsCisActive(cig_id, 0xFF)) return ISO_HCI_FAILED; + + if(BTM_BleRemoveCig(cig_id, &hci_cig_remove_param_callback) + == HCI_SUCCESS) { + cig->cig_state = CigState::REMOVING; + return ISO_HCI_IN_PROGRESS; + } else return ISO_HCI_FAILED; + } + return ISO_HCI_FAILED; + } + + IsoHciStatus CreateCis(uint8_t cig_id, std::vector cis_ids, + RawAddress peer_bda) override { + LOG(INFO) <<__func__ << ": CIG Id = " << loghex(cig_id); + LOG(INFO) <<__func__ << ": No. of CISes = " << loghex(cis_ids.size()); + + IsoHciStatus ret; + uint32_t cur_state; + // check if the CIG exists + CIG *cig = GetCig(cig_id); + if (cig == nullptr) { + return ISO_HCI_FAILED; + } + + if(cig->cig_state != CigState::CREATED) { + return ISO_HCI_FAILED; + } + + bool cis_created = false; + CreateCisNode param; + param.cig_id = cig_id; + param.cis_ids = cis_ids; + param.peer_bda = peer_bda; + std::vector cis_handles; + + for (auto i: cis_ids) { + CIS *cis = GetCis(cig_id, i); + if (cis == nullptr) { + return ISO_HCI_FAILED; + } + cis_handles.push_back(cis->cis_handle); + } + param.cis_handles = cis_handles; + + for (auto i: cis_ids) { + LOG(INFO) <<__func__ << ": CIS Id = " << loghex(i); + // check if CIS ID mentioned is present as part of CIG + CIS *cis = GetCis(cig_id, i); + if (cis == nullptr) { + ret = ISO_HCI_FAILED; + break; + } + + cur_state = cis->cis_sm.StateId(); + + // check if CIS is already created or in progress + if(cur_state == CisStateMachine::kStateEstablishing) { + ret = ISO_HCI_IN_PROGRESS; + break; + } else if(cur_state == CisStateMachine::kStateEstablished) { + ret = ISO_HCI_SUCCESS; + break; + } else if(cur_state == CisStateMachine::kStateDestroying) { + ret = ISO_HCI_FAILED; + break; + } + if (cis_created == false) { + // queue it if there is pending create CIS + if (cis_queue.size()) { + // hand it over to the CIS module + // check if the new request is already exists + // as the head entry in the list + CreateCisNode& head = cis_queue.front(); + if(head.cig_id == cig_id && head.cis_ids == cis_ids && + head.peer_bda == peer_bda) { + if(cis->cis_sm.ProcessEvent( + IsoHciEvent::CIS_CREATE_REQ, ¶m)) { + ret = ISO_HCI_IN_PROGRESS; + } else { + ret = ISO_HCI_FAILED; + break; + } + } else { + cis_queue.push_back(param); + } + } else { + cis_queue.push_back(param); + if(cis->cis_sm.ProcessEvent(IsoHciEvent::CIS_CREATE_REQ, + ¶m)) { + ret = ISO_HCI_IN_PROGRESS; + } else { + ret = ISO_HCI_FAILED; + break; + } + } + cis_created = true; + } else { + if(cis->cis_sm.ProcessEvent(IsoHciEvent::CIS_CREATE_REQ_DUMMY, + &peer_bda)) { + ret = ISO_HCI_IN_PROGRESS; + } else { + ret = ISO_HCI_FAILED; + break; + } + } + } + return ret; + } + + IsoHciStatus DisconnectCis(uint8_t cig_id, uint8_t cis_id, + uint8_t direction) override { + LOG(INFO) <<__func__ << ": CIG Id = " << loghex(cig_id) + << ": CIS Id = " << loghex(cis_id); + + uint32_t cur_state; + // check if the CIG exists + CIG *cig = GetCig(cig_id); + if (cig == nullptr) { + return ISO_HCI_FAILED; + } + + if(cig->cig_state != CigState::CREATED) { + return ISO_HCI_FAILED; + } + + // check if CIS ID mentioned is present as part of CIG + CIS *cis = GetCis(cig_id, cis_id); + if (cis == nullptr) { + return ISO_HCI_FAILED; + } + + if(cis->disc_direction & direction) { + // remove the direction bit form disc direciton + cis->disc_direction &= ~direction; + } + + if(cis->disc_direction) return ISO_HCI_SUCCESS; + + // if all directions are voted off go for CIS disconneciton + cur_state = cis->cis_sm.StateId(); + + // check if CIS is not created or in progress + if(cur_state == CisStateMachine::kStateReady) { + return ISO_HCI_SUCCESS; + } else if(cur_state == CisStateMachine::kStateEstablishing) { + return ISO_HCI_FAILED; + } else if(cur_state == CisStateMachine::kStateDestroying) { + return ISO_HCI_IN_PROGRESS; + } + + LOG(INFO) <<__func__ << " Request issued to CIS SM"; + // hand it over to the CIS module + if(cis->cis_sm.ProcessEvent( + IsoHciEvent::CIS_DISCONNECT_REQ, nullptr)) { + return ISO_HCI_IN_PROGRESS; + } else return ISO_HCI_FAILED; + } + + IsoHciStatus SetupDataPath(uint8_t cig_id, uint8_t cis_id, + uint8_t data_path_direction, uint8_t data_path_id) override { + LOG(INFO) <<__func__ << ": CIG Id = " << loghex(cig_id) + << ": CIS Id = " << loghex(cis_id); + + uint32_t cur_state; + // check if the CIG exists + CIG *cig = GetCig(cig_id); + if (cig == nullptr) { + return ISO_HCI_FAILED; + } + + if(cig->cig_state != CigState::CREATED) { + return ISO_HCI_FAILED; + } + + // check if CIS ID mentioned is present as part of CIG + CIS *cis = GetCis(cig_id, cis_id); + if (cis == nullptr) { + return ISO_HCI_FAILED; + } + + cur_state = cis->cis_sm.StateId(); + + // check if CIS is not created or in progress + if(cur_state == CisStateMachine::kStateReady || + cur_state == CisStateMachine::kStateEstablishing || + cur_state == CisStateMachine::kStateDestroying) { + return ISO_HCI_FAILED; + } else if(cur_state == CisStateMachine::kStateEstablished) { + // return success as it is already created + return ISO_HCI_SUCCESS; + } + + // hand it over to the CIS module + tIsoSetUpDataPath data_path_info; + data_path_info.data_path_direction = data_path_direction; + data_path_info.data_path_id = data_path_id; + + if(cis->cis_sm.ProcessEvent( + IsoHciEvent::SETUP_DATA_PATH_REQ, &data_path_info)) { + return ISO_HCI_IN_PROGRESS; + } else return ISO_HCI_FAILED; + } + + IsoHciStatus RemoveDataPath(uint8_t cig_id, uint8_t cis_id, + uint8_t data_path_direction) override { + LOG(INFO) <<__func__ << ": CIG Id = " << loghex(cig_id) + << ": CIS Id = " << loghex(cis_id); + + uint32_t cur_state; + // check if the CIG exists + CIG *cig = GetCig(cig_id); + if (cig == nullptr) { + return ISO_HCI_FAILED; + } + + if(cig->cig_state != CigState::CREATED) { + return ISO_HCI_FAILED; + } + + // check if CIS ID mentioned is present as part of CIG + CIS *cis = GetCis(cig_id, cis_id); + if (cis == nullptr) { + return ISO_HCI_FAILED; + } + + cur_state = cis->cis_sm.StateId(); + + // check if CIS is not created or in progress + if(cur_state == CisStateMachine::kStateReady || + cur_state == CisStateMachine::kStateEstablishing || + cur_state == CisStateMachine::kStateDestroying || + cur_state == CisStateMachine::kStateEstablished) { + return ISO_HCI_FAILED; + } + + // hand it over to the CIS module + if(cis->cis_sm.ProcessEvent( + IsoHciEvent::REMOVE_DATA_PATH_REQ, &data_path_direction)) { + return ISO_HCI_SUCCESS; + } else return ISO_HCI_FAILED; + } + + const char* GetEventName(uint32_t event) { + switch (event) { + CASE_RETURN_STR(CIG_CONFIGURED_EVT) + CASE_RETURN_STR(CIS_STATUS_EVT) + CASE_RETURN_STR(CIS_ESTABLISHED_EVT) + CASE_RETURN_STR(CIS_DISCONNECTED_EVT) + CASE_RETURN_STR(CIG_REMOVED_EVT) + CASE_RETURN_STR(SETUP_DATA_PATH_DONE_EVT) + CASE_RETURN_STR(REMOVE_DATA_PATH_DONE_EVT) + default: + return "Unknown Event"; + } + } + + IsoHciStatus ProcessEvent (uint32_t event, void* p_data) { + LOG(INFO) <<__func__ <<": Event = " << GetEventName(event); + switch (event) { + case CIG_CONFIGURED_EVT: { + tBTM_BLE_SET_CIG_RET_PARAM *param = + (tBTM_BLE_SET_CIG_RET_PARAM *) p_data; + LOG(INFO) <<__func__ <<": CIG Id = " << loghex(param->cig_id) + << ": status = " << loghex(param->status); + + auto it = cig_list.find(param->cig_id); + if (it == cig_list.end()) { + return ISO_HCI_FAILED; + } + + if(!param->status) { + uint8_t i = 0; + CIG *cig = it->second; + tIsoSetUpDataPath data_path_info; + + for (auto it = cig->cis_list.begin(); + it != cig->cis_list.end(); it++) { + CIS *cis = it->second; + cis->cis_handle = *(param->conn_handle + i++); + cis->cis_sm.Start(); + if(cis->direction & DIR_TO_AIR) { + data_path_info.data_path_direction = DIR_TO_AIR; + data_path_info.data_path_id = 0x01; + cis->cis_sm.ProcessEvent(IsoHciEvent::SETUP_DATA_PATH_REQ, + &data_path_info); + } + if(cis->direction & DIR_FROM_AIR) { + data_path_info.data_path_direction = DIR_FROM_AIR; + data_path_info.data_path_id = 0x01; + cis->cis_sm.ProcessEvent(IsoHciEvent::SETUP_DATA_PATH_REQ, + &data_path_info); + } + } + } else { + // delete CIG and CIS + CIG *cig = it->second; + cig->cig_state = CigState::IDLE; + + while (!cig->cis_list.empty()) { + auto it = cig->cis_list.begin(); + CIS * cis = it->second; + cig->cis_list.erase(it); + delete cis; + } + callbacks->OnCigState(param->cig_id, CigState::IDLE); + cig_list.erase(it); + delete cig; + } + + } break; + case CIG_REMOVED_EVT: { + tBTM_BLE_SET_CIG_REMOVE_PARAM *param = + (tBTM_BLE_SET_CIG_REMOVE_PARAM *) p_data; + auto it = cig_list.find(param->cig_id); + if (it == cig_list.end()) { + return ISO_HCI_FAILED; + } else { + // delete CIG and CIS + CIG *cig = it->second; + while (!cig->cis_list.empty()) { + auto it = cig->cis_list.begin(); + CIS * cis = it->second; + cig->cis_list.erase(it); + delete cis; + } + cig->cig_state = CigState::IDLE; + cig_list.erase(it); + callbacks->OnCigState(param->cig_id, CigState::IDLE); + delete cig; + } + } break; + case CIS_STATUS_EVT: { + // clear the first entry from cis queue and send the next + // CIS creation request queue it if there is pending create CIS + CreateCisNode &head = cis_queue.front(); + for (auto i: head.cis_ids) { + CIS *cis = GetCis(head.cig_id, i); + if(cis) { + cis->cis_sm.ProcessEvent(IsoHciEvent::CIS_STATUS_EVT, p_data); + } + } + } break; + case CIS_ESTABLISHED_EVT: { + tBTM_BLE_CIS_ESTABLISHED_EVT_PARAM *param = + (tBTM_BLE_CIS_ESTABLISHED_EVT_PARAM *) p_data; + LOG(INFO) << __func__ << ": CIS handle = " + << loghex(param->connection_handle) + << ": Status = " << loghex(param->status); + CIS *cis = GetCis(param->connection_handle); + if (cis == nullptr) { + return ISO_HCI_FAILED; + } else { + cis->cis_sm.ProcessEvent(IsoHciEvent::CIS_ESTABLISHED_EVT, p_data); + } + bool cis_status = false; + if (cis_queue.size()) { + cis_queue.pop_front(); + } + while(cis_queue.size() && !cis_status) { + CreateCisNode &head = cis_queue.front(); + CIS *cis = GetCis(head.cig_id, head.cis_ids[0]); + if(cis == nullptr || + cis->cis_sm.StateId() == CisStateMachine::kStateEstablished) { + // remove the entry + cis_queue.pop_front(); + } else if(cis) { + IsoHciStatus hci_status = CreateCis(head.cig_id, head.cis_ids, + head.peer_bda); + if(hci_status == ISO_HCI_SUCCESS || + hci_status == ISO_HCI_IN_PROGRESS) { + cis_status = true; + } else { + // remove the entry + cis_queue.pop_front(); + } + } + } + } break; + case CIS_DISCONNECTED_EVT: { + tBTM_BLE_CIS_DISCONNECTED_EVT_PARAM *param = + (tBTM_BLE_CIS_DISCONNECTED_EVT_PARAM *) p_data; + CIS *cis = GetCis(param->cis_handle); + if (cis == nullptr) { + return ISO_HCI_FAILED; + } else { + cis->cis_sm.ProcessEvent(IsoHciEvent::CIS_DISCONNECTED_EVT, p_data); + } + } break; + case SETUP_DATA_PATH_DONE_EVT: { + tBTM_BLE_CIS_DATA_PATH_EVT_PARAM *param = + (tBTM_BLE_CIS_DATA_PATH_EVT_PARAM *) p_data; + + CIS *cis = GetCis(param->conn_handle); + CIG *cig = nullptr; + if (cis == nullptr) { + return ISO_HCI_FAILED; + } else { + cis->cis_sm.ProcessEvent(IsoHciEvent::SETUP_DATA_PATH_DONE_EVT, + p_data); + } + uint8_t cig_id = cis->cig_id; + + auto it = cig_list.find(cig_id); + if (it == cig_list.end()) { + break; + } else { + // delete CIG and CIS + cig = it->second; + } + + uint8_t num_cis_is_ready = 0; + for(auto it = cig->cis_list.begin(); it != cig->cis_list.end(); it++) { + CIS *cis = it->second; + if(cis->cis_sm.StateId() == CisStateMachine::kStateReady) { + num_cis_is_ready++; + } + } + + // check if all setup data paths are completed + if(num_cis_is_ready == cig->cis_list.size()) { + cig->cig_state = CigState::CREATED; + callbacks->OnCigState(cig_id, CigState::CREATED); + } + } break; + case REMOVE_DATA_PATH_DONE_EVT: { + tBTM_BLE_CIS_DATA_PATH_EVT_PARAM *param = + (tBTM_BLE_CIS_DATA_PATH_EVT_PARAM *) p_data; + CIS *cis = GetCis(param->conn_handle); + if (cis == nullptr) { + return ISO_HCI_FAILED; + } else { + cis->cis_sm.ProcessEvent(IsoHciEvent::REMOVE_DATA_PATH_DONE_EVT, + p_data); + } + } break; + default: + break; + } + return ISO_HCI_SUCCESS; + } + + private: + std::map cig_list; // cig id to CIG structure + std::list cis_queue; + CisInterfaceCallbacks *callbacks; + // 0xFF will be passed for cis id in case search is for any of the + // CIS part of that group + bool IsCisActive(uint8_t cig_id, uint8_t cis_id) { + bool is_cis_active = false; + auto it = cig_list.find(cig_id); + if (it == cig_list.end()) { + return is_cis_active; + } else { + CIG *cig = it->second; + if(cis_id != 0XFF) { + auto it = cig->cis_list.find(cis_id); + if (it != cig->cis_list.end()) { + CIS *cis = it->second; + if(cis->cis_sm.StateId() == CisStateMachine::kStateEstablished) { + is_cis_active = true; + } + } + } else { + for (auto it : cig->cis_list) { + CIS *cis = it.second; + if(cis->cis_sm.StateId() == CisStateMachine::kStateEstablished) { + is_cis_active = true; + break; + } + } + } + } + return is_cis_active; + } + + bool IsCigParamsSame(CIGConfig &cig_config, + std::vector &cis_configs) { + CIG *cig = GetCig(cig_config.cig_id); + bool is_params_same = true; + uint8_t i = 0; + + if(cig == nullptr || (cis_configs.size() != cig->cig_config.cis_count)) { + LOG(WARNING) << __func__ << ": Count is different "; + return false; + } + + if(cig->cig_config.cig_id != cig_config.cig_id || + cig->cig_config.cis_count != cig_config.cis_count || + cig->cig_config.packing != cig_config.packing || + cig->cig_config.framing != cig_config.framing || + cig->cig_config.max_tport_latency_m_to_s != + cig_config.max_tport_latency_m_to_s || + cig->cig_config.max_tport_latency_s_to_m != + cig_config.max_tport_latency_s_to_m || + cig->cig_config.sdu_interval_m_to_s[0] != + cig_config.sdu_interval_m_to_s[0] || + cig->cig_config.sdu_interval_m_to_s[1] != + cig_config.sdu_interval_m_to_s[1] || + cig->cig_config.sdu_interval_m_to_s[2] != + cig_config.sdu_interval_m_to_s[2] || + cig->cig_config.sdu_interval_s_to_m[0] != + cig_config.sdu_interval_s_to_m[0] || + cig->cig_config.sdu_interval_s_to_m[1] != + cig_config.sdu_interval_s_to_m[1] || + cig->cig_config.sdu_interval_s_to_m[2] != + cig_config.sdu_interval_s_to_m[2]) { + LOG(WARNING) << __func__ << " cig params are different "; + return false; + } + + for (auto it = cig->cis_list.begin(); it != cig->cis_list.end();) { + CIS *cis = it->second; + if(cis->cis_config.cis_id == cis_configs[i].cis_id && + cis->cis_config.max_sdu_m_to_s == cis_configs[i].max_sdu_m_to_s && + cis->cis_config.max_sdu_s_to_m == cis_configs[i].max_sdu_s_to_m && + cis->cis_config.phy_m_to_s == cis_configs[i].phy_m_to_s && + cis->cis_config.phy_s_to_m == cis_configs[i].phy_s_to_m && + cis->cis_config.rtn_m_to_s == cis_configs[i].rtn_m_to_s && + cis->cis_config.rtn_s_to_m == cis_configs[i].rtn_s_to_m) { + it++; i++; + } else { + is_params_same = false; + break; + } + } + LOG(WARNING) << __func__ << ": is_params_same : " + << loghex(is_params_same); + return is_params_same; + } + + bool IsCisExists(uint8_t cig_id, uint8_t cis_id) { + bool is_cis_exists = false; + auto it = cig_list.find(cig_id); + if (it != cig_list.end()) { + CIG *cig = it->second; + auto it = cig->cis_list.find(cis_id); + if (it != cig->cis_list.end()) { + is_cis_exists = true; + } + } + return is_cis_exists; + } + + CIS *GetCis(uint8_t cig_id, uint8_t cis_id) { + auto it = cig_list.find(cig_id); + if (it != cig_list.end()) { + CIG *cig = it->second; + auto it = cig->cis_list.find(cis_id); + if (it != cig->cis_list.end()) { + return it->second; + } + } + return nullptr; + } + + CIG *GetCig(uint8_t cig_id) { + auto it = cig_list.find(cig_id); + if (it != cig_list.end()) { + return it->second; + } + return nullptr; + } + + CIS *GetCis(uint16_t cis_handle) { + bool cis_found = false; + CIS *cis = nullptr; + for (auto it : cig_list) { + CIG *cig = it.second; + if(cig->cig_state == CigState::CREATED || + cig->cig_state == CigState::CREATING) { + for (auto it : cig->cis_list) { + cis = it.second; + if(cis->cis_handle == cis_handle) { + cis_found = true; + break; + } + } + } + if(cis_found) return cis; + } + return nullptr; + } + + // TODO to remove if there is no need + bool IsCisEstablished(uint8_t cig_id, uint8_t cis_id) { + bool is_cis_established = false; + auto it = cig_list.find(cig_id); + if (it == cig_list.end()) { + return false; + } else { + CIG *cig = it->second; + if(cis_id != 0XFF) { + auto it = cig->cis_list.find(cis_id); + if (it != cig->cis_list.end()) { + CIS *cis = it->second; + if(cis->cis_sm.StateId() == CisStateMachine::kStateEstablished) { + is_cis_established = true; + } + } + } else { + for (auto it : cig->cis_list) { + CIS *cis = it.second; + if(cis->cis_sm.StateId() == CisStateMachine::kStateEstablished) { + is_cis_established = true; + break; + } + } + } + } + return is_cis_established; + } + + // TODO to remove if there is no need + bool IsCisStreaming(uint8_t cig_id, uint8_t cis_id) { + bool is_cis_streaming = false; + auto it = cig_list.find(cig_id); + if (it == cig_list.end()) { + return false; + } else { + CIG *cig = it->second; + if(cis_id != 0XFF) { + auto it = cig->cis_list.find(cis_id); + if (it != cig->cis_list.end()) { + CIS *cis = it->second; + if(cis->cis_sm.StateId() == CisStateMachine::kStateEstablished) { + is_cis_streaming = true; + } + } + } else { + for (auto it : cig->cis_list) { + CIS *cis = it.second; + if(cis->cis_sm.StateId() == CisStateMachine::kStateEstablished) { + is_cis_streaming = true; + break; + } + } + } + } + return is_cis_streaming; + } +}; + +void CisInterface::Initialize( + CisInterfaceCallbacks* callbacks) { + if (instance) { + LOG(ERROR) << "Already initialized!"; + } else { + instance = new CisInterfaceImpl(callbacks); + } +} + +void CisInterface::CleanUp() { + + CisInterfaceImpl* ptr = instance; + instance = nullptr; + ptr->CleanUp(); + delete ptr; +} + +CisInterface* CisInterface::Get() { + CHECK(instance); + return instance; +} + +static void hci_cig_param_callback(tBTM_BLE_SET_CIG_RET_PARAM *param) { + if (instance) { + instance->ProcessEvent(IsoHciEvent::CIG_CONFIGURED_EVT, param); + } +} + +static void hci_cig_param_test_callback(tBTM_BLE_SET_CIG_PARAM_TEST_RET *param) { + if (instance) { + instance->ProcessEvent(IsoHciEvent::CIG_CONFIGURED_EVT, param); + } +} + +static void hci_cig_remove_param_callback(uint8_t status, uint8_t cig_id) { + tBTM_BLE_SET_CIG_REMOVE_PARAM param = { .status = status, + .cig_id = cig_id }; + if (instance) { + instance->ProcessEvent(IsoHciEvent::CIG_REMOVED_EVT, ¶m); + } +} + +static void hci_cis_create_status_callback ( uint8_t status) { + if (instance) { + instance->ProcessEvent(IsoHciEvent::CIS_STATUS_EVT, &status); + } +} + +static void hci_cis_create_callback ( + tBTM_BLE_CIS_ESTABLISHED_EVT_PARAM *param) { + if (instance) { + instance->ProcessEvent(IsoHciEvent::CIS_ESTABLISHED_EVT, param); + } +} + +static void hci_cis_setup_datapath_callback ( uint8_t status, + uint16_t conn_handle) { + tBTM_BLE_CIS_DATA_PATH_EVT_PARAM param = { .status = status, + .conn_handle = conn_handle }; + if (instance) { + instance->ProcessEvent(IsoHciEvent::SETUP_DATA_PATH_DONE_EVT, ¶m); + } +} + +static void hci_cis_disconnect_callback ( uint8_t status, uint16_t cis_handle, + uint8_t reason) { + tBTM_BLE_CIS_DISCONNECTED_EVT_PARAM param = { .status = status, + .cis_handle = cis_handle, + .reason = reason + }; + if (instance) { + instance->ProcessEvent(IsoHciEvent::CIS_DISCONNECTED_EVT, ¶m); + } +} + +#if 0 +static void hci_cis_remove_datapath_callback ( uint8_t status, + uint16_t conn_handle) { + tBTM_BLE_CIS_DATA_PATH_EVT_PARAM param = { .status = status, + .conn_handle = conn_handle }; + if (instance) { + instance->ProcessEvent(IsoHciEvent::REMOVE_DATA_PATH_DONE_EVT, ¶m); + } +} +#endif + +} // namespace ucast +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/bap/gattc_ops_queue.cc b/le_audio/system/bt/bta/bap/gattc_ops_queue.cc new file mode 100644 index 00000000000..7c7b187d0f8 --- /dev/null +++ b/le_audio/system/bt/bta/bap/gattc_ops_queue.cc @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2017 The Android Open Source Project + * + * 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. + */ + +#include "gattc_ops_queue.h" + +#include +#include +#include + +namespace bluetooth { +namespace bap { + +using gatt_operation = GattOpsQueue::gatt_operation; +using bluetooth::Uuid; + +constexpr uint8_t GATT_READ_CHAR = 1; +constexpr uint8_t GATT_READ_DESC = 2; +constexpr uint8_t GATT_WRITE_CHAR = 3; +constexpr uint8_t GATT_WRITE_DESC = 4; +constexpr uint8_t GATT_SERV_SEARCH = 5; + +struct gatt_read_op_data { + BAP_GATT_READ_OP_CB cb; + void* cb_data; +}; + +std::unordered_map> + GattOpsQueue::gatt_op_queue; +std::unordered_set GattOpsQueue::gatt_op_queue_executing; + +std::unordered_map GattOpsQueue::congestion_queue; + +void GattOpsQueue::mark_as_not_executing(uint16_t conn_id) { + gatt_op_queue_executing.erase(conn_id); +} + +void GattOpsQueue::gatt_read_op_finished(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + gatt_read_op_data* tmp = (gatt_read_op_data*)data; + BAP_GATT_READ_OP_CB tmp_cb = tmp->cb; + void* tmp_cb_data = tmp->cb_data; + + APPL_TRACE_DEBUG("%s: conn_id=0x%x handle=%d status=%d len=%d", __func__, + conn_id, handle, status, len); + + osi_free(data); + + auto map_ptr = gatt_op_queue.find(conn_id); + if (map_ptr == gatt_op_queue.end() || map_ptr->second.empty()) { + APPL_TRACE_DEBUG("%s: no more operations queued for conn_id %d", __func__, + conn_id); + return; + } + + std::list& gatt_ops = map_ptr->second; + gatt_operation op = gatt_ops.front(); + gatt_ops.pop_front(); + + mark_as_not_executing(conn_id); + gatt_execute_next_op(conn_id); + + if (tmp_cb) { + tmp_cb(op.client_id, conn_id, status, handle, len, value, tmp_cb_data); + return; + } +} + +struct gatt_write_op_data { + BAP_GATT_WRITE_OP_CB cb; + void* cb_data; +}; + +void GattOpsQueue::gatt_write_op_finished(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data) { + gatt_write_op_data* tmp = (gatt_write_op_data*)data; + BAP_GATT_WRITE_OP_CB tmp_cb = tmp->cb; + void* tmp_cb_data = tmp->cb_data; + + APPL_TRACE_DEBUG("%s: conn_id=0x%x handle=%d status=%d", __func__, conn_id, + handle, status); + + osi_free(data); + + auto map_ptr = gatt_op_queue.find(conn_id); + if (map_ptr == gatt_op_queue.end() || map_ptr->second.empty()) { + APPL_TRACE_DEBUG("%s: no more operations queued for conn_id %d", __func__, + conn_id); + return; + } + + std::list& gatt_ops = map_ptr->second; + gatt_operation op = gatt_ops.front(); + gatt_ops.pop_front(); + + mark_as_not_executing(conn_id); + gatt_execute_next_op(conn_id); + + if (tmp_cb) { + tmp_cb(op.client_id, conn_id, status, handle, tmp_cb_data); + return; + } +} + +void GattOpsQueue::gatt_execute_next_op(uint16_t conn_id) { + APPL_TRACE_DEBUG("%s: conn_id=0x%x", __func__, conn_id); + if (gatt_op_queue.empty()) { + APPL_TRACE_DEBUG("%s: op queue is empty", __func__); + return; + } + + auto ptr = congestion_queue.find(conn_id); + + if (ptr != congestion_queue.end()) { + bool is_congested = ptr->second; + APPL_TRACE_DEBUG("%s: congestion queue exist, conn_id: %d, is_congested: %d", + __func__, conn_id, is_congested); + if(is_congested) { + APPL_TRACE_DEBUG("%s: lower layer is congested", __func__); + return; + } + } + + auto map_ptr = gatt_op_queue.find(conn_id); + + if (map_ptr == gatt_op_queue.end()) { + APPL_TRACE_DEBUG("%s: Queue is null", __func__); + return; + } + + if (map_ptr->second.empty()) { + APPL_TRACE_DEBUG("%s: queue is empty for conn_id: %d", __func__, + conn_id); + return; + } + + if (gatt_op_queue_executing.count(conn_id)) { + APPL_TRACE_DEBUG("%s: can't enqueue next op, already executing", __func__); + return; + } + + gatt_op_queue_executing.insert(conn_id); + + std::list& gatt_ops = map_ptr->second; + + gatt_operation& op = gatt_ops.front(); + + APPL_TRACE_DEBUG("%s: op.type=%d, handle=%d", __func__, op.type, + op.handle); + if (op.type == GATT_READ_CHAR) { + gatt_read_op_data* data = + (gatt_read_op_data*)osi_malloc(sizeof(gatt_read_op_data)); + data->cb = op.read_cb; + data->cb_data = op.read_cb_data; + BTA_GATTC_ReadCharacteristic(conn_id, op.handle, GATT_AUTH_REQ_NONE, + gatt_read_op_finished, data); + + } else if (op.type == GATT_READ_DESC) { + gatt_read_op_data* data = + (gatt_read_op_data*)osi_malloc(sizeof(gatt_read_op_data)); + data->cb = op.read_cb; + data->cb_data = op.read_cb_data; + BTA_GATTC_ReadCharDescr(conn_id, op.handle, GATT_AUTH_REQ_NONE, + gatt_read_op_finished, data); + + } else if (op.type == GATT_WRITE_CHAR) { + gatt_write_op_data* data = + (gatt_write_op_data*)osi_malloc(sizeof(gatt_write_op_data)); + data->cb = op.write_cb; + data->cb_data = op.write_cb_data; + BTA_GATTC_WriteCharValue(conn_id, op.handle, op.write_type, + std::move(op.value), GATT_AUTH_REQ_NONE, + gatt_write_op_finished, data); + + } else if (op.type == GATT_WRITE_DESC) { + gatt_write_op_data* data = + (gatt_write_op_data*)osi_malloc(sizeof(gatt_write_op_data)); + data->cb = op.write_cb; + data->cb_data = op.write_cb_data; + BTA_GATTC_WriteCharDescr(conn_id, op.handle, std::move(op.value), + GATT_AUTH_REQ_NONE, gatt_write_op_finished, data); + } else if (op.type == GATT_SERV_SEARCH) { + BTA_GATTC_ServiceSearchRequest(conn_id, op.p_srvc_uuid); + } +} + +void GattOpsQueue::Clean(uint16_t conn_id) { + APPL_TRACE_DEBUG("%s: conn_id=0x%x", __func__, conn_id); + + gatt_op_queue.erase(conn_id); + gatt_op_queue_executing.erase(conn_id); +} + +void GattOpsQueue::ReadCharacteristic(uint16_t client_id, + uint16_t conn_id, uint16_t handle, + BAP_GATT_READ_OP_CB cb, void* cb_data) { + gatt_op_queue[conn_id].push_back({.type = GATT_READ_CHAR, + .client_id = client_id, + .handle = handle, + .read_cb = cb, + .read_cb_data = cb_data}); + gatt_execute_next_op(conn_id); +} + +void GattOpsQueue::ReadDescriptor(uint16_t client_id, + uint16_t conn_id, uint16_t handle, + BAP_GATT_READ_OP_CB cb, void* cb_data) { + gatt_op_queue[conn_id].push_back({.type = GATT_READ_DESC, + .client_id = client_id, + .handle = handle, + .read_cb = cb, + .read_cb_data = cb_data}); + gatt_execute_next_op(conn_id); +} + +void GattOpsQueue::WriteCharacteristic(uint16_t client_id, + uint16_t conn_id, uint16_t handle, + std::vector value, + tGATT_WRITE_TYPE write_type, + BAP_GATT_WRITE_OP_CB cb, void* cb_data) { + gatt_op_queue[conn_id].push_back({.type = GATT_WRITE_CHAR, + .client_id = client_id, + .handle = handle, + .write_type = write_type, + .write_cb = cb, + .write_cb_data = cb_data, + .value = std::move(value)}); + gatt_execute_next_op(conn_id); +} + +void GattOpsQueue::WriteDescriptor(uint16_t client_id, + uint16_t conn_id, uint16_t handle, + std::vector value, + tGATT_WRITE_TYPE write_type, + BAP_GATT_WRITE_OP_CB cb, void* cb_data) { + gatt_op_queue[conn_id].push_back({.type = GATT_WRITE_DESC, + .client_id = client_id, + .handle = handle, + .write_type = write_type, + .write_cb = cb, + .write_cb_data = cb_data, + .value = std::move(value)}); + gatt_execute_next_op(conn_id); +} + +void GattOpsQueue::ServiceSearch(uint16_t client_id, + uint16_t conn_id, Uuid* srvc_uuid) { + gatt_op_queue[conn_id].push_back({.type = GATT_SERV_SEARCH, + .client_id = client_id, + .p_srvc_uuid = srvc_uuid}); + gatt_execute_next_op(conn_id); +} + +uint16_t GattOpsQueue::ServiceSearchComplete(uint16_t conn_id, + tGATT_STATUS status) { + auto map_ptr = gatt_op_queue.find(conn_id); + if (map_ptr == gatt_op_queue.end() || map_ptr->second.empty()) { + APPL_TRACE_DEBUG("%s: no more operations queued for conn_id %d", __func__, + conn_id); + return 0; + } + + std::list& gatt_ops = map_ptr->second; + + gatt_operation gatt_op = gatt_ops.front(); + gatt_ops.pop_front(); + mark_as_not_executing(conn_id); + gatt_execute_next_op(conn_id); + return gatt_op.client_id; +} + +void GattOpsQueue::CongestionCallback(uint16_t conn_id, bool congested) { + congestion_queue[conn_id] = congested; + if(!congested) { + gatt_execute_next_op(conn_id); + } +} + +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/bap/gattc_ops_queue.h b/le_audio/system/bt/bta/bap/gattc_ops_queue.h new file mode 100644 index 00000000000..4dd952536fd --- /dev/null +++ b/le_audio/system/bt/bta/bap/gattc_ops_queue.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2017 The Android Open Source Project + * + * 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. + */ + +#include + +#include +#include +#include +#include "bta_gatt_api.h" + +typedef void (*BAP_GATT_READ_OP_CB)(uint16_t client_id,uint16_t conn_id, + tGATT_STATUS status, uint16_t handle, + uint16_t len, uint8_t* value, + void* data); + +typedef void (*BAP_GATT_WRITE_OP_CB)(uint16_t client_id, uint16_t conn_id, + tGATT_STATUS status, + uint16_t handle, void* data); + +/* BTA GATTC implementation does not allow for multiple commands queuing. So one + * client making calls to BTA_GATTC_ReadCharacteristic, BTA_GATTC_ReadCharDescr, + * BTA_GATTC_WriteCharValue, BTA_GATTC_WriteCharDescr must wait for the callacks + * before scheduling next operation. + * + * Methods below can be used as replacement to BTA_GATTC_* in BTA app. They do + * queue the commands if another command is currently being executed. + * + * If you decide to use those methods in your app, make sure to not mix it with + * existing BTA_GATTC_* API. + */ + +namespace bluetooth { +namespace bap { + +class GattOpsQueue { + public: + static void Clean(uint16_t conn_id); + static void ReadCharacteristic(uint16_t client_id, + uint16_t conn_id, uint16_t handle, + BAP_GATT_READ_OP_CB cb, void* cb_data); + static void ReadDescriptor(uint16_t client_id, + uint16_t conn_id, uint16_t handle, + BAP_GATT_READ_OP_CB cb, void* cb_data); + static void WriteCharacteristic(uint16_t client_id, + uint16_t conn_id, uint16_t handle, + std::vector value, + tGATT_WRITE_TYPE write_type, + BAP_GATT_WRITE_OP_CB cb, void* cb_data); + static void WriteDescriptor(uint16_t client_id, + uint16_t conn_id, uint16_t handle, + std::vector value, + tGATT_WRITE_TYPE write_type, BAP_GATT_WRITE_OP_CB cb, + void* cb_data); + static void ServiceSearch(uint16_t client_id, + uint16_t conn_id, Uuid* p_srvc_uuid); + + static uint16_t ServiceSearchComplete(uint16_t conn_id, tGATT_STATUS status); + + static void CongestionCallback(uint16_t conn_id, bool congested); + + + /* Holds pending GATT operations */ + struct gatt_operation { + uint8_t type; + uint16_t client_id; + uint16_t handle; + BAP_GATT_READ_OP_CB read_cb; + void* read_cb_data; + BAP_GATT_WRITE_OP_CB write_cb; + void* write_cb_data; + + /* write-specific fields */ + tGATT_WRITE_TYPE write_type; + std::vector value; + + /* discovery specific */ + Uuid* p_srvc_uuid; + }; + + private: + static bool is_congested; + + static void mark_as_not_executing(uint16_t conn_id); + static void gatt_execute_next_op(uint16_t conn_id); + static void gatt_read_op_finished(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data); + static void gatt_write_op_finished(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data); + + // maps connection id to operations waiting for execution + static std::unordered_map> gatt_op_queue; + + // maps connection id to congestion status of each device + static std::unordered_map congestion_queue; + + // contain connection ids that currently execute operations + static std::unordered_set gatt_op_queue_executing; +}; + +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/bap/gatts_ops_queue.cc b/le_audio/system/bt/bta/bap/gatts_ops_queue.cc new file mode 100644 index 00000000000..b84fe6593fa --- /dev/null +++ b/le_audio/system/bt/bta/bap/gatts_ops_queue.cc @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2017 The Android Open Source Project + * + * 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. + */ + +#include "gatts_ops_queue.h" + +#include +#include +#include + +namespace bluetooth { +namespace bap { + +using gatts_operation = GattsOpsQueue::gatts_operation; +using bluetooth::Uuid; + +constexpr uint8_t GATT_NOTIFY = 1; + +std::unordered_map> + GattsOpsQueue::gatts_op_queue; +std::unordered_set GattsOpsQueue::gatts_op_queue_executing; +std::unordered_map GattsOpsQueue::congestion_queue; + +void GattsOpsQueue::mark_as_not_executing(uint16_t conn_id) { + gatts_op_queue_executing.erase(conn_id); +} + +void GattsOpsQueue::gatts_execute_next_op(uint16_t conn_id) { + APPL_TRACE_DEBUG("%s: conn_id=0x%x", __func__, conn_id); + + if (gatts_op_queue.empty()) { + APPL_TRACE_DEBUG("%s: op queue is empty", __func__); + return; + } + + auto ptr = congestion_queue.find(conn_id); + + if (ptr != congestion_queue.end()) { + bool is_congested = ptr->second; + APPL_TRACE_DEBUG("%s: congestion queue exist, conn_id: %d, is_congested: %d", + __func__, conn_id, is_congested); + if(is_congested) { + APPL_TRACE_DEBUG("%s: lower layer is congested", __func__); + return; + } + } + + auto map_ptr = gatts_op_queue.find(conn_id); + + if (map_ptr == gatts_op_queue.end()) { + APPL_TRACE_DEBUG("%s: Queue is null", __func__); + return; + } + + if (map_ptr->second.empty()) { + APPL_TRACE_DEBUG("%s: queue is empty for conn_id: %d", __func__, + conn_id); + return; + } + + if (gatts_op_queue_executing.count(conn_id)) { + APPL_TRACE_DEBUG("%s: can't enqueue next op, already executing", __func__); + return; + } + + std::list& gatts_ops = map_ptr->second; + gatts_operation& op = gatts_ops.front(); + APPL_TRACE_DEBUG("%s: op.type=%d, attr_id=%d", + __func__, op.type, op.attr_id); + + if(op.type == GATT_NOTIFY) { + if(GATTS_CheckStatusForApp(conn_id,op.need_confirm) == GATT_SUCCESS) { + BTA_GATTS_HandleValueIndication(conn_id, op.attr_id, op.value, op.need_confirm); + gatts_op_queue_executing.insert(conn_id); + } + } +} + +void GattsOpsQueue::Clean(uint16_t conn_id) { + APPL_TRACE_DEBUG("%s: conn_id=0x%x", __func__, conn_id); + + gatts_op_queue.erase(conn_id); + gatts_op_queue_executing.erase(conn_id); +} + +void GattsOpsQueue::SendNotification(uint16_t conn_id, + uint16_t handle, + std::vector value, + bool need_confirm) { + gatts_op_queue[conn_id].push_back({.type = GATT_NOTIFY, + .attr_id = handle, + .value = value, + .need_confirm = need_confirm}); + gatts_execute_next_op(conn_id); +} + +void GattsOpsQueue::NotificationCallback(uint16_t conn_id){ + auto map_ptr = gatts_op_queue.find(conn_id); + if (map_ptr == gatts_op_queue.end() || map_ptr->second.empty()) { + APPL_TRACE_DEBUG("%s: no more operations queued for conn_id %d", + __func__, conn_id); + return; + } + + std::list& gatts_ops = map_ptr->second; + gatts_operation op = gatts_ops.front(); + gatts_ops.pop_front(); + mark_as_not_executing(conn_id); + gatts_execute_next_op(conn_id); +} + +void GattsOpsQueue::CongestionCallback(uint16_t conn_id, bool congested) { + APPL_TRACE_DEBUG("%s: conn_id: %d, congested: %d", + __func__, conn_id,congested); + + congestion_queue[conn_id] = congested; + if(!congested) { + gatts_execute_next_op(conn_id); + } +} + +} +} // namespace ends diff --git a/le_audio/system/bt/bta/bap/gatts_ops_queue.h b/le_audio/system/bt/bta/bap/gatts_ops_queue.h new file mode 100644 index 00000000000..6c0d5a855c9 --- /dev/null +++ b/le_audio/system/bt/bta/bap/gatts_ops_queue.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2017 The Android Open Source Project + * + * 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. + */ + +#include + +#include +#include +#include +#include "bta_gatt_api.h" +namespace bluetooth { +namespace bap { + +class GattsOpsQueue { + public: + static void Clean(uint16_t conn_id); + static void SendNotification(uint16_t conn_id, uint16_t handle, std::vector value, bool need_confirm); + static void NotificationCallback(uint16_t conn_id); + static void CongestionCallback(uint16_t conn_id, bool congested); + + /* Holds pending GATT operations */ + struct gatts_operation { + uint8_t type; + uint16_t attr_id; + std::vector value; + bool need_confirm; + }; + + private: + static bool is_congested; + static void mark_as_not_executing(uint16_t conn_id); + static void gatts_execute_next_op(uint16_t conn_id); + + // maps connection id to operations waiting for execution + static std::unordered_map> gatts_op_queue; + + // maps connection id to congestion status of each device + static std::unordered_map congestion_queue; + + // contain connection ids that currently execute operations + static std::unordered_set gatts_op_queue_executing; + +}; // Class GattsOpsQueue ends + +} +} // namespace ends diff --git a/le_audio/system/bt/bta/bap/pacs_client.cc b/le_audio/system/bt/bta/bap/pacs_client.cc new file mode 100644 index 00000000000..2123dcf0e6b --- /dev/null +++ b/le_audio/system/bt/bta/bap/pacs_client.cc @@ -0,0 +1,1862 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * 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. + */ + +#include "bta_gatt_api.h" +#include "bta_pacs_client_api.h" +#include "gattc_ops_queue.h" +#include +#include +#include +#include +#include "stack/btm/btm_int.h" +#include "device/include/controller.h" +#include "osi/include/properties.h" + +#include +#include "btif/include/btif_bap_config.h" +#include "osi/include/log.h" +#include "btif_util.h" +#include +#include "btif_bap_codec_utils.h" + +namespace bluetooth { +namespace bap { +namespace pacs { + +//using bluetooth::bap::pacs::PacsClientCallbacks; +using base::Closure; +using bluetooth::bap::GattOpsQueue; + +Uuid PACS_UUID = Uuid::FromString("1850"); +Uuid PACS_SINK_PAC_UUID = Uuid::FromString("2BC9"); +Uuid PACS_SINK_LOC_UUID = Uuid::FromString("2BCA"); +Uuid PACS_SRC_PAC_UUID = Uuid::FromString("2BCB"); +Uuid PACS_SRC_LOC_UUID = Uuid::FromString("2BCC"); +Uuid PACS_AVA_AUDIO_UUID = Uuid::FromString("2BCD"); +Uuid PACS_SUP_AUDIO_UUID = Uuid::FromString("2BCE"); + +class PacsClientImpl; +PacsClientImpl* instance; + +typedef uint8_t codec_type_t[5]; + +constexpr uint8_t SINK_PAC = 0x01; +constexpr uint8_t SRC_PAC = 0x02; +constexpr uint8_t SINK_LOC = 0x04; +constexpr uint8_t SRC_LOC = 0x08; +constexpr uint8_t AVAIL_CONTEXTS = 0x10; +constexpr uint8_t SUPP_CONTEXTS = 0x20; + +constexpr uint8_t LTV_TYPE_SUP_FREQS = 0x01; +constexpr uint8_t LTV_TYPE_SUP_FRAME_DUR = 0x02; +constexpr uint8_t LTV_TYPE_CHNL_COUNTS = 0x03; +constexpr uint8_t LTV_TYPE_OCTS_PER_FRAME = 0x04; +constexpr uint8_t LTV_TYPE_MAX_SUP_FRAMES_PER_SDU = 0x05; + +constexpr uint8_t LTV_TYPE_PREF_AUD_CONTEXT = 0x01; +constexpr uint8_t LTV_TYPE_VS_META_DATA = 0xFF;//TODO +constexpr uint16_t QTI_ID = 0x000A; + +constexpr uint8_t LTV_TYPE_VS_META_DATA_LC3Q = 0x10; + +//constexpr uint16_t SAMPLE_RATE_NONE = 0x0; +constexpr uint16_t SAMPLE_RATE_8K = 0x1 << 0; +//constexpr uint16_t SAMPLE_RATE_11K = 0x1 << 1; +constexpr uint16_t SAMPLE_RATE_16K = 0x1 << 2; +//constexpr uint16_t SAMPLE_RATE_22K = 0x1 << 3; +constexpr uint16_t SAMPLE_RATE_24K = 0x1 << 4; +constexpr uint16_t SAMPLE_RATE_32K = 0x1 << 5; +constexpr uint16_t SAMPLE_RATE_441K = 0x1 << 6; +constexpr uint16_t SAMPLE_RATE_48K = 0x1 << 7; +constexpr uint16_t SAMPLE_RATE_882K = 0x1 << 8; +constexpr uint16_t SAMPLE_RATE_96K = 0x1 << 9; +constexpr uint16_t SAMPLE_RATE_176K = 0x1 << 10; +constexpr uint16_t SAMPLE_RATE_192K = 0x1 << 11; +//constexpr uint16_t SAMPLE_RATE_384K = 0x1 << 12; + +constexpr uint8_t CODEC_ID_LC3 = 0x06; +constexpr uint8_t DISCOVER_SUCCESS = 0x00; +constexpr uint8_t DISCOVER_FAIL = 0xFF; + +std::map freq_map = { + {SAMPLE_RATE_8K, CodecSampleRate::CODEC_SAMPLE_RATE_8000 }, + {SAMPLE_RATE_16K, CodecSampleRate::CODEC_SAMPLE_RATE_16000 }, + {SAMPLE_RATE_24K, CodecSampleRate::CODEC_SAMPLE_RATE_24000 }, + {SAMPLE_RATE_32K, CodecSampleRate::CODEC_SAMPLE_RATE_32000 }, + {SAMPLE_RATE_441K, CodecSampleRate::CODEC_SAMPLE_RATE_44100 }, + {SAMPLE_RATE_48K, CodecSampleRate::CODEC_SAMPLE_RATE_48000 }, + {SAMPLE_RATE_882K, CodecSampleRate::CODEC_SAMPLE_RATE_88200 }, + {SAMPLE_RATE_96K, CodecSampleRate::CODEC_SAMPLE_RATE_96000 }, + {SAMPLE_RATE_176K, CodecSampleRate::CODEC_SAMPLE_RATE_176400}, + {SAMPLE_RATE_192K, CodecSampleRate::CODEC_SAMPLE_RATE_192000} +}; + +// ltv type to length +std::map ltv_info = { + {LTV_TYPE_SUP_FREQS, 0x03}, + {LTV_TYPE_SUP_FRAME_DUR, 0x02}, + {LTV_TYPE_CHNL_COUNTS, 0x02}, + {LTV_TYPE_OCTS_PER_FRAME, 0x05}, + {LTV_TYPE_MAX_SUP_FRAMES_PER_SDU, 0x02}, + {LTV_TYPE_PREF_AUD_CONTEXT, 0x03} +}; + +enum class ProfleOP { + CONNECT, + DISCONNECT +}; + +struct ProfileOperation { + uint16_t client_id; + ProfleOP type; +}; + +enum class DevState { + IDLE = 0, + CONNECTING, + CONNECTED, + DISCONNECTING +}; + +struct SinkPacsData { + uint16_t sink_pac_handle; + uint16_t sink_pac_ccc_handle; + std::vector sink_pac_records; + bool read_sink_pac_record; +}; + +struct SrcPacsData { + uint16_t src_pac_handle; + uint16_t src_pac_ccc_handle; + std::vector src_pac_records; + bool read_src_pac_record; +}; + +void pacs_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data); +void encryption_callback(const RawAddress*, tGATT_TRANSPORT, void*, + tBTM_STATUS); + +struct PacsDevice { + RawAddress address; + /* This is true only during first connection to profile, until we store the + * device */ + bool first_connection; + bool service_changed_rcvd; + + /* we are making active attempt to connect to this device, 'direct connect'. + * This is true only during initial phase of first connection. */ + bool connecting_actively; + + uint16_t conn_id; + std::vectorsink_info; + std::vectorsrc_info; + uint16_t sink_loc_handle; + uint16_t sink_loc_ccc_handle; + uint16_t src_loc_handle; + uint16_t src_loc_ccc_handle; + uint16_t avail_contexts_handle; + uint16_t avail_contexts_ccc_handle; + uint16_t supp_contexts_handle; + uint16_t supp_contexts_ccc_handle; + uint16_t srv_changed_ccc_handle; + uint8_t chars_read; + uint8_t chars_to_be_read; + std::vector consolidated_sink_pac_records; + std::vector consolidated_src_pac_records; + uint32_t sink_locations; + uint32_t src_locations; + uint32_t available_contexts; + uint32_t supported_contexts; + bool discovery_completed; + bool notifications_enabled; + DevState state; + bool is_congested; + std::vector profile_queue; + std::vector connected_client_list; //list client requested for connection + PacsDevice(const RawAddress& address) : address(address) {} + PacsDevice() { + first_connection = false; + service_changed_rcvd = false; + conn_id = 0; + sink_loc_handle = 0; + sink_loc_ccc_handle = 0; + src_loc_handle = 0; + src_loc_ccc_handle = 0; + avail_contexts_handle = 0; + avail_contexts_ccc_handle = 0; + supp_contexts_handle = 0; + supp_contexts_ccc_handle = 0; + srv_changed_ccc_handle = 0; + chars_read = 0; + sink_locations = 0; + src_locations = 0; + available_contexts = 0; + supported_contexts = 0; + discovery_completed = false; + notifications_enabled = false; + state = static_cast(0); + is_congested = false; + } +}; + +class PacsDevices { + public: + void Add(PacsDevice device) { + if (FindByAddress(device.address) != nullptr) return; + + devices.push_back(device); + } + + void Remove(const RawAddress& address) { + for (auto it = devices.begin(); it != devices.end();) { + if (it->address != address) { + ++it; + continue; + } + + it = devices.erase(it); + return; + } + } + + PacsDevice* FindByAddress(const RawAddress& address) { + auto iter = std::find_if(devices.begin(), devices.end(), + [&address](const PacsDevice& device) { + return device.address == address; + }); + + return (iter == devices.end()) ? nullptr : &(*iter); + } + + PacsDevice* FindByConnId(uint16_t conn_id) { + auto iter = std::find_if(devices.begin(), devices.end(), + [&conn_id](const PacsDevice& device) { + return device.conn_id == conn_id; + }); + + return (iter == devices.end()) ? nullptr : &(*iter); + } + + size_t size() { return (devices.size()); } + + std::vector devices; +}; + +class PacsClientImpl : public PacsClient { + public: + ~PacsClientImpl() override = default; + + PacsClientImpl() : gatt_client_id(BTA_GATTS_INVALID_IF) {}; + + bool Register(PacsClientCallbacks *callback) { + // looks for client is already registered + bool is_client_registered = false; + for (auto it : callbacks) { + PacsClientCallbacks *pac_callback = it.second; + if (callback == pac_callback) { + is_client_registered = true; + break; + } + } + + LOG(WARNING) << __func__ << " is_client_registered: " + << is_client_registered + << ", gatt_client_id: " << gatt_client_id; + if (is_client_registered) return false; + + if (gatt_client_id == BTA_GATTS_INVALID_IF) { + BTA_GATTC_AppRegister( + pacs_gattc_callback, + base::Bind( + [](PacsClientCallbacks *callback, uint8_t client_id, uint8_t status) { + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Can't start PACS profile - no gatt " + "clients left!"; + return; + } + + if (instance) { + LOG(WARNING) << " PACS gatt_client_id: " + << instance->gatt_client_id; + instance->gatt_client_id = client_id; + instance->callbacks.insert(std::make_pair( + ++instance->pacs_client_id, callback)); + callback->OnInitialized(0, instance->pacs_client_id); + } + }, + callback), true); + } else { + instance->callbacks.insert(std::make_pair( + ++instance->pacs_client_id, callback)); + callback->OnInitialized(0, instance->pacs_client_id); + } + return true; + } + + bool Deregister (uint16_t client_id) { + bool status = false; + auto it = callbacks.find(client_id); + if (it != callbacks.end()) { + callbacks.erase(it); + if(callbacks.empty()) { + // deregister with GATT + LOG(WARNING) << __func__ << " Gatt de-register from pacs"; + BTA_GATTC_AppDeregister(gatt_client_id); + gatt_client_id = BTA_GATTS_INVALID_IF; + } + status = true; + } + return status; + } + + uint8_t GetClientCount () { + return callbacks.size(); + } + + void Connect(uint16_t client_id, const RawAddress& address, + bool is_direct) override { + LOG(WARNING) << __func__ << " address: " << address; + PacsDevice *dev = pacsDevices.FindByAddress(address); + ProfileOperation op; + op.client_id = client_id; + op.type = ProfleOP::CONNECT; + + if (dev == nullptr) { + PacsDevice pac_dev(address); + pacsDevices.Add(pac_dev); + dev = pacsDevices.FindByAddress(address); + } + if (dev == nullptr) { + LOG(ERROR) << __func__ << "dev is null"; + return; + } + + LOG(WARNING) << __func__ << ": state: " << static_cast(dev->state); + + switch(dev->state) { + case DevState::IDLE: { + BTA_GATTC_Open(gatt_client_id, address, is_direct, + GATT_TRANSPORT_LE, false); + dev->state = DevState::CONNECTING; + dev->profile_queue.push_back(op); + } break; + case DevState::CONNECTING: { + dev->profile_queue.push_back(op); + } break; + case DevState::CONNECTED: { + // add it to the client id list if not already done + auto iter = std::find_if(dev->connected_client_list.begin(), + dev->connected_client_list.end(), + [&client_id](uint16_t id) { + return id == client_id; + }); + + if (iter == dev->connected_client_list.end()) + dev->connected_client_list.push_back(client_id); + + // respond immediately as connected + + } break; + case DevState::DISCONNECTING: { + dev->profile_queue.push_back(op); + } break; + } + } + + void Disconnect(uint16_t client_id, const RawAddress& address) override { + PacsDevice* dev = pacsDevices.FindByAddress(address); + if (!dev) { + LOG(INFO) << __func__ <<": Device not connected to profile: " << address; + return; + } + + ProfileOperation op; + op.client_id = client_id; + op.type = ProfleOP::DISCONNECT; + + LOG(WARNING) << __func__ << ": address: " << address + << ", state: " << static_cast(dev->state); + + switch(dev->state) { + case DevState::CONNECTING: { + auto iter = std::find_if(dev->profile_queue.begin(), + dev->profile_queue.end(), + [&client_id]( ProfileOperation entry) { + return ((entry.type == ProfleOP::CONNECT) && + (entry.client_id == client_id)); + }); + // If it is the last client requested for disconnect + if (iter != dev->profile_queue.end() && + dev->profile_queue.size() == 1) { + if (dev->conn_id) { + // Removes all registrations for connection. + BTA_GATTC_CancelOpen(dev->conn_id, address, false); + GattOpsQueue::Clean(dev->conn_id); + BTA_GATTC_Close(dev->conn_id); + } else { + // clear the connection queue and + // move the state to DISCONNECTING to better track + dev->profile_queue.clear(); + } + dev->state = DevState::DISCONNECTING; + dev->profile_queue.push_back(op); + } else { + // remove the connection entry from the list + // as the same client has requested for disconnection + dev->profile_queue.erase(iter); + } + } break; + case DevState::CONNECTED: { + auto iter = std::find_if(dev->connected_client_list.begin(), + dev->connected_client_list.end(), + [&client_id]( uint16_t stored_client_id) { + return stored_client_id == client_id; + }); + // if it is the last client requested for disconnection + if (iter != dev->connected_client_list.end() && + dev->connected_client_list.size() == 1) { + if (dev->conn_id) { + // Removes all registrations for connection. + BTA_GATTC_CancelOpen(dev->conn_id, address, false); + GattOpsQueue::Clean(dev->conn_id); + BTA_GATTC_Close(dev->conn_id); + dev->profile_queue.push_back(op); + dev->state = DevState::DISCONNECTING; + } + } else { + // remove the client from connected_client_list + dev->connected_client_list.erase(iter); + // remove the pending gatt ops( not the ongoing one ) + // initiated from client which requested disconnect + // TODO and send callback as disconnected + } + } break; + case DevState::DISCONNECTING: { + dev->profile_queue.push_back(op); + } break; + default: + break; + } + } + + void StartDiscovery(uint16_t client_id, const RawAddress& address) override { + PacsDevice* dev = pacsDevices.FindByAddress(address); + if (!dev) { + LOG(INFO) << __func__ << ": Device not connected to profile: " << address; + return; + } + LOG(WARNING) << __func__ << " address: " << address + << ", state: " << static_cast(dev->state); + switch(dev->state) { + case DevState::CONNECTED: { + auto iter = std::find_if(dev->connected_client_list.begin(), + dev->connected_client_list.end(), + [&client_id]( uint16_t stored_client_id) { + LOG(WARNING) << __func__ + << ": client_id: " << client_id + << ", stored_client_id:" << stored_client_id; + return stored_client_id == client_id; + }); + // check if the client present in the connected client list + if (iter == dev->connected_client_list.end()) { + break; + } + + LOG(WARNING) << __func__ + << ", discovery_completed: " << dev->discovery_completed + << ", notifications_enabled: " << dev->notifications_enabled; + + // check if the discovery is already finished + // send back the same results to the other client + if (dev->discovery_completed && dev->notifications_enabled) { + auto iter = callbacks.find(client_id); + if (iter != callbacks.end()) { + LOG(WARNING) << __func__ << ": OnSearchComplete"; + PacsClientCallbacks *callback = iter->second; + callback->OnSearchComplete(DISCOVER_SUCCESS, + dev->address, + dev->consolidated_sink_pac_records, + dev->consolidated_src_pac_records, + dev->sink_locations, + dev->src_locations, + dev->available_contexts, + dev->supported_contexts); + } + break; + } + + // reset it + dev->chars_read = 0x00; + dev->chars_to_be_read = 0x00; + dev->sink_info.clear(); + dev->src_info.clear(); + dev->consolidated_sink_pac_records.clear(); + dev->consolidated_src_pac_records.clear(); + //TODO + //btif_bap_remove_record() + + // queue the request to GATT queue module + GattOpsQueue::ServiceSearch(client_id, dev->conn_id, &PACS_UUID); + } break; + + default: + break; + } + } + + void GetAudioAvailability(uint16_t client_id, const RawAddress& address) { + PacsDevice* dev = pacsDevices.FindByAddress(address); + if (!dev) { + LOG(INFO) << __func__ << ": Device not connected to profile: " << address; + return; + } + LOG(WARNING) << __func__ << ": address: " << address + << ", state: " << static_cast(dev->state); + + switch(dev->state) { + case DevState::CONNECTED: { + auto iter = std::find_if(dev->connected_client_list.begin(), + dev->connected_client_list.end(), + [&client_id]( uint16_t stored_client_id) { + return stored_client_id == client_id; + }); + // check if the client present in the connected client list + if (iter == dev->connected_client_list.end()) { + break; + } + + // check if the discovery is already finished + // send back the same results to the other client + if (dev->discovery_completed && dev->notifications_enabled) { + auto iter = callbacks.find(client_id); + if (iter != callbacks.end()) { + PacsClientCallbacks *callback = iter->second; + callback->OnAudioContextAvailable(dev->address, + dev->available_contexts); + } + break; + } + + // queue the request to GATT queue module + GattOpsQueue::ReadCharacteristic( + client_id, dev->conn_id, dev->avail_contexts_handle, + PacsClientImpl::OnReadAvailableAudioStatic, nullptr); + } break; + default: + LOG(WARNING) << __func__ << " default"; + break; + } + } + + void OnGattConnected(tGATT_STATUS status, uint16_t conn_id, + tGATT_IF client_if, RawAddress address, + tBTA_TRANSPORT transport, uint16_t mtu) { + + PacsDevice* dev = pacsDevices.FindByAddress(address); + if (!dev) { + /* When device is quickly disabled and enabled in settings, this case + * might happen */ + LOG(WARNING) << __func__ + <<"Closing connection to non pacs device, address: " + << address; + BTA_GATTC_Close(conn_id); + return; + } + + LOG(WARNING) << __func__ << " address: " << address + << ", state: " << static_cast(dev->state) + << ", status: " << loghex(status); + + if (dev->state == DevState::CONNECTING) { + if (status != GATT_SUCCESS) { + LOG(ERROR) << __func__ << ": Failed to connect to PACS device"; + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if(it->type == ProfleOP::CONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + PacsClientCallbacks *callback = iter->second; + callback->OnConnectionState(address, + ConnectionState::DISCONNECTED); + } + dev->profile_queue.erase(it); + } else { + it++; + } + } + dev->state = DevState::IDLE; + pacsDevices.Remove(address); + return; + } + } else if (dev->state == DevState::DISCONNECTING) { + // TODO will this happens ? + // it could have called the cancel open to expect the + // open cancelled event + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Failed to connect to PACS device"; + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if(it->type == ProfleOP::DISCONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + PacsClientCallbacks *callback = iter->second; + callback->OnConnectionState(address, + ConnectionState::DISCONNECTED); + } + dev->profile_queue.erase(it); + } else { + it++; + } + } + dev->state = DevState::IDLE; + pacsDevices.Remove(address); + return; + } else { + // gatt connected successfully + // if the disconnect entry is found we need to initiate the + // gatt disconnect. may be a race condition just after sending + // cancel open gatt connected event received + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if(it->type == ProfleOP::DISCONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + // Removes all registrations for connection. + BTA_GATTC_CancelOpen(dev->conn_id, address, false); + GattOpsQueue::Clean(dev->conn_id); + BTA_GATTC_Close(dev->conn_id); + break; + } + } else { + it++; + } + } + return; + } + } else { + // return unconditinally + return; + } + + // success scenario code + dev->conn_id = conn_id; + + tACL_CONN* p_acl = btm_bda_to_acl(address, BT_TRANSPORT_LE); + if (p_acl != nullptr && + controller_get_interface()->supports_ble_2m_phy() && + HCI_LE_2M_PHY_SUPPORTED(p_acl->peer_le_features)) { + LOG(INFO) << address << " set preferred PHY to 2M"; + BTM_BleSetPhy(address, PHY_LE_2M, PHY_LE_2M, 0); + } + + /* verify bond */ + uint8_t sec_flag = 0; + BTM_GetSecurityFlagsByTransport(address, &sec_flag, BT_TRANSPORT_LE); + + if (sec_flag & BTM_SEC_FLAG_ENCRYPTED) { + /* if link has been encrypted */ + OnEncryptionComplete(address, true); + return; + } + + if (sec_flag & BTM_SEC_FLAG_LKEY_KNOWN) { + /* if bonded and link not encrypted */ + sec_flag = BTM_BLE_SEC_ENCRYPT; + LOG(WARNING) << "trying to encrypt now"; + BTM_SetEncryption(address, BTA_TRANSPORT_LE, encryption_callback, + nullptr, sec_flag); + return; + } + + /* otherwise let it go through */ + OnEncryptionComplete(address, true); + } + + void OnGattDisconnected(tGATT_STATUS status, uint16_t conn_id, + tGATT_IF client_if, RawAddress remote_bda, + tBTA_GATT_REASON reason) { + PacsDevice* dev = pacsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(ERROR) << __func__ + << ": Skipping unknown device disconnect, conn_id=" + << loghex(conn_id); + return; + } + + LOG(WARNING) << __func__ << " remote_bda: " << remote_bda + << ", state: " << static_cast(dev->state); + + switch(dev->state) { + case DevState::CONNECTING: { + // sudden disconnection + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if (it->type == ProfleOP::CONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + PacsClientCallbacks *callback = iter->second; + callback->OnConnectionState(remote_bda, + ConnectionState::DISCONNECTED); + } + it = dev->profile_queue.erase(it); + } else { + it++; + } + } + } break; + case DevState::CONNECTED: { + // sudden disconnection + for (auto it = dev->connected_client_list.begin(); + it != dev->connected_client_list.end();) { + // get the callback and update the upper layers + auto iter = callbacks.find(*it); + if (iter != callbacks.end()) { + PacsClientCallbacks *callback = iter->second; + callback->OnConnectionState(remote_bda, + ConnectionState::DISCONNECTED); + } + it = dev->connected_client_list.erase(it); + } + } break; + case DevState::DISCONNECTING: { + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if (it->type == ProfleOP::DISCONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + PacsClientCallbacks *callback = iter->second; + callback->OnConnectionState(remote_bda, + ConnectionState::DISCONNECTED); + } + it = dev->profile_queue.erase(it); + } else { + it++; + } + } + + for (auto it = dev->connected_client_list.begin(); + it != dev->connected_client_list.end();) { + // get the callback and update the upper layers + it = dev->connected_client_list.erase(it); + } + // check if the connection queue is not empty + // if not initiate the Gatt connection + } break; + default: + break; + } + + if (dev->conn_id) { + GattOpsQueue::Clean(dev->conn_id); + BTA_GATTC_Close(dev->conn_id); + dev->conn_id = 0; + } + + dev->state = DevState::IDLE; + pacsDevices.Remove(remote_bda); + } + + void OnConnectionUpdateComplete(uint16_t conn_id, tBTA_GATTC* p_data) { + PacsDevice* dev = pacsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(ERROR) << __func__ + << ": Skipping unknown device, conn_id=" + << loghex(conn_id); + return; + } + } + + void OnEncryptionComplete(const RawAddress& address, bool success) { + PacsDevice* dev = pacsDevices.FindByAddress(address); + if (!dev) { + LOG(ERROR) << __func__ << ": Skipping unknown device" << address; + return; + } + + if(dev->state != DevState::CONNECTING) { + LOG(ERROR) << __func__ << ": received in wrong state" << address; + return; + } + + LOG(WARNING) << __func__ << ": address=" << address << loghex(success); + // encryption failed + if (!success) { + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if (it->type == ProfleOP::CONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + PacsClientCallbacks *callback = iter->second; + callback->OnConnectionState(address, + ConnectionState::DISCONNECTED); + } + // change the type to disconnect + it->type = ProfleOP::DISCONNECT; + } else { + it++; + } + } + dev->state = DevState::DISCONNECTING; + // Removes all registrations for connection. + BTA_GATTC_CancelOpen(dev->conn_id, address, false); + BTA_GATTC_Close(dev->conn_id); + } else { + for (auto it = dev->profile_queue.begin(); + it != dev->profile_queue.end();) { + if (it->type == ProfleOP::CONNECT) { + // get the callback and update the upper layers + auto iter = callbacks.find(it->client_id); + if (iter != callbacks.end()) { + dev->connected_client_list.push_back(it->client_id); + PacsClientCallbacks *callback = iter->second; + LOG(WARNING) << __func__ << " calling OnConnectionState"; + callback->OnConnectionState(address, ConnectionState::CONNECTED); + } + dev->profile_queue.erase(it); + } else { + it++; + } + } + dev->state = DevState::CONNECTED; + } + } + + void OnServiceChangeEvent(const RawAddress& address) { + PacsDevice* dev = pacsDevices.FindByAddress(address); + if (!dev) { + LOG(ERROR) << __func__ << ": Skipping unknown device: " << address; + return; + } + LOG(INFO) << __func__ << ": address: " << address; + dev->first_connection = true; + dev->service_changed_rcvd = true; + GattOpsQueue::Clean(dev->conn_id); + } + + void OnServiceDiscDoneEvent(const RawAddress& address) { + PacsDevice* dev = pacsDevices.FindByAddress(address); + if (!dev) { + LOG(ERROR) << __func__ << ": Skipping unknown device: " << address; + return; + } + + LOG(WARNING) << __func__ << " service_changed_rcvd: " + << dev->service_changed_rcvd; + if (dev->service_changed_rcvd) { + // queue the request to GATT queue module with dummu client id + GattOpsQueue::ServiceSearch(0XFF, dev->conn_id, &PACS_UUID); + } + } + + void RegisterForNotification(uint16_t client_id, uint16_t conn_id, + PacsDevice* dev, uint16_t ccc_handle, + uint16_t handle) { + if(handle && ccc_handle) { + /* Register and enable Notification */ + tGATT_STATUS register_status; + register_status = BTA_GATTC_RegisterForNotifications( + conn_id, dev->address, handle); + if (register_status != GATT_SUCCESS) { + LOG(ERROR) << __func__ + << ": BTA_GATTC_RegisterForNotifications failed, status=" + << loghex(register_status); + } + std::vector value(2); + uint8_t* ptr = value.data(); + UINT16_TO_STREAM(ptr, GATT_CHAR_CLIENT_CONFIG_NOTIFICATION); + GattOpsQueue::WriteDescriptor( + client_id, conn_id, ccc_handle, + std::move(value), GATT_WRITE, nullptr, nullptr); + } + } + + void OnServiceSearchComplete(uint16_t conn_id, tGATT_STATUS status) { + PacsDevice* dev = pacsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(ERROR) << __func__ << ": Skipping unknown device, conn_id = " + << loghex(conn_id); + return; + } + + LOG(WARNING) << __func__ << ": conn_id = " << loghex(conn_id); + + uint16_t client_id = GattOpsQueue::ServiceSearchComplete(conn_id, + status); + LOG(WARNING) << __func__ << ": client_id = " << loghex(client_id); + auto iter = callbacks.find(client_id); + if (status != GATT_SUCCESS) { + /* close connection and report service discovery complete with error */ + LOG(ERROR) << __func__ << ": Service discovery failed"; + if (iter != callbacks.end()) { + PacsClientCallbacks *callback = iter->second; + callback->OnSearchComplete(DISCOVER_FAIL, + dev->address, + dev->consolidated_sink_pac_records, + dev->consolidated_src_pac_records, + dev->sink_locations, + dev->src_locations, + dev->available_contexts, + dev->supported_contexts); + } + return; + } + + const std::vector* services = BTA_GATTC_GetServices(conn_id); + + const gatt::Service* service = nullptr; + if (services) { + for (const gatt::Service& tmp : *services) { + if (tmp.uuid == Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER)) { + LOG(INFO) << __func__ << ": Found UUID_CLASS_GATT_SERVER, handle=" + << loghex(tmp.handle); + const gatt::Service* service_changed_service = &tmp; + find_server_changed_ccc_handle(conn_id, service_changed_service); + } else if (tmp.uuid == PACS_UUID) { + LOG(INFO) << __func__ << ": Found PACS service, handle=" + << loghex(tmp.handle); + service = &tmp; + } + } + } else { + LOG(ERROR) << __func__ + << ": no services found for conn_id: " << loghex(conn_id); + return; + } + + if (!service) { + LOG(ERROR) << __func__ << ": No PACS service found"; + if (iter != callbacks.end()) { + PacsClientCallbacks *callback = iter->second; + callback->OnSearchComplete(DISCOVER_FAIL, + dev->address, + dev->consolidated_sink_pac_records, + dev->consolidated_src_pac_records, + dev->sink_locations, + dev->src_locations, + dev->available_contexts, + dev->supported_contexts); + } + return; + } + + for (const gatt::Characteristic& charac : service->characteristics) { + LOG(INFO) << __func__ << ": uuid: " << charac.uuid; + if (charac.uuid == PACS_SINK_PAC_UUID) { + LOG(INFO) << __func__ << ": sink pac uuid found. "; + + SinkPacsData info; + memset(&info, 0, sizeof(info)); + info.sink_pac_handle = charac.value_handle; + info.sink_pac_ccc_handle = find_ccc_handle(conn_id, charac.value_handle); + dev->sink_info.push_back(info); + dev->chars_to_be_read |= SINK_PAC; + GattOpsQueue::ReadCharacteristic( + client_id, conn_id, charac.value_handle, + PacsClientImpl::OnReadOnlyPropertiesReadStatic, nullptr); + + if (info.sink_pac_ccc_handle) { + RegisterForNotification(client_id, conn_id, dev, + info.sink_pac_ccc_handle, + info.sink_pac_handle); + } + + } else if (charac.uuid == PACS_SINK_LOC_UUID) { + LOG(INFO) << __func__ << ": sink loc uuid found. "; + dev->sink_loc_handle = charac.value_handle; + + GattOpsQueue::ReadCharacteristic( + client_id,conn_id, charac.value_handle, + PacsClientImpl::OnReadOnlyPropertiesReadStatic, nullptr); + + dev->sink_loc_ccc_handle = + find_ccc_handle(conn_id, charac.value_handle); + + dev->chars_to_be_read |= SINK_LOC; + if (dev->sink_loc_ccc_handle) { + RegisterForNotification(client_id, conn_id, dev, + dev->sink_loc_ccc_handle, + dev->sink_loc_handle); + } + + } else if (charac.uuid == PACS_SRC_PAC_UUID) { + LOG(INFO) << __func__ << ": src pac uuid found. "; + + SrcPacsData info; + memset(&info, 0, sizeof(info)); + info.src_pac_handle = charac.value_handle; + info.src_pac_ccc_handle = find_ccc_handle(conn_id, charac.value_handle); + dev->src_info.push_back(info); + dev->chars_to_be_read |= SRC_PAC; + GattOpsQueue::ReadCharacteristic( + client_id, conn_id, charac.value_handle, + PacsClientImpl::OnReadOnlyPropertiesReadStatic, nullptr); + + if (info.src_pac_ccc_handle) { + RegisterForNotification(client_id, conn_id, dev, + info.src_pac_ccc_handle, + info.src_pac_handle); + } + + } else if (charac.uuid == PACS_SRC_LOC_UUID) { + LOG(INFO) << __func__ << ": src loc uuid found. "; + dev->src_loc_handle = charac.value_handle; + + GattOpsQueue::ReadCharacteristic( + client_id, conn_id, charac.value_handle, + PacsClientImpl::OnReadOnlyPropertiesReadStatic, nullptr); + + dev->src_loc_ccc_handle = + find_ccc_handle(conn_id, charac.value_handle); + dev->chars_to_be_read |= SRC_LOC; + if (dev->src_loc_ccc_handle) { + RegisterForNotification(client_id, conn_id, dev, + dev->src_loc_ccc_handle, + dev->src_loc_handle); + } + + } else if (charac.uuid == PACS_AVA_AUDIO_UUID) { + LOG(INFO) << __func__ << ": avaliable audio uuid found. "; + dev->avail_contexts_handle = charac.value_handle; + + GattOpsQueue::ReadCharacteristic( + client_id, conn_id, charac.value_handle, + PacsClientImpl::OnReadOnlyPropertiesReadStatic, nullptr); + + dev->avail_contexts_ccc_handle = + find_ccc_handle(conn_id, charac.value_handle); + dev->chars_to_be_read |= AVAIL_CONTEXTS; + if (dev->avail_contexts_ccc_handle) { + RegisterForNotification(client_id, conn_id, dev, + dev->avail_contexts_ccc_handle, + dev->avail_contexts_handle); + } + + } else if (charac.uuid == PACS_SUP_AUDIO_UUID) { + LOG(INFO) << __func__ << ": supported audio uuid found. "; + dev->supp_contexts_handle = charac.value_handle; + + GattOpsQueue::ReadCharacteristic( + client_id, conn_id, charac.value_handle, + PacsClientImpl::OnReadOnlyPropertiesReadStatic, nullptr); + + dev->supp_contexts_ccc_handle = + find_ccc_handle(conn_id, charac.value_handle); + dev->chars_to_be_read |= SUPP_CONTEXTS; + if (dev->supp_contexts_ccc_handle) { + RegisterForNotification(client_id, conn_id, dev, + dev->supp_contexts_ccc_handle, + dev->supp_contexts_handle); + } + } else { + LOG(WARNING) << "Unknown characteristic found:" << charac.uuid; + } + } + + dev->notifications_enabled = true; + + LOG(INFO) << __func__ + << ": service_changed_rcvd: " << dev->service_changed_rcvd; + if (dev->service_changed_rcvd) { + dev->service_changed_rcvd = false; + } + } + + void OnNotificationEvent(uint16_t conn_id, uint16_t handle, uint16_t len, + uint8_t* value) { + + PacsDevice* dev = pacsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(INFO) << __func__ + << ": Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + + LOG(WARNING) << __func__ << ": conn_id: " << loghex(conn_id); + + if(dev->avail_contexts_handle == handle) { + uint8_t* p = value; + STREAM_TO_UINT32(dev->available_contexts, p); + } + } + + void OnCongestionEvent(uint16_t conn_id, bool congested) { + PacsDevice* dev = pacsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(INFO) << __func__ + << ": Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + + LOG(WARNING) << __func__ << ": conn_id=" << loghex(conn_id) + << ", congested: " << congested; + dev->is_congested = congested; + GattOpsQueue::CongestionCallback(conn_id, congested); + } + + void OnReadAvailableAudio(uint16_t client_id, + uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, uint8_t* value, + void* data) { + PacsDevice* dev = pacsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(ERROR) << __func__ << ": unknown conn_id: " << loghex(conn_id); + return; + } + + LOG(WARNING) << __func__ << ": conn_id: " << loghex(conn_id); + + if(dev->avail_contexts_handle == handle) { + uint8_t* p = value; + STREAM_TO_UINT32(dev->available_contexts, p); + // check if all pacs characteristics are read + // send out the callback as service discovery completed + // get the callback and update the upper layers + auto iter = callbacks.find(client_id); + if (iter != callbacks.end()) { + PacsClientCallbacks *callback = iter->second; + callback->OnAudioContextAvailable(dev->address, + dev->available_contexts); + } + } + } + + bool IsRecordReadable(uint16_t total_len, uint16_t processed_len, + uint16_t req_len) { + LOG(WARNING) << __func__ << ": processed_len: " << loghex(processed_len) + << ", req_len: " << loghex(req_len); + if((total_len > processed_len) && + ((total_len - processed_len) >= req_len)) { + return true; + } else { + return false; + } + } + + bool IsLtvValid(uint8_t ltv_type, uint16_t ltv_len) { + bool valid = true; + for (auto it : ltv_info) { + if(ltv_type == it.first && + ltv_len != it.second) { + valid = false; + break; + } + } + return valid; + } + + void ParsePacRecord (PacsDevice *dev, uint16_t handle, uint16_t total_len, + uint8_t *value, void* data) { + std::vector pac_records; + CodecIndex codec_type; + uint8_t *p = value; + codec_type_t codec_id; + bool stop_reading = false; + uint8_t codec_cap_len; + std::vector codec_caps; + uint8_t meta_data_len; + std::vector meta_data; + uint16_t processed_len = 0; + uint8_t num_pac_recs; + uint16_t context_type; + + SinkPacsData* sinkinfo = FindSinkByHandle(dev, handle); + SrcPacsData* srcinfo = FindSrcByHandle(dev, handle); + + // Number_of_PAC_records is 1 byte + if (!total_len) { + LOG(ERROR) << __func__ + << ": zero len record, total_len: "; + return; + } + + STREAM_TO_UINT8(num_pac_recs, p); + processed_len ++; + + LOG(WARNING) << __func__ << ": num_pac_recs: " << loghex(num_pac_recs) + << ", total_len: " << loghex(total_len); + while (!stop_reading && num_pac_recs) { + // reset context type for before reading record + context_type = ucast::CONTENT_TYPE_UNSPECIFIED; + // read the complete record + // codec_id is of 5 bytes. + if (!IsRecordReadable(total_len, processed_len, sizeof(codec_id))) { + LOG(ERROR) << __func__ << ": Not valid codec id, Bad pacs record."; + break; + } + + STREAM_TO_ARRAY(&codec_id, p, static_cast (sizeof(codec_id))); + + processed_len += static_cast (sizeof(codec_id)); + + if (codec_id[0] == CODEC_ID_LC3) { + LOG(INFO) << __func__ << ": LC3 codec "; + codec_type = CodecIndex::CODEC_INDEX_SOURCE_LC3; + } else { + // TODO to check for vendor codecs + break; + } + + // codec_cap_len is of 1 byte + if (!IsRecordReadable(total_len, processed_len, 1)) { + LOG(ERROR) << __func__ << ": Not valid codec id, Bad pacs record."; + break; + } + + STREAM_TO_UINT8(codec_cap_len, p); + processed_len ++; + + LOG(WARNING) << __func__ + << ": codec_cap_len: " << loghex(codec_cap_len) + << ": processed_len: " << loghex(processed_len); + + if (!codec_cap_len) { + LOG(ERROR) << __func__ + << ": Invalid codec cap len"; + break; + } + + if (!IsRecordReadable(total_len, processed_len, codec_cap_len)) { + LOG(ERROR) << __func__ << ": not enough data, Bad pacs record."; + break; + } + + codec_caps.resize(codec_cap_len); + STREAM_TO_ARRAY(codec_caps.data(), p, codec_cap_len); + uint8_t len = codec_cap_len; + uint8_t *pp = codec_caps.data(); + + // Now look for supported freq LTV entry + while (len) { + LOG(WARNING) << __func__ << ": len: " << loghex(len); + + if (!IsRecordReadable(total_len, processed_len, 1)) { + LOG(ERROR) << __func__ << ": not enough data, Bad pacs record."; + break; + } + uint8_t ltv_len = *pp++; + len--; + processed_len++; + + LOG(WARNING) << __func__ << ": ltv_len: " << loghex(ltv_len); + if (!ltv_len || + !IsRecordReadable(total_len, processed_len, ltv_len)) { + LOG(ERROR) << __func__ << ": Not valid ltv length"; + stop_reading = true; + break; + } + + processed_len += ltv_len; + + // get type and value + uint8_t ltv_type = *pp++; + LOG(WARNING) << __func__ << ": ltv_type: " << loghex(ltv_type); + if(!IsLtvValid(ltv_type, ltv_len)) { + LOG(ERROR) << __func__ << ": No ltv type to length match"; + stop_reading = true; + break; + } + if(ltv_type == LTV_TYPE_SUP_FREQS) { + uint16_t supp_freqs; + STREAM_TO_UINT16(supp_freqs, pp); + LOG(WARNING) << __func__ << ": supp_freqs: " << supp_freqs; + + for (auto it : freq_map) { + if(supp_freqs & it.first) { + CodecConfig codec_config; + codec_config.codec_type = codec_type; + codec_config.sample_rate = it.second; + pac_records.push_back(codec_config); + } + } + } else { + uint8_t rem_len = ltv_len - 1; + LOG(WARNING) << __func__ << ": rem_len: " << loghex(rem_len); + while (rem_len--) { pp++; }; + } + + if (len >= ltv_len) { + len -= ltv_len; + } else { + LOG(ERROR) << __func__ << "wrong len"; + len = 0; + } + } + + LOG(WARNING) << __func__ << ": stop_reading: " << stop_reading; + if (stop_reading) break; + + // set the default chnl count to mono as it is optional + for (auto it = pac_records.begin(); it != pac_records.end(); ++it) { + it->channel_mode = CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + } + + // now check for other LTV values + len = codec_cap_len; + pp = codec_caps.data(); + while (len) { + LOG(WARNING) << __func__ + << ": checking other LTV values,len: " << loghex(len); + uint8_t ltv_len = *pp++; + len--; + LOG(WARNING) << __func__ << ": ltv_len: " << loghex(ltv_len); + + //get type and value + uint8_t ltv_type = *pp++; + LOG(WARNING) << __func__ << ": ltv_type: " << loghex(ltv_type); + if(ltv_type == LTV_TYPE_SUP_FRAME_DUR) { + uint8_t supp_frames; + STREAM_TO_UINT8(supp_frames, pp); + LOG(WARNING) << __func__ + << ": pac rec len: " << loghex(pac_records.size()); + for (auto it = pac_records.begin(); it != pac_records.end(); + ++it) { + UpdateCapaSupFrameDurations(&(*it), supp_frames); + } + } else if (ltv_type == LTV_TYPE_CHNL_COUNTS) { + uint8_t chnl_allocation; + STREAM_TO_UINT8(chnl_allocation, pp); + for (auto it = pac_records.begin(); it != pac_records.end(); ++it) { + it->channel_mode = + static_cast (chnl_allocation); + } + } else if (ltv_type == LTV_TYPE_OCTS_PER_FRAME) { + uint32_t octs_per_frame; + STREAM_TO_UINT32(octs_per_frame, pp); + for (auto it = pac_records.begin(); it != pac_records.end(); ++it) { + UpdateCapaSupOctsPerFrame(&(*it), octs_per_frame); + } + } else if (ltv_type == LTV_TYPE_MAX_SUP_FRAMES_PER_SDU) { + uint32_t max_sup_frames_per_sdu; + STREAM_TO_UINT32(max_sup_frames_per_sdu, pp); + for (auto it = pac_records.begin(); it != pac_records.end(); ++it) { + UpdateCapaMaxSupLc3Frames(&(*it), max_sup_frames_per_sdu); + } + } else { + uint8_t rem_len = ltv_len - 1; + LOG(WARNING) << __func__ << ": rem_len: " << loghex(rem_len); + while (rem_len--) { pp++;}; + } + + if(len >= ltv_len) { + len -= ltv_len; + } else { + LOG(ERROR) << __func__ << ": wrong len"; + len = 0; + } + } + + //Meta data length 1 byte + if (!IsRecordReadable(total_len, processed_len, 1)) { + LOG(ERROR) << __func__ << ": Not valid meta data len, Bad pacs record."; + break; + } + + STREAM_TO_UINT8(meta_data_len, p); + processed_len ++; + LOG(WARNING) << __func__ << ": meta_data_len: " << loghex(meta_data_len) + << ": processed_len: " << loghex(processed_len); + + if (meta_data_len) { + if (!IsRecordReadable(total_len, processed_len, meta_data_len)) { + LOG(ERROR) << __func__ << ": not enough data, Bad pacs record."; + break; + } + + meta_data.resize(meta_data_len); + STREAM_TO_ARRAY(meta_data.data(), p, meta_data_len); + uint8_t len = meta_data_len; + uint8_t *pp = meta_data.data(); + + while (len) { + LOG(WARNING) << __func__ << ": len: " << loghex(len); + uint8_t ltv_len = *pp++; + len--; + processed_len++; + + LOG(WARNING) << __func__ << ": ltv_len: " << loghex(ltv_len); + if (!ltv_len || + !IsRecordReadable(total_len, processed_len, ltv_len)) { + LOG(ERROR) << __func__ << ": Not valid ltv length"; + stop_reading = true; + break; + } + + processed_len += ltv_len; + + // get type and value + uint8_t ltv_type = *pp++; + + LOG(WARNING) << __func__ << ": ltv_type: " << loghex(ltv_type); + + if (!IsLtvValid(ltv_type, ltv_len)) { + LOG(ERROR) << __func__ << ": No ltv type to length match"; + stop_reading = true; + break; + } + + if (ltv_type == LTV_TYPE_PREF_AUD_CONTEXT) { + STREAM_TO_UINT16(context_type, pp); + LOG(WARNING) << __func__ + << ": ltv_context_type: " << loghex(context_type); + + for (auto it = pac_records.begin(); it != pac_records.end(); ++it) { + UpdateCapaPreferredContexts(&(*it), context_type); + } + } else if (ltv_type == LTV_TYPE_VS_META_DATA) { + uint16_t company_id; + STREAM_TO_UINT16(company_id, pp); + + //total vs meta data length(meta length -4) in bytes. + uint8_t total_vendor_ltv_len = meta_data_len - 4; + LOG(WARNING) << __func__ + << ": total_vendor_ltv_len: " << loghex(total_vendor_ltv_len); + + if (company_id == QTI_ID) { + while (total_vendor_ltv_len) { + uint8_t vs_meta_data_len = *pp++; + LOG(WARNING) << __func__ + << ": vs_meta_data_len: " << loghex(vs_meta_data_len); + + // get type and value + uint8_t vs_meta_data_type = *pp++; + LOG(WARNING) << __func__ + << ": vs_meta_data_type: " << loghex(vs_meta_data_type); + + if (vs_meta_data_type == LTV_TYPE_VS_META_DATA_LC3Q) { + uint8_t vs_meta_data_value[vs_meta_data_len - 1]; + STREAM_TO_ARRAY(&vs_meta_data_value, pp, + static_cast (sizeof(vs_meta_data_value))); + + for (auto it = pac_records.begin(); it != pac_records.end(); ++it) { + UpdateCapaVendorMetaDataLc3QPref(&(*it), true); + UpdateCapaVendorMetaDataLc3QVer(&(*it), vs_meta_data_value[0]); + } + } else { + //TODO check for other ltvs + uint8_t rem_len = vs_meta_data_len - 1; + LOG(WARNING) << __func__ << ": rem_len: " << loghex(rem_len); + while (rem_len--) { pp++;}; + } + + /* 5bytes (VS length bypte + Meta datatype + + company ID(2 bytes) + Lc3q length) */ + if(total_vendor_ltv_len >= (vs_meta_data_len + 5)) { + total_vendor_ltv_len -= (vs_meta_data_len + 5); + len = total_vendor_ltv_len; + } else { + LOG(ERROR) << __func__ << ": wrong len."; + total_vendor_ltv_len = 0; + } + } + } else { + //TODO check for other comany IDs + uint8_t rem_len = total_vendor_ltv_len - 1; + LOG(WARNING) << __func__ << ": rem_len: " << loghex(rem_len); + while (rem_len--) { pp++;}; + } + } else { + uint8_t rem_len = ltv_len - 1; + LOG(WARNING) << __func__ << ": rem_len: " << loghex(rem_len); + while (rem_len--) { pp++;}; + } + + if (len >= ltv_len) { + len -= ltv_len; + } else { + LOG(ERROR) << __func__ << ": wrong len"; + len = 0; + } + } + } + + if (sinkinfo != nullptr) { + // Now update all records to conf file + while (!pac_records.empty()) { + CodecConfig record = pac_records.back(); + sinkinfo->sink_pac_records.push_back(record); + pac_records.pop_back(); + btif_bap_add_record(dev->address, REC_TYPE_CAPABILITY, + context_type, CodecDirection::CODEC_DIR_SINK, + &record); + } + } else if (srcinfo != nullptr) { + // Now update all records to conf file + while (!pac_records.empty()) { + CodecConfig record = pac_records.back(); + srcinfo->src_pac_records.push_back(record); + pac_records.pop_back(); + btif_bap_add_record(dev->address, REC_TYPE_CAPABILITY, + context_type, CodecDirection::CODEC_DIR_SRC, + &record); + } + } + num_pac_recs--; + } + + if (sinkinfo != nullptr) { + sinkinfo->read_sink_pac_record = true; + bool all_sink_pacs_read = false; + for (auto it = dev->sink_info.begin(); + it != dev->sink_info.end(); it ++) { + if (it->read_sink_pac_record == true) { + all_sink_pacs_read = true; + continue; + } else { + all_sink_pacs_read = false; + break; + } + } + + LOG(WARNING) << __func__ + << ": all_sink_pacs_read: " << all_sink_pacs_read; + if (all_sink_pacs_read) + dev->chars_read |= SINK_PAC; + + } else if (srcinfo != nullptr) { + srcinfo->read_src_pac_record = true; + bool all_source_pacs_read = false; + for (auto it = dev->src_info.begin(); + it != dev->src_info.end(); it ++) { + if (it->read_src_pac_record == true) { + all_source_pacs_read = true; + continue; + } else { + all_source_pacs_read = false; + break; + } + } + + LOG(WARNING) << __func__ + << ": all_source_pacs_read: " << all_source_pacs_read; + if (all_source_pacs_read) + dev->chars_read |= SRC_PAC; + } + } + + void OnReadOnlyPropertiesRead(uint16_t client_id, uint16_t conn_id, + tGATT_STATUS status, uint16_t handle, + uint16_t len, uint8_t *value, void* data) { + PacsDevice* dev = pacsDevices.FindByConnId(conn_id); + if (!dev) { + LOG(ERROR) << __func__ << "unknown conn_id=" << loghex(conn_id); + return; + } + SinkPacsData* sinkinfo = FindSinkByHandle(dev, handle); + SrcPacsData* srcinfo = FindSrcByHandle(dev, handle); + + if (sinkinfo != nullptr || srcinfo != nullptr) { + ParsePacRecord(dev, handle, len, value, data); + + } else if (dev->sink_loc_handle == handle) { + uint8_t *p = value; + STREAM_TO_UINT32(dev->sink_locations, p); + dev->chars_read |= SINK_LOC; + btif_bap_add_audio_loc(dev->address, CodecDirection::CODEC_DIR_SINK, + dev->sink_locations); + LOG(WARNING) << __func__ << ": sink loc: " << loghex(dev->sink_locations); + + } else if(dev->src_loc_handle == handle) { + uint8_t *p = value; + STREAM_TO_UINT32(dev->src_locations, p); + dev->chars_read |= SRC_LOC; + btif_bap_add_audio_loc(dev->address, CodecDirection::CODEC_DIR_SRC, + dev->src_locations); + LOG(WARNING) << __func__ << ": src loc: " << loghex(dev->src_locations); + + } else if(dev->avail_contexts_handle == handle) { + uint8_t* p = value; + STREAM_TO_UINT32(dev->available_contexts, p); + dev->chars_read |= AVAIL_CONTEXTS; + + } else if(dev->supp_contexts_handle == handle) { + uint8_t* p = value; + STREAM_TO_UINT32(dev->supported_contexts, p); + dev->chars_read |= SUPP_CONTEXTS; + btif_bap_add_supp_contexts(dev->address, dev->supported_contexts); + } + + LOG(WARNING) << __func__ << ": chars_read: " << loghex(dev->chars_read); + + // check if all pacs characteristics are read + // send out the callback as service discovery completed + if (dev->chars_read == dev->chars_to_be_read) { + + UpdateConsolidatedsinkPacRecords(dev); + UpdateConsolidatedsrcPacRecords(dev); + + // get the callback and update the upper layers + auto iter = callbacks.find(client_id); + if (iter != callbacks.end()) { + dev->discovery_completed = true; + PacsClientCallbacks *callback = iter->second; + callback->OnSearchComplete(DISCOVER_SUCCESS, + dev->address, + dev->consolidated_sink_pac_records, + dev->consolidated_src_pac_records, + dev->sink_locations, + dev->src_locations, + dev->available_contexts, + dev->supported_contexts); + } + } + } + + static void OnReadOnlyPropertiesReadStatic(uint16_t client_id, + uint16_t conn_id, + tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + if (instance) + instance->OnReadOnlyPropertiesRead(client_id, conn_id, status, handle, + len, value, data); + } + + static void OnReadAvailableAudioStatic(uint16_t client_id, + uint16_t conn_id, + tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + if (instance) + instance->OnReadAvailableAudio(client_id, conn_id, status, handle, + len, value, data); + } + + + private: + uint8_t gatt_client_id = BTA_GATTS_INVALID_IF; + uint16_t pacs_client_id = 0; + PacsDevices pacsDevices; + // client id to callbacks mapping + std::map callbacks; + + void find_server_changed_ccc_handle(uint16_t conn_id, + const gatt::Service* service) { + PacsDevice* pacsDevice = pacsDevices.FindByConnId(conn_id); + if (!pacsDevice) { + LOG(ERROR) << __func__ + << ": Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + for (const gatt::Characteristic& charac : service->characteristics) { + if (charac.uuid == Uuid::From16Bit(GATT_UUID_GATT_SRV_CHGD)) { + pacsDevice->srv_changed_ccc_handle = + find_ccc_handle(conn_id, charac.value_handle); + if (!pacsDevice->srv_changed_ccc_handle) { + LOG(ERROR) << __func__ + << ": cannot find service changed CCC descriptor"; + continue; + } + LOG(INFO) << __func__ << ": service_changed_ccc=" + << loghex(pacsDevice->srv_changed_ccc_handle); + break; + } + } + } + + // Find the handle for the client characteristics configuration of a given + // characteristics + uint16_t find_ccc_handle(uint16_t conn_id, uint16_t char_handle) { + const gatt::Characteristic* p_char = + BTA_GATTC_GetCharacteristic(conn_id, char_handle); + + if (!p_char) { + LOG(WARNING) << __func__ << ": No such characteristic: " << char_handle; + return 0; + } + + for (const gatt::Descriptor& desc : p_char->descriptors) { + if (desc.uuid == Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)) + return desc.handle; + } + + return 0; + } + + SinkPacsData *FindSinkByHandle(PacsDevice *dev, uint16_t handle) { + LOG(INFO) << __func__ << ": handle:" << loghex(handle); + auto iter = std::find_if(dev->sink_info.begin(), + dev->sink_info.end(), + [&handle](SinkPacsData data) { + return (data.sink_pac_handle == handle); + }); + + return (iter == dev->sink_info.end()) ? nullptr : &(*iter); + } + + SrcPacsData *FindSrcByHandle(PacsDevice *dev, uint16_t handle) { + LOG(INFO) << __func__ << ": handle:" << loghex(handle); + auto iter = std::find_if(dev->src_info.begin(), + dev->src_info.end(), + [&handle](SrcPacsData data) { + return (data.src_pac_handle == handle); + }); + + return (iter == dev->src_info.end()) ? nullptr : &(*iter); + } + + void UpdateConsolidatedsinkPacRecords(PacsDevice *dev) { + LOG(INFO) << __func__; + for (auto it = dev->sink_info.begin(); + it != dev->sink_info.end(); it ++) { + for (auto i = it->sink_pac_records.begin(); + i != it->sink_pac_records.end(); i ++) { + dev->consolidated_sink_pac_records. + push_back(static_cast(*i)); + } + } + } + + void UpdateConsolidatedsrcPacRecords(PacsDevice *dev) { + LOG(INFO) << __func__; + for (auto it = dev->src_info.begin(); + it != dev->src_info.end(); it ++) { + for (auto i = it->src_pac_records.begin(); + i != it->src_pac_records.end(); i ++) { + dev->consolidated_src_pac_records. + push_back(static_cast(*i)); + } + } + } +}; + +const char* get_gatt_event_name(uint32_t event) { + switch (event) { + CASE_RETURN_STR(BTA_GATTC_DEREG_EVT) + CASE_RETURN_STR(BTA_GATTC_OPEN_EVT) + CASE_RETURN_STR(BTA_GATTC_CLOSE_EVT) + CASE_RETURN_STR(BTA_GATTC_SEARCH_CMPL_EVT) + CASE_RETURN_STR(BTA_GATTC_NOTIF_EVT) + CASE_RETURN_STR(BTA_GATTC_ENC_CMPL_CB_EVT) + CASE_RETURN_STR(BTA_GATTC_CONN_UPDATE_EVT) + CASE_RETURN_STR(BTA_GATTC_SRVC_CHG_EVT) + CASE_RETURN_STR(BTA_GATTC_SRVC_DISC_DONE_EVT) + CASE_RETURN_STR(BTA_GATTC_CONGEST_EVT) + default: + return "Unknown Event"; + } +} + +void pacs_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { + if (p_data == nullptr || !instance) { + LOG(ERROR) << __func__ << ": p_data is null or no instance, return"; + return; + } + LOG(INFO) << __func__ << ": Event : " << get_gatt_event_name(event); + + switch (event) { + case BTA_GATTC_DEREG_EVT: + break; + + case BTA_GATTC_OPEN_EVT: { + tBTA_GATTC_OPEN& o = p_data->open; + instance->OnGattConnected(o.status, o.conn_id, o.client_if, o.remote_bda, + o.transport, o.mtu); + break; + } + + case BTA_GATTC_CLOSE_EVT: { + tBTA_GATTC_CLOSE& c = p_data->close; + instance->OnGattDisconnected(c.status, c.conn_id, c.client_if, + c.remote_bda, c.reason); + } break; + + case BTA_GATTC_SEARCH_CMPL_EVT: + instance->OnServiceSearchComplete(p_data->search_cmpl.conn_id, + p_data->search_cmpl.status); + break; + + case BTA_GATTC_NOTIF_EVT: + if (!p_data->notify.is_notify || p_data->notify.len > GATT_MAX_ATTR_LEN) { + LOG(ERROR) << __func__ << ": rejected BTA_GATTC_NOTIF_EVT. is_notify=" + << p_data->notify.is_notify + << ", len=" << p_data->notify.len; + break; + } + instance->OnNotificationEvent(p_data->notify.conn_id, + p_data->notify.handle, p_data->notify.len, + p_data->notify.value); + break; + + case BTA_GATTC_ENC_CMPL_CB_EVT: + instance->OnEncryptionComplete(p_data->enc_cmpl.remote_bda, true); + break; + + case BTA_GATTC_CONN_UPDATE_EVT: + instance->OnConnectionUpdateComplete(p_data->conn_update.conn_id, + p_data); + break; + + case BTA_GATTC_SRVC_CHG_EVT: + instance->OnServiceChangeEvent(p_data->remote_bda); + break; + + case BTA_GATTC_SRVC_DISC_DONE_EVT: + instance->OnServiceDiscDoneEvent(p_data->remote_bda); + break; + case BTA_GATTC_CONGEST_EVT: + instance->OnCongestionEvent(p_data->congest.conn_id, + p_data->congest.congested); + break; + default: + break; + } +} + +void encryption_callback(const RawAddress* address, tGATT_TRANSPORT, void*, + tBTM_STATUS status) { + if (instance) { + instance->OnEncryptionComplete(*address, + status == BTM_SUCCESS ? true : false); + } +} + +void PacsClient::Initialize(PacsClientCallbacks* callbacks) { + if (instance) { + instance->Register(callbacks); + } else { + instance = new PacsClientImpl(); + instance->Register(callbacks); + } +} + +void PacsClient::CleanUp(uint16_t client_id) { + if(instance->GetClientCount()) { + instance->Deregister(client_id); + if(!instance->GetClientCount()) { + delete instance; + instance = nullptr; + } + } +} + +PacsClient* PacsClient::Get() { + CHECK(instance); + return instance; +} + +} // namespace pacs +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/bap/ucast_client_int.h b/le_audio/system/bt/bta/bap/ucast_client_int.h new file mode 100644 index 00000000000..7bf6c56acc2 --- /dev/null +++ b/le_audio/system/bt/bta/bap/ucast_client_int.h @@ -0,0 +1,1262 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +#pragma once + +#include +#include "state_machine.h" +#include +#include "bta_bap_uclient_api.h" +#include "bta_pacs_client_api.h" +#include "bta_ascs_client_api.h" +#include "bt_trace.h" +#include "uclient_alarm.h" +#include "btif_util.h" + +namespace bluetooth { +namespace bap { +namespace ucast { + +using bluetooth::bap::pacs::ConnectionState; +using bluetooth::bap::pacs::PacsClient; +using bluetooth::bap::ascs::GattState; +using bluetooth::bap::ascs::AscsClient; +using bluetooth::bap::ascs::AseParams; +using bluetooth::bap::ascs::AseCodecConfigParams; +using bluetooth::bap::ascs::AseCodecConfigOp; +using bluetooth::bap::cis::CisInterface; + +using bluetooth::bap::cis::CigState; +using bluetooth::bap::cis::CisState; + +using bluetooth::bap::alarm::BapAlarm; +using bluetooth::bap::alarm::BapAlarmCallbacks; + +class UstreamManager; +class UstreamManagers; +struct UcastAudioStream; +class UcastAudioStreams; +class StreamContexts; +struct StreamContext; +class StreamTracker; + +enum class StreamAttachedState { + IDLE = 0x1 << 0, + IDLE_TO_PHY = 0x1 << 1, + VIRTUAL = 0x1 << 2, + VIR_TO_PHY = 0x1 << 3, + PHYSICAL = 0x1 << 4 +}; + +enum class StreamControlType { + None = 0x00, + Connect = 0X01, + Disconnect = 0x02, + Start = 0x04, + Stop = 0x08, + Reconfig = 0x10, + UpdateStream = 0x20 +}; + +enum class DeviceType { + NONE = 0x00, + EARBUD = 0X01, // group member + HEADSET_STEREO = 0x02, // headset with 1 CIS + HEADSET_SPLIT_STEREO = 0x03 // headset with 2 CIS +}; + +enum class IntConnectState { + IDLE = 0x00, + PACS_CONNECTING = 0x01, + PACS_DISCOVERING = 0X02, + ASCS_CONNECTING = 0x03, + ASCS_DISCOVERING = 0x04, + ASCS_DISCOVERED = 0x05, +}; + +enum class AscsPendingCmd { + NONE = 0x00, + CODEC_CONFIG_ISSUED = 0x01, + QOS_CONFIG_ISSUED = 0x02, + ENABLE_ISSUED = 0x03, + START_READY_ISSUED = 0x04, + DISABLE_ISSUED = 0x05, + STOP_READY_ISSUED = 0x06, + RELEASE_ISSUED = 0x07, + UPDATE_METADATA_ISSUED = 0x08 +}; + +enum class CisPendingCmd { + NONE = 0x00, + CIG_CREATE_ISSUED = 0x08, + CIS_CREATE_ISSUED = 0x09, + CIS_SETUP_DATAPATH_ISSUED = 0x10, + CIS_RMV_DATAPATH_ISSUED = 0x11, + CIS_DESTROY_ISSUED = 0x12, + CIG_REMOVE_ISSUED = 0x13 +}; + +enum class GattPendingCmd { + NONE = 0x00, + GATT_CONN_PENDING = 0x01, + GATT_DISC_PENDING = 0x02 +}; + +typedef enum { + BAP_CONNECT_REQ_EVT = 0X00, + BAP_DISCONNECT_REQ_EVT, + BAP_START_REQ_EVT, + BAP_STOP_REQ_EVT, + BAP_RECONFIG_REQ_EVT, + BAP_STREAM_UPDATE_REQ_EVT, + PACS_CONNECTION_STATE_EVT, + PACS_DISCOVERY_RES_EVT, + PACS_AUDIO_CONTEXT_RES_EVT, + ASCS_CONNECTION_STATE_EVT, + ASCS_DISCOVERY_RES_EVT, + ASCS_ASE_STATE_EVT, + ASCS_ASE_OP_FAILED_EVT, + CIS_GROUP_STATE_EVT, + CIS_STATE_EVT, + BAP_TIME_OUT_EVT, +} BapEvent; + +struct BapConnect { + std::vector bd_addr; + bool is_direct; + std::vector streams; +}; + +struct BapDisconnect { + RawAddress bd_addr; + std::vector streams; +}; + +struct BapStart { + RawAddress bd_addr; + std::vector streams; +}; + +struct BapStop { + RawAddress bd_addr; + std::vector streams; +}; + +struct BapReconfig { + RawAddress bd_addr; + std::vector streams; +}; + +struct BapStreamUpdate { + RawAddress bd_addr; + std::vector update_streams; +}; + +struct PacsConnectionState { + RawAddress bd_addr; + ConnectionState state; +}; + +struct AscsConnectionState { + RawAddress bd_addr; + GattState state; +}; + +struct AscsDiscovery { + int status; + RawAddress bd_addr; + std::vector sink_ases_list; + std::vector src_ases_list; +}; + +struct AscsState { + RawAddress bd_addr; + AseParams ase_params; +}; + +struct AscsOpFailed { + RawAddress bd_addr; + ascs::AseOpId ase_op_id; + std::vector ase_list; +}; + +struct CisGroupState { + uint8_t cig_id; + CigState state; +}; + +struct CisStreamState { + uint8_t cig_id; + uint8_t cis_id; + uint8_t direction; + CisState state; +}; + +struct PacsDiscovery { + int status; + RawAddress bd_addr; + std::vector sink_pac_records; + std::vector src_pac_records; + uint32_t sink_locations; + uint32_t src_locations; + uint32_t available_contexts; + uint32_t supported_contexts; +}; + +struct PacsAvailableContexts { + RawAddress bd_addr; + uint32_t available_contexts; +}; + +struct IntStrmTracker { + IntStrmTracker(StreamType strm_type, uint8_t ase_id, uint8_t cig_id, + uint8_t cis_id, CodecConfig &codec_config, + QosConfig &qos_config) + : strm_type(strm_type), ase_id(ase_id) , cig_id(cig_id) , + cis_id(cis_id), codec_config(codec_config), + qos_config(qos_config) { + attached_state = StreamAttachedState::IDLE; + } + StreamType strm_type; + uint8_t ase_id; + uint8_t cig_id; + uint8_t cis_id; + CodecConfig codec_config; + QosConfig qos_config; + StreamAttachedState attached_state; +}; + +class IntStrmTrackers { + public: + std::vector FindByCigId(uint8_t cig_id) { + std::vector trackers; + for (auto i = int_strm_trackers.begin(); + i != int_strm_trackers.end();i++) { + if((*i)->cig_id == cig_id) { + LOG(WARNING) << __func__ << " tracker found"; + trackers.push_back(*i); + } + } + return trackers; + } + + std::vector FindByCigIdAndDir(uint8_t cig_id, + uint8_t direction) { + std::vector trackers; + for (auto i = int_strm_trackers.begin(); + i != int_strm_trackers.end();i++) { + if((*i)->cig_id == cig_id && + (*i)->strm_type.direction == direction) { + trackers.push_back(*i); + } + } + return trackers; + } + + std::vector FindByCisId(uint8_t cig_id, uint8_t cis_id) { + std::vector trackers; + for (auto i = int_strm_trackers.begin(); + i != int_strm_trackers.end();i++) { + if((*i)->cig_id == cig_id && (*i)->cis_id == cis_id) { + trackers.push_back(*i); + } + } + return trackers; + } + + IntStrmTracker *FindByIndex(uint8_t i) { + IntStrmTracker *tracker = int_strm_trackers.at(i); + return tracker; + } + + IntStrmTracker *FindByAseId(uint8_t ase_id) { + auto iter = std::find_if(int_strm_trackers.begin(), int_strm_trackers.end(), + [&ase_id](IntStrmTracker *tracker) { + return tracker->ase_id == ase_id; + }); + + return (iter == int_strm_trackers.end()) ? nullptr : (*iter); + } + + IntStrmTracker *FindOrAddBytrackerType(StreamType strm_type, + uint8_t ase_id, uint8_t cig_id, uint8_t cis_id, + CodecConfig &codec_config, QosConfig &qos_config) { + + auto iter = std::find_if(int_strm_trackers.begin(), int_strm_trackers.end(), + [&strm_type, &cig_id, &cis_id](IntStrmTracker *tracker) { + return ((tracker->strm_type.type == strm_type.type) && + (tracker->strm_type.direction == + strm_type.direction) && + (tracker->cig_id == cig_id) && + (tracker->cis_id == cis_id)); + }); + + if (iter == int_strm_trackers.end()) { + IntStrmTracker *tracker = new IntStrmTracker(strm_type, + ase_id, cig_id, cis_id, codec_config, qos_config); + int_strm_trackers.push_back(tracker); + return tracker; + } else { + return (*iter); + } + } + + void Remove(StreamType strm_type, uint8_t cig_id, uint8_t cis_id) { + for (auto it = int_strm_trackers.begin(); it != int_strm_trackers.end();) { + if (((*it)->strm_type.type = strm_type.type) && + ((*it)->strm_type.direction = strm_type.direction) && + ((*it)->cig_id = cig_id) && ((*it)->cis_id = cis_id)) { + delete(*it); + it = int_strm_trackers.erase(it); + } else { + it++; + } + } + } + + void RemoveVirtualAttachedTrackers() { + LOG(WARNING) << __func__; + for (auto it = int_strm_trackers.begin(); it != int_strm_trackers.end();) { + if ((*it)->attached_state == StreamAttachedState::VIRTUAL) { + delete(*it); + it = int_strm_trackers.erase(it); + LOG(WARNING) << __func__ + << ": Removed virtual attached tracker"; + } else { + it++; + } + } + } + + size_t size() { return (int_strm_trackers.size()); } + + std::vector *GetTrackerList() { + return &int_strm_trackers; + } + + std::vector GetTrackerListByDir(uint8_t direction) { + std::vector trackers; + for (auto i = int_strm_trackers.begin(); + i != int_strm_trackers.end();i++) { + if((*i)->strm_type.direction == direction) { + trackers.push_back(*i); + } + } + return trackers; + } + + private: + std::vector int_strm_trackers; +}; + +union BapEventData { + BapConnect connect_req; + BapDisconnect disc_req; + BapStart start_req; + BapStop stop_req; + BapReconfig reconfig_req; + PacsConnectionState connection_state_rsp; + PacsDiscovery pacs_discovery_rsp; + PacsAvailableContexts pacs_audio_context_rsp; +}; + +enum class TimeoutVal { //in milli seconds(1sec = 1000ms) + ConnectingTimeout = 10000, + StartingTimeout = 2000, + StoppingTimeout = 2000, + DisconnectingTimeout = 1000, + ReconfiguringTimeout = 2000, + UpdatingTimeout = 1000 +}; + +enum class MaxTimeoutVal { //in milli seconds(1sec = 1000ms) + ConnectingTimeout = 10000, + StartingTimeout = 4000, + StoppingTimeout = 4000, + DisconnectingTimeout = 4000, + ReconfiguringTimeout = 8000, + UpdatingTimeout = 4000 +}; + +enum class TimeoutReason { + STATE_TRANSITION = 1, +}; + +struct BapTimeout { + RawAddress bd_addr; + StreamTracker* tracker; + TimeoutReason reason; + int transition_state; +}; + +class StreamTracker : public bluetooth::common::StateMachine { + public: + enum { + kStateIdle, // + kStateConnecting, // + kStateConnected, // + kStateStarting, // + kStateStreaming, // + kStateStopping, // + kStateDisconnecting, // + kStateReconfiguring, // + kStateUpdating + }; + + class StateIdle : public State { + public: + StateIdle(StreamTracker& sm) + : State(sm, kStateIdle), tracker_(sm), + strm_mgr_(sm.GetStreamManager()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Idle"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + StreamTracker &tracker_; + UstreamManager *strm_mgr_; + }; + + class StateConnecting : public State { + public: + StateConnecting(StreamTracker& sm) + : State(sm, kStateConnecting), tracker_(sm), + strm_mgr_(sm.GetStreamManager()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Connecting"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + void DeriveDeviceType(PacsDiscovery *pacs_discovery); + bool AttachStreamsToContext(std::vector *all_trackers, + std::vector *streams, + uint8_t cis_count, + std::vector *ase_ops); + alarm_t* state_transition_timer; + BapTimeout timeout; + + private: + StreamTracker &tracker_; + UstreamManager *strm_mgr_; + PacsDiscovery pacs_discovery_; + AscsDiscovery ascs_discovery_; + IntStrmTrackers int_strm_trackers_; + }; + + class StateConnected : public State { + public: + StateConnected(StreamTracker& sm) + : State(sm, kStateConnected), tracker_(sm), + strm_mgr_(sm.GetStreamManager()){} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Connected"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + StreamTracker &tracker_; + UstreamManager *strm_mgr_; + }; + + class StateStarting : public State { + public: + StateStarting(StreamTracker& sm) + : State(sm, kStateStarting), tracker_(sm), + strm_mgr_(sm.GetStreamManager()){} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Starting"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + bool CheckAndUpdateStreamingState(); + alarm_t* state_transition_timer; + PacsAvailableContexts pacs_contexts; + BapTimeout timeout; + + private: + StreamTracker &tracker_; + UstreamManager *strm_mgr_; + IntStrmTrackers int_strm_trackers_; + }; + + class StateStreaming : public State { + public: + StateStreaming(StreamTracker& sm) + : State(sm, kStateStreaming), tracker_(sm), + strm_mgr_(sm.GetStreamManager()){} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Streaming"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + StreamTracker &tracker_; + UstreamManager *strm_mgr_; + }; + + class StateStopping : public State { + public: + StateStopping(StreamTracker& sm) + : State(sm, kStateStopping), tracker_(sm), + strm_mgr_(sm.GetStreamManager()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Stopping"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + bool TerminateCisAndCig(UcastAudioStream *stream); + bool CheckAndUpdateStoppedState(); + alarm_t* state_transition_timer; + BapTimeout timeout; + + private: + StreamTracker &tracker_; + UstreamManager *strm_mgr_; + IntStrmTrackers int_strm_trackers_; + }; + + class StateDisconnecting : public State { + public: + StateDisconnecting(StreamTracker& sm) + : State(sm, kStateDisconnecting), tracker_(sm), + strm_mgr_(sm.GetStreamManager()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Disconnecting"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + bool TerminateGattConnection(); + void ContinueDisconnection(UcastAudioStream *stream); + bool CheckAndUpdateDisconnectedState(); + alarm_t* state_transition_timer; + BapTimeout timeout; + + private: + StreamTracker &tracker_; + UstreamManager *strm_mgr_; + IntStrmTrackers int_strm_trackers_; + }; + + class StateReconfiguring: public State { + public: + StateReconfiguring(StreamTracker& sm) + : State(sm, kStateReconfiguring), tracker_(sm), + strm_mgr_(sm.GetStreamManager()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Reconfiguring"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + alarm_t* state_transition_timer; + BapTimeout timeout; + + private: + StreamTracker &tracker_; + UstreamManager *strm_mgr_; + IntStrmTrackers int_strm_trackers_; + }; + + class StateUpdating: public State { + public: + StateUpdating(StreamTracker& sm) + : State(sm, kStateUpdating), tracker_(sm), + strm_mgr_(sm.GetStreamManager()) {} + void OnEnter() override; + void OnExit() override; + const char* GetState() { return "Updating"; } + bool ProcessEvent(uint32_t event, void* p_data) override; + alarm_t* state_transition_timer; + BapTimeout timeout; + + private: + StreamTracker &tracker_; + UstreamManager *strm_mgr_; + IntStrmTrackers int_strm_trackers_; + }; + + StreamTracker(int init_state_id, UstreamManager *strm_mgr, + std::vector *connect_streams, + std::vector *reconfig_streams, + std::vector *streams, + StreamControlType ops_type): + init_state_id_(init_state_id), + strm_mgr_(strm_mgr) { + + state_idle_ = new StateIdle(*this); + state_connecting_ = new StateConnecting(*this); + state_connected_ = new StateConnected(*this); + state_starting_ = new StateStarting(*this); + state_streaming_ = new StateStreaming(*this); + state_stopping_ = new StateStopping(*this); + state_disconnecting_ = new StateDisconnecting(*this); + state_reconfiguring_ = new StateReconfiguring(*this); + state_updating_ = new StateUpdating(*this); + pacs_disc_succeded_ = false; + + AddState(state_idle_); + AddState(state_connecting_); + AddState(state_connected_); + AddState(state_starting_); + AddState(state_streaming_); + AddState(state_stopping_); + AddState(state_disconnecting_); + AddState(state_reconfiguring_); + AddState(state_updating_); + + switch(init_state_id) { + case kStateIdle: + SetInitialState(state_idle_); + break; + case kStateConnected: + SetInitialState(state_connected_); + break; + case kStateStreaming: + SetInitialState(state_streaming_); + break; + case kStateDisconnecting: + SetInitialState(state_disconnecting_); + break; + default: + SetInitialState(state_idle_); + } + + str_ops_type = ops_type; + + if(ops_type == StreamControlType::Connect) { + conn_streams = *connect_streams; + } else if(ops_type == StreamControlType::Reconfig) { + reconf_streams = *reconfig_streams; + } else if(ops_type != StreamControlType::UpdateStream) { + other_streams = *streams; + } + } + + void PauseRemoteDevInteraction(bool pause); + bool decoupleStream(StreamType *stream_info); + + uint8_t ChooseBestCodec(StreamType stream_type, + std::vector *codec_qos_configs, + PacsDiscovery *pacs_discovery); + + bool ChooseBestQos(QosConfig *src_config, + ascs::AseCodecConfigParams *rem_qos_prefs, + QosConfig *dst_config, + int stream_state, uint8_t stream_direction); + + bool HandlePacsConnectionEvent(void *p_data); + + bool HandlePacsAudioContextEvent(PacsAvailableContexts *pacs_contexts); + + bool HandleCisEventsInStreaming(void* p_data); + + bool HandleStreamUpdate (int cur_state); + + bool CheckAndUpdateStreamingState(IntStrmTrackers *int_strm_trackers); + + bool HandleAscsConnectionEvent(void *p_data); + + void HandleCigStateEvent(uint32_t event, void *p_data, + IntStrmTrackers *int_strm_trackers); + + void HandleAseStateEvent(void *p_data, StreamControlType control_type, + IntStrmTrackers *int_strm_trackers); + + void HandleAseOpFailedEvent(void *p_data); + + bool ValidateAseUpdate(void* p_data, IntStrmTrackers *int_strm_trackers, + int exp_strm_state); + + bool HandleDisconnect(void* p_data, int cur_state); + + bool HandleRemoteDisconnect(uint32_t event, void* p_data, int cur_state); + + bool StreamCanbeDisconnected(StreamContext *cur_context, uint8_t ase_id); + + bool HandleInternalDisconnect(bool release); + + bool HandleStop(void* p_data, int cur_state); + + bool HandleRemoteStop(uint32_t event, void* p_data, int cur_state); + + bool HandleRemoteReconfig(uint32_t event, void* p_data, int cur_state); + + bool PrepareCodecConfigPayload(std::vector *ase_ops, + UcastAudioStream *stream); + + void CheckAndSendQosConfig(IntStrmTrackers *int_strm_trackers); + + void CheckAndSendEnable(IntStrmTrackers *int_strm_trackers); + + bool HandleAbruptStop(uint32_t event, void* p_data); + + alarm_t* SetTimer(const char* alarmname, BapTimeout* timeout, + TimeoutReason reason, uint64_t ms); + + void ClearTimer(alarm_t* timer, const char* alarmname); + + void OnTimeout(void* data); + + StreamControlType GetControlType() { + return str_ops_type; + } + + UstreamManager *GetStreamManager() { + return strm_mgr_; + } + + bool UpdatePacsDiscovery(PacsDiscovery disc_res) { + pacs_disc_succeded_ = true; + pacs_discovery_ = disc_res; + return true; + } + + PacsDiscovery *GetPacsDiscovery() { + if(pacs_disc_succeded_) { + return &pacs_discovery_; + } else { + return nullptr; + } + } + + bool UpdateControlType(StreamControlType ops_type) { + str_ops_type = ops_type; + return true; + } + + bool UpdateStreams(std::vector *streams) { + other_streams = *streams; + return true; + } + + bool UpdateConnStreams( + std::vector *connect_streams) { + conn_streams = *connect_streams; + return true; + } + + bool UpdateReconfStreams( + std::vector *reconfig_streams) { + reconf_streams = *reconfig_streams; + return true; + } + + bool UpdateMetaUpdateStreams( + std::vector *meta_streams) { + meta_update_streams = *meta_streams; + return true; + } + + std::vector *GetStreams() { + return &other_streams; + } + std::vector *GetConnStreams() { + return &conn_streams; + } + std::vector *GetReconfStreams() { + return &reconf_streams; + } + + std::vector *GetMetaUpdateStreams() { + return &meta_update_streams; + } + + const char* GetEventName(uint32_t event) { + switch (event) { + CASE_RETURN_STR(BAP_CONNECT_REQ_EVT) + CASE_RETURN_STR(BAP_DISCONNECT_REQ_EVT) + CASE_RETURN_STR(BAP_START_REQ_EVT) + CASE_RETURN_STR(BAP_STOP_REQ_EVT) + CASE_RETURN_STR(BAP_RECONFIG_REQ_EVT) + CASE_RETURN_STR(BAP_STREAM_UPDATE_REQ_EVT) + CASE_RETURN_STR(PACS_CONNECTION_STATE_EVT) + CASE_RETURN_STR(PACS_DISCOVERY_RES_EVT) + CASE_RETURN_STR(PACS_AUDIO_CONTEXT_RES_EVT) + CASE_RETURN_STR(ASCS_CONNECTION_STATE_EVT) + CASE_RETURN_STR(ASCS_DISCOVERY_RES_EVT) + CASE_RETURN_STR(ASCS_ASE_STATE_EVT) + CASE_RETURN_STR(ASCS_ASE_OP_FAILED_EVT) + CASE_RETURN_STR(CIS_GROUP_STATE_EVT) + CASE_RETURN_STR(CIS_STATE_EVT) + CASE_RETURN_STR(BAP_TIME_OUT_EVT) + default: + return "Unknown Event"; + } + } + + private: + int init_state_id_; + UstreamManager *strm_mgr_; + std::vector conn_streams; + std::vector other_streams; + std::vector reconf_streams; + std::vector meta_update_streams; + StreamControlType str_ops_type; + bool pacs_disc_succeded_; + PacsDiscovery pacs_discovery_; + StateIdle *state_idle_; + StateConnecting *state_connecting_; + StateConnected *state_connected_; + StateStarting *state_starting_; + StateStreaming *state_streaming_; + StateStopping *state_stopping_; + StateDisconnecting *state_disconnecting_; + StateReconfiguring *state_reconfiguring_; + StateUpdating *state_updating_; +}; + +struct StreamOpConnect { + StreamType stream_type; + //std::vector codec_configs; + //std::vector qos_configs; +}; + +struct StreamOpReconfig { + StreamType stream_type; + //std::vector codec_configs; + //std::vector qos_configs; +}; + +union StreamOpData { + StreamOpConnect connect_op; + StreamType stream_type; + StreamOpReconfig reconfig_op; +}; + +struct StreamOpNode { + bool busy; + bool is_client_originated; + StreamControlType ops_type; + StreamOpData ops_data; +}; + +struct StreamIdType { + uint8_t ase_id; + uint8_t ase_direction; + bool virtual_attach; + uint8_t cig_id; + uint8_t cis_id; +}; + +struct StreamContext { + StreamContext(StreamType strm_type) + : stream_type(strm_type) { + stream_state = StreamState::DISCONNECTED; + attached_state = StreamAttachedState::IDLE; + } + StreamType stream_type; + StreamAttachedState attached_state; + std::vector stream_ids; + StreamState stream_state; + IntConnectState connection_state; + CodecConfig codec_config; + QosConfig qos_config; + QosConfig req_qos_config; +}; + +class StreamContexts { + public: + + StreamContext *FindByType(StreamType stream_type); + + StreamContext *FindOrAddByType(StreamType stream_type); + + void Remove(StreamType stream_type); + + bool IsAseAttached(StreamType stream_type); + + std::vector FindByAseAttachedState(uint16_t ase_id, + StreamAttachedState state); + + + StreamContext* FindByAseId(uint16_t ase_id); + + std::vector *GetAllContexts() { + return &strm_contexts; + } + + size_t size() { return (strm_contexts.size()); } + + private: + std::vector strm_contexts; +}; + + +class StreamOpsQueue { + public: + bool Add(StreamOpNode op_node); + + bool AddFirst(StreamOpNode op_node); + + StreamOpNode *GetNextNode(); + + bool Remove(StreamType stream_type); + + StreamOpNode* FindByContext(StreamType stream_type); + + bool ChangeOpType(StreamType stream_type, StreamControlType new_ops_type); + + size_t size() { return (queue.size()); } + + std::vector queue; +}; + +class StreamTrackers { + public: + StreamTracker *FindOrAddByType(int init_state_id, UstreamManager *strm_mgr, + std::vector *connect_streams, + std::vector *reconfig_streams, + std::vector *streams, + StreamControlType ops_type); + + bool Remove(std::vector streams, + StreamControlType ops_type); + + void RemoveByStates(std::vector state_ids); + + std::map > GetTrackersByType( + std::vector *streams); + + StreamTracker *FindByStreamsType(std::vector *streams); + + std::vector GetTrackersByStates( + std::vector *state_ids); + + bool ChangeOpType( StreamType stream_type, + StreamControlType new_ops_type); + + bool IsStreamTrackerValid(StreamTracker* Tracker, + std::vector *state_ids); + + size_t size() { return (stream_trackers.size()); } + + std::vector stream_trackers; +}; + +struct UcastAudioStream { + UcastAudioStream(uint8_t ase_id, uint8_t ase_state, uint8_t ase_direction) + : ase_id(ase_id) , ase_state(ase_state) { + ase_state = ascs::ASE_STATE_INVALID; + cig_state = CigState::INVALID; + cis_state = CisState::INVALID; + direction = ase_direction; + cig_id = 0XFF; + cis_id = 0xFF; + cis_retry_count = 0; + overall_state = StreamTracker::kStateIdle; + ase_pending_cmd = AscsPendingCmd::NONE; + cis_pending_cmd = CisPendingCmd::NONE; + } + bool is_active; + uint8_t ase_id; + uint8_t ase_state; + AseParams ase_params; + AseCodecConfigParams pref_qos_params; + uint8_t cig_id; + CigState cig_state; + uint8_t cis_id; + CisState cis_state; + uint8_t cis_retry_count; + int overall_state; // stream tracker state + StreamControlType control_type; + AscsPendingCmd ase_pending_cmd; + CisPendingCmd cis_pending_cmd; + uint16_t audio_context; + uint8_t direction; + uint32_t audio_location; + CodecConfig codec_config; + QosConfig qos_config; + QosConfig req_qos_config; +}; + +class UcastAudioStreams { + public: + + std::vector FindByCigId(uint8_t cig_id, int state) { + std::vector streams; + for (auto i = audio_streams.begin(); i != audio_streams.end();i++) { + if((*i)->cig_id == cig_id && + (*i)->overall_state == state) { + streams.push_back(*i); + } + } + return streams; + } + + std::vector FindByCisId(uint8_t cig_id, uint8_t cis_id) { + std::vector streams; + for (auto i = audio_streams.begin(); i != audio_streams.end();i++) { + if((*i)->cig_id == cig_id && (*i)->cis_id == cis_id) { + streams.push_back(*i); + } + } + return streams; + } + + UcastAudioStream *FindByStreamType(uint16_t audio_context, + uint8_t direction) { + auto it = audio_streams.begin(); + while (it != audio_streams.end()) { + if((*it)->audio_context == audio_context && + (*it)->direction & direction) { + break; + } + it++; + } + return (it == audio_streams.end()) ? nullptr : (*it); + } + + UcastAudioStream *FindByCisIdAndDir(uint8_t cig_id, uint8_t cis_id, + uint8_t dir) { + auto it = audio_streams.begin(); + while (it != audio_streams.end()) { + if((*it)->cig_id == cig_id && (*it)->cis_id == cis_id && + (*it)->direction & dir) { + break; + } + it++; + } + return (it == audio_streams.end()) ? nullptr : (*it); + } + + UcastAudioStream *FindByAseId(uint8_t ase_id) { + auto it = audio_streams.begin(); + while (it != audio_streams.end()) { + if((*it)->ase_id == ase_id) { + break; + } + it++; + } + return (it == audio_streams.end()) ? nullptr : (*it); + } + + UcastAudioStream *FindOrAddByAseId(uint8_t ase_id, uint8_t ase_state, + uint8_t ase_direction) { + auto iter = std::find_if(audio_streams.begin(), audio_streams.end(), + [&ase_id, &ase_direction](UcastAudioStream *stream) { + return (stream->ase_id == ase_id && + stream->direction == ase_direction); + }); + + if (iter == audio_streams.end()) { + UcastAudioStream *stream = new UcastAudioStream(ase_id, ase_state, + ase_direction); + stream->overall_state = StreamTracker::kStateIdle; + audio_streams.push_back(stream); + auto it = std::find_if(audio_streams.begin(), audio_streams.end(), + [&ase_id, &ase_direction](UcastAudioStream* stream) { + return (stream->ase_id == ase_id && + stream->direction == ase_direction); + }); + return (it == audio_streams.end()) ? nullptr : (*it); + } else { + return (*iter); + } + } + + std::vector GetStreamsByStates( + std::vector state_ids, + uint8_t directions) { + std::vector streams; + for (auto i = audio_streams.begin(); i != audio_streams.end();i++) { + for (auto j = state_ids.begin(); j != state_ids.end();j++) { + if(((*i)->overall_state == *j) && ((*i)->direction & directions)) { + streams.push_back(*i); + } + } + } + return streams; + } + + void Remove(uint8_t ase_id) { + for (auto it = audio_streams.begin(); it != audio_streams.end();) { + if ((*it)->ase_id == ase_id) { + delete(*it); + it = audio_streams.erase(it); + } else { + it++; + } + } + } + + std::vector *GetAllStreams() { + return &audio_streams; + } + + size_t size() { return (audio_streams.size()); } + // UcastAudioStream + private: + std::vector audio_streams; +}; + +struct GattPendingData { + GattPendingData() { + ascs_pending_cmd = GattPendingCmd::NONE; + pacs_pending_cmd = GattPendingCmd::NONE; + } + GattPendingCmd ascs_pending_cmd; + GattPendingCmd pacs_pending_cmd; +}; + +class UstreamManager { + public: + UstreamManager(const RawAddress& address, PacsClient *pacs_client, + uint16_t pacs_client_id, + AscsClient *ascs_client, CisInterface *cis_intf, + UcastClientCallbacks* callbacks, + BapAlarm *bap_alarm) + : address(address) , pacs_client(pacs_client), + pacs_client_id(pacs_client_id), + ascs_client(ascs_client), cis_intf(cis_intf), + ucl_callbacks(callbacks), bap_alarm(bap_alarm) { + pacs_state = ConnectionState::DISCONNECTED; + gatt_pending_data.pacs_pending_cmd = GattPendingCmd::NONE; + gatt_pending_data.ascs_pending_cmd = GattPendingCmd::NONE; + ascs_state = GattState::DISCONNECTED; + dev_type = DeviceType::NONE; + } + + bool PushEventToTracker(uint32_t event, void *p_data, + std::vector *state_ids); + + std::map > SplitContextOnState( + std::vector *streams); + + void ProcessEvent(uint32_t event, void* p_data); + + uint16_t GetConnId(); + + std::list GetCigId(); + + std::list GetCisId(); + + void ReportStreamState (std::vector stream_info); + + RawAddress &GetAddress() { return address; }; + + PacsClient *GetPacsClient() { + return pacs_client; + } + + uint16_t GetPacsClientId() { + return pacs_client_id; + } + + GattPendingData *GetGattPendingData() { + return &gatt_pending_data; + } + + bool UpdatePacsState(ConnectionState state) { + pacs_state = state; + return true; + } + + bool UpdateAscsState(GattState state) { + ascs_state = state; + return true; + } + + ConnectionState GetPacsState() { + return pacs_state; + } + + GattState GetAscsState() { + return ascs_state; + } + + AscsClient *GetAscsClient() { + return ascs_client; + } + + CisInterface *GetCisInterface() { + return cis_intf; + } + + BapAlarm *GetBapAlarm() { + return bap_alarm; + } + + UcastAudioStreams *GetAudioStreams() { + return &audio_streams; + } + + StreamTrackers *GetStreamTrackers() { + return &stream_trackers; + } + + StreamContexts *GetStreamContexts() { + return &stream_contexts; + } + + UcastClientCallbacks *GetUclientCbacks() { + return ucl_callbacks; + } + + void UpdateDevType(DeviceType device_type) { + dev_type = device_type; + } + + DeviceType GetDevType() { + return dev_type; + } + + const char* GetEventName(uint32_t event) { + switch (event) { + CASE_RETURN_STR(BAP_CONNECT_REQ_EVT) + CASE_RETURN_STR(BAP_DISCONNECT_REQ_EVT) + CASE_RETURN_STR(BAP_START_REQ_EVT) + CASE_RETURN_STR(BAP_STOP_REQ_EVT) + CASE_RETURN_STR(BAP_RECONFIG_REQ_EVT) + CASE_RETURN_STR(BAP_STREAM_UPDATE_REQ_EVT) + CASE_RETURN_STR(PACS_CONNECTION_STATE_EVT) + CASE_RETURN_STR(PACS_DISCOVERY_RES_EVT) + CASE_RETURN_STR(PACS_AUDIO_CONTEXT_RES_EVT) + CASE_RETURN_STR(ASCS_CONNECTION_STATE_EVT) + CASE_RETURN_STR(ASCS_DISCOVERY_RES_EVT) + CASE_RETURN_STR(ASCS_ASE_STATE_EVT) + CASE_RETURN_STR(ASCS_ASE_OP_FAILED_EVT) + CASE_RETURN_STR(CIS_GROUP_STATE_EVT) + CASE_RETURN_STR(CIS_STATE_EVT) + CASE_RETURN_STR(BAP_TIME_OUT_EVT) + default: + return "Unknown Event"; + } + } + + private: + RawAddress address; + PacsClient *pacs_client; + uint16_t pacs_client_id; + AscsClient *ascs_client; + ConnectionState pacs_state; + CisInterface *cis_intf; + GattState ascs_state; + StreamOpsQueue ops_queue; + UcastAudioStreams audio_streams; + StreamTrackers stream_trackers; + StreamContexts stream_contexts; + GattPendingData gatt_pending_data; + UcastClientCallbacks* ucl_callbacks; + BapAlarm *bap_alarm; + DeviceType dev_type; +}; + +class UstreamManagers { + public: + UstreamManager* FindByAddress(const RawAddress& address); + + UstreamManager* FindorAddByAddress(const RawAddress& address, + PacsClient *pacs_client, uint16_t pacs_client_id, + AscsClient *ascs_client, CisInterface *cis_intf, + UcastClientCallbacks* callbacks, BapAlarm* bap_alarm); + + + std::vector *GetAllManagers(); + + void Remove(const RawAddress& address); + + size_t size() { return (strm_mgrs.size()); } + + std::vector strm_mgrs; +}; + +} // namespace ucast +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/bap/uclient_alarm.cc b/le_audio/system/bt/bta/bap/uclient_alarm.cc new file mode 100644 index 00000000000..622bfe69071 --- /dev/null +++ b/le_audio/system/bt/bta/bap/uclient_alarm.cc @@ -0,0 +1,85 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +#include "uclient_alarm.h" +#include "bt_trace.h" +#define LOG_TAG "uclient_alarm" + +namespace bluetooth { +namespace bap { +namespace alarm { + +class BapAlarmImpl; +BapAlarmImpl *instance; + +static void alarm_handler(void* data); + +class BapAlarmImpl : public BapAlarm { + public: + BapAlarmImpl(BapAlarmCallbacks* callback): + callbacks(callback) { } + + ~BapAlarmImpl() override = default; + + void CleanUp () { } + + alarm_t* Create(const char* name) { + return alarm_new(name); + } + + void Delete(alarm_t* alarm) { + alarm_free(alarm); + } + + void Start(alarm_t* alarm, period_ms_t interval_ms, + void* data) { + alarm_set_on_mloop(alarm, interval_ms, alarm_handler, data); + } + + void Stop(alarm_t* alarm) { + alarm_cancel(alarm); + } + + bool IsScheduled(const alarm_t* alarm) { + return alarm_is_scheduled(alarm); + } + + void Timeout(void* data) { + if (callbacks) + callbacks->OnTimeout(data); // Call uclient_main + } + + private: + BapAlarmCallbacks *callbacks; +}; + +void BapAlarm::Initialize( + BapAlarmCallbacks* callbacks) { + if (instance) { + LOG(ERROR) << "Already initialized!"; + } else { + instance = new BapAlarmImpl(callbacks); + } +} + +void BapAlarm::CleanUp() { + BapAlarmImpl* ptr = instance; + instance = nullptr; + ptr->CleanUp(); + delete ptr; +} + +BapAlarm* BapAlarm::Get() { + return instance; +} + +static void alarm_handler(void* data) { + if (instance) + instance->Timeout(data); +} + +} //alarm +} //bap +} //bluetooth diff --git a/le_audio/system/bt/bta/bap/uclient_alarm.h b/le_audio/system/bt/bta/bap/uclient_alarm.h new file mode 100644 index 00000000000..b0f0c08fa34 --- /dev/null +++ b/le_audio/system/bt/bta/bap/uclient_alarm.h @@ -0,0 +1,49 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +#pragma once + +#include +#include +#include "osi/include/alarm.h" +#include + +namespace bluetooth { +namespace bap { +namespace alarm { + +class BapAlarmCallbacks { + public: + virtual ~BapAlarmCallbacks() = default; + + /** Callback for timer timeout */ + virtual void OnTimeout(void* data) = 0; +}; + +class BapAlarm { + public: + virtual ~BapAlarm() = default; + + static void Initialize(BapAlarmCallbacks* callbacks); + static void CleanUp(); + static BapAlarm* Get(); + + virtual alarm_t* Create(const char* name) = 0; + + virtual void Delete(alarm_t* alarm) = 0; + + virtual void Start(alarm_t* alarm, period_ms_t interval_ms, + void* data) = 0; + + virtual void Stop(alarm_t* alarm) = 0; + + virtual bool IsScheduled(const alarm_t* alarm) = 0; + + virtual void Timeout(void* data) = 0; +}; + +} //alarm +} //bap +} //bluetooth diff --git a/le_audio/system/bt/bta/bap/uclient_main.cc b/le_audio/system/bt/bta/bap/uclient_main.cc new file mode 100644 index 00000000000..4cd2078f630 --- /dev/null +++ b/le_audio/system/bt/bta/bap/uclient_main.cc @@ -0,0 +1,534 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +#include "bta_bap_uclient_api.h" +#include "ucast_client_int.h" +#include "bta_pacs_client_api.h" +#include "bta_ascs_client_api.h" +#include +#include +#include +#include +#include "bta_closure_api.h" +#include "bt_trace.h" + +namespace bluetooth { +namespace bap { +namespace ucast { + +using base::Bind; +using base::Unretained; +using base::Closure; +using bluetooth::Uuid; + +using bluetooth::bap::pacs::PacsClient; +using bluetooth::bap::pacs::ConnectionState; +using bluetooth::bap::pacs::CodecConfig; +using bluetooth::bap::pacs::PacsClientCallbacks; + +using bluetooth::bap::ascs::AscsClient; +using bluetooth::bap::ascs::GattState; +using bluetooth::bap::ascs::AscsClientCallbacks; +using bluetooth::bap::ascs::AseOpId; +using bluetooth::bap::ascs::AseOpStatus; +using bluetooth::bap::ascs::AseParams; + +using bluetooth::bap::ucast::UstreamManagers; +using bluetooth::bap::ucast::UstreamManager; + +using bluetooth::bap::ucast::BapEventData; +using bluetooth::bap::ucast::BapEvent; +using bluetooth::bap::ucast::BapConnect; +using bluetooth::bap::ucast::BapDisconnect; +using bluetooth::bap::ucast::BapStart; +using bluetooth::bap::ucast::BapStop; +using bluetooth::bap::ucast::BapReconfig; +using bluetooth::bap::ucast::PacsConnectionState; +using bluetooth::bap::ucast::PacsDiscovery; +using bluetooth::bap::ucast::PacsAvailableContexts; + +using bluetooth::bap::ucast::CisGroupState; +using bluetooth::bap::ucast::CisStreamState; +using bluetooth::bap::cis::CigState; +using bluetooth::bap::cis::CisState; +using bluetooth::bap::cis::CisInterface; + +using bluetooth::bap::alarm::BapAlarm; +using bluetooth::bap::alarm::BapAlarmCallbacks; + +class UcastClientImpl; +UcastClientImpl* instance = nullptr; + +class CisInterfaceCallbacksImpl : public CisInterfaceCallbacks { + public: + ~CisInterfaceCallbacksImpl() = default; + /** Callback for connection state change */ + void OnCigState(uint8_t cig_id, CigState state) { + do_in_bta_thread(FROM_HERE, Bind(&CisInterfaceCallbacks::OnCigState, + Unretained(UcastClient::Get()), cig_id, + state)); + + } + + void OnCisState(uint8_t cig_id, uint8_t cis_id, + uint8_t direction, CisState state) { + do_in_bta_thread(FROM_HERE, Bind(&CisInterfaceCallbacks::OnCisState, + Unretained(UcastClient::Get()), cig_id, + cis_id, direction, state)); + } +}; + +class PacsClientCallbacksImpl : public PacsClientCallbacks { + public: + ~PacsClientCallbacksImpl() = default; + void OnInitialized(int status, int client_id) override { + LOG(WARNING) << __func__ << ": status =" << loghex(status); + do_in_bta_thread(FROM_HERE, Bind(&PacsClientCallbacks::OnInitialized, + Unretained(UcastClient::Get()), status, + client_id)); + } + + void OnConnectionState(const RawAddress& address, + bluetooth::bap::pacs::ConnectionState state) override { + LOG(WARNING) << __func__ << ": address=" << address; + do_in_bta_thread(FROM_HERE, Bind(&PacsClientCallbacks::OnConnectionState, + Unretained(UcastClient::Get()), + address, state)); + } + + void OnAudioContextAvailable(const RawAddress& address, + uint32_t available_contexts) override { + do_in_bta_thread(FROM_HERE, + Bind(&PacsClientCallbacks::OnAudioContextAvailable, + Unretained(UcastClient::Get()), + address, available_contexts)); + } + + void OnSearchComplete(int status, const RawAddress& address, + std::vector sink_pac_records, + std::vector src_pac_records, + uint32_t sink_locations, + uint32_t src_locations, + uint32_t available_contexts, + uint32_t supported_contexts) override { + do_in_bta_thread(FROM_HERE, Bind(&PacsClientCallbacks::OnSearchComplete, + Unretained(UcastClient::Get()), + status, address, + sink_pac_records, + src_pac_records, + sink_locations, + src_locations, + available_contexts, + supported_contexts)); + } +}; + +class AscsClientCallbacksImpl : public AscsClientCallbacks { + public: + ~AscsClientCallbacksImpl() = default; + void OnAscsInitialized(int status, int client_id) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClientCallbacks::OnAscsInitialized, + Unretained(UcastClient::Get()), status, + client_id)); + } + + void OnConnectionState(const RawAddress& address, + bluetooth::bap::ascs::GattState state) override { + DVLOG(2) << __func__ << " address: " << address; + do_in_bta_thread(FROM_HERE, Bind(&AscsClientCallbacks::OnConnectionState, + Unretained(UcastClient::Get()), + address, state)); + } + + void OnAseOpFailed(const RawAddress& address, + AseOpId ase_op_id, + std::vector status) { + do_in_bta_thread(FROM_HERE, + Bind(&AscsClientCallbacks::OnAseOpFailed, + Unretained(UcastClient::Get()), + address, ase_op_id, status)); + + } + + void OnAseState(const RawAddress& address, + AseParams ase) override { + do_in_bta_thread(FROM_HERE, + Bind(&AscsClientCallbacks::OnAseState, + Unretained(UcastClient::Get()), + address, ase)); + } + + void OnSearchComplete(int status, const RawAddress& address, + std::vector sink_ase_list, + std::vector src_ase_list) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClientCallbacks::OnSearchComplete, + Unretained(UcastClient::Get()), + status, address, sink_ase_list, + src_ase_list)); + } +}; + +class BapAlarmCallbacksImpl : public BapAlarmCallbacks { + public: + ~BapAlarmCallbacksImpl() = default; + /** Callback for timer timeout */ + void OnTimeout(void* data) { + do_in_bta_thread(FROM_HERE, Bind(&BapAlarmCallbacks::OnTimeout, + Unretained(UcastClient::Get()), data)); + } +}; + +class UcastClientImpl : public UcastClient { + public: + ~UcastClientImpl() override = default; + + // APIs exposed for upper layers + void Connect(std::vector address, bool is_direct, + std::vector streams) override { + if(address.size() == 1) { + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address[0], + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + // hand over the request to stream manager + BapConnect data = { .bd_addr = address, .is_direct = is_direct, + .streams = streams}; + mgr->ProcessEvent(BAP_CONNECT_REQ_EVT, &data); + } + } + + void Disconnect(const RawAddress& address, + std::vector streams) override { + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + + // hand over the request to stream manager + BapDisconnect data = { .bd_addr = address, + .streams = streams}; + mgr->ProcessEvent(BAP_DISCONNECT_REQ_EVT, &data); + } + + void Start(const RawAddress& address, + std::vector streams) override { + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + + // hand over the request to stream manager + BapStart data = { .bd_addr = address, + .streams = streams}; + mgr->ProcessEvent(BAP_START_REQ_EVT, &data); + } + + void Stop(const RawAddress& address, + std::vector streams) override { + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + + // hand over the request to stream manager + BapStop data = { .bd_addr = address, + .streams = streams}; + mgr->ProcessEvent(BAP_STOP_REQ_EVT, &data); + + } + + void Reconfigure(const RawAddress& address, + std::vector streams) override { + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + + // hand over the request to stream manager + BapReconfig data = { .bd_addr = address, + .streams = streams}; + mgr->ProcessEvent(BAP_RECONFIG_REQ_EVT, &data); + } + + void UpdateStream(const RawAddress& address, + std::vector update_streams) override { + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + + // hand over the request to stream manager + BapStreamUpdate data = { .bd_addr = address, + .update_streams = update_streams}; + mgr->ProcessEvent(BAP_STREAM_UPDATE_REQ_EVT, &data); + } + + // To be called from device specific stream manager + bool ReportStreamState(const RawAddress& address) { + //TODO to check + return true; + + } + + // PACS client related callbacks + // to be forwarded to device specific stream manager + void OnInitialized(int status, int client_id) override { + LOG(WARNING) << __func__ << ": actual client_id = " << loghex(client_id); + pacs_client_id = client_id; + } + + void OnConnectionState(const RawAddress& address, + ConnectionState state) override { + LOG(WARNING) << __func__ << ": address=" << address; + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + // hand over the request to stream manager + PacsConnectionState data = { .bd_addr = address, + .state = state + }; + mgr->ProcessEvent(PACS_CONNECTION_STATE_EVT, &data); + } + + void OnAudioContextAvailable(const RawAddress& address, + uint32_t available_contexts) override { + LOG(WARNING) << __func__ << ": address=" << address; + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + // hand over the request to stream manager + PacsAvailableContexts data = { + .bd_addr = address, + .available_contexts = available_contexts, + }; + mgr->ProcessEvent(PACS_AUDIO_CONTEXT_RES_EVT, &data); + } + + void OnSearchComplete(int status, const RawAddress& address, + std::vector sink_pac_records, + std::vector src_pac_records, + uint32_t sink_locations, + uint32_t src_locations, + uint32_t available_contexts, + uint32_t supported_contexts) override { + LOG(WARNING) << __func__ << ": address=" << address; + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + // hand over the request to stream manager + PacsDiscovery data = { + .status = status, + .bd_addr = address, + .sink_pac_records = sink_pac_records, + .src_pac_records = src_pac_records, + .sink_locations = sink_locations, + .src_locations = src_locations, + .available_contexts = available_contexts, + .supported_contexts = supported_contexts + }; + mgr->ProcessEvent(PACS_DISCOVERY_RES_EVT, &data); + } + + // ASCS client related callbacks + // to be forwarded to device specific stream manager + void OnAscsInitialized(int status, int client_id) override { + + } + + void OnConnectionState(const RawAddress& address, + bluetooth::bap::ascs::GattState state) override { + LOG(WARNING) << __func__ << ": address=" << address; + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + // hand over the request to stream manager + AscsConnectionState data = { .bd_addr = address, + .state = state + }; + mgr->ProcessEvent(ASCS_CONNECTION_STATE_EVT, &data); + } + + void OnAseOpFailed(const RawAddress& address, + AseOpId ase_op_id, + std::vector status) { + + LOG(WARNING) << __func__ << ": address=" << address; + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + // hand over the request to stream manager + AscsOpFailed data = { + .bd_addr = address, + .ase_op_id = ase_op_id, + .ase_list = status + }; + mgr->ProcessEvent(ASCS_ASE_OP_FAILED_EVT, &data); + } + + void OnAseState(const RawAddress& address, + AseParams ase_params) override { + LOG(WARNING) << __func__ << ": address=" << address; + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + // hand over the request to stream manager + AscsState data = { + .bd_addr = address, + .ase_params = ase_params + }; + mgr->ProcessEvent(ASCS_ASE_STATE_EVT, &data); + } + + void OnSearchComplete(int status, const RawAddress& address, + std::vector sink_ase_list, + std::vector src_ase_list) override { + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(address, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + // hand over the request to stream manager + AscsDiscovery data = { + .status = status, + .bd_addr = address, + .sink_ases_list = sink_ase_list, + .src_ases_list = src_ase_list + }; + mgr->ProcessEvent(ASCS_DISCOVERY_RES_EVT, &data); + } + + // cis callbacks + void OnCigState(uint8_t cig_id, CigState state) override { + std::vector *mgrs_list = strm_mgrs.GetAllManagers(); + // hand over the request to stream manager + CisGroupState data = { + .cig_id = cig_id, + .state = state + }; + + for (auto it = mgrs_list->begin(); it != mgrs_list->end(); it++) { + (*it)->ProcessEvent(CIS_GROUP_STATE_EVT, &data); + } + } + + void OnCisState(uint8_t cig_id, uint8_t cis_id, uint8_t direction, + CisState state) override { + std::vector *mgrs_list = strm_mgrs.GetAllManagers(); + // hand over the request to stream manager + CisStreamState data = { + .cig_id = cig_id, + .cis_id = cis_id, + .direction = direction, + .state = state + }; + + for (auto it = mgrs_list->begin(); it != mgrs_list->end(); it++) { + (*it)->ProcessEvent(CIS_STATE_EVT, &data); + } + } + + void OnTimeout(void* data) override { + LOG(ERROR) << __func__; + BapTimeout* data_ = (BapTimeout *)data; + UstreamManager *mgr = strm_mgrs.FindorAddByAddress(data_->bd_addr, + pacs_client, pacs_client_id, + ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + // hand over the request to stream manager + mgr->ProcessEvent(BAP_TIME_OUT_EVT, data); + } + + bool Init(UcastClientCallbacks *callback) { + // register callbacks with CIS, ASCS client, PACS client + pacs_callbacks = new PacsClientCallbacksImpl; + PacsClient::Initialize(pacs_callbacks); + pacs_client = PacsClient::Get(); + + ascs_callbacks = new AscsClientCallbacksImpl; + AscsClient::Init(ascs_callbacks); + ascs_client = AscsClient::Get(); + + cis_callbacks = new CisInterfaceCallbacksImpl; + CisInterface::Initialize(cis_callbacks); + cis_intf = CisInterface::Get(); + + bap_alarm_cb = new BapAlarmCallbacksImpl; + BapAlarm::Initialize(bap_alarm_cb); + bap_alarm = BapAlarm::Get(); + + pacs_client_id = 0; + if(ucl_callbacks != nullptr) { + // flag an error + return false; + } else { + ucl_callbacks = callback; + return true; + } + } + + bool CleanUp() { + if(ucl_callbacks != nullptr) { + ucl_callbacks = nullptr; + //call clean ups for each clients(ascs, pacs, cis and bap_alarm) + LOG(ERROR) << __func__ + <<": Cleaning up pacs, ascs clients, cis intf and bap_alarm."; + pacs_client->CleanUp(pacs_client_id); + ascs_client->CleanUp(0x01); + cis_intf->CleanUp(); + bap_alarm->CleanUp(); + pacs_client = nullptr; + ascs_client = nullptr; + cis_intf = nullptr; + bap_alarm = nullptr; + // remove all stream managers and other clean ups + return true; + } else { + return false; + } + } + + private: + UcastClientCallbacks* ucl_callbacks; + UstreamManagers strm_mgrs; + PacsClient *pacs_client; + AscsClient *ascs_client; + PacsClientCallbacks *pacs_callbacks; + AscsClientCallbacks *ascs_callbacks; + CisInterface *cis_intf; + CisInterfaceCallbacks *cis_callbacks; + uint16_t pacs_client_id; + BapAlarm* bap_alarm; + BapAlarmCallbacks* bap_alarm_cb; +}; + +void UcastClient::Initialize(UcastClientCallbacks* callbacks) { + if (!instance) { + instance = new UcastClientImpl(); + instance->Init(callbacks); + } else { + LOG(ERROR) << __func__ << " 2nd client registration ignored"; + } +} + +void UcastClient::CleanUp() { + if(instance && instance->CleanUp()) { + delete instance; + instance = nullptr; + } +} + +UcastClient* UcastClient::Get() { + CHECK(instance); + return instance; +} + +} // namespace ucast +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/bap/uclient_strm_mgr.cc b/le_audio/system/bt/bta/bap/uclient_strm_mgr.cc new file mode 100644 index 00000000000..5c1be806d95 --- /dev/null +++ b/le_audio/system/bt/bta/bap/uclient_strm_mgr.cc @@ -0,0 +1,999 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +#include "bta_bap_uclient_api.h" +#include "ucast_client_int.h" +#include "bt_trace.h" + +namespace bluetooth { +namespace bap { +namespace ucast { + +using namespace std; +using bluetooth::bap::ucast::UstreamManagers; +using bluetooth::bap::ucast::UstreamManager; + +std::map state_map = { + {StreamState::DISCONNECTED, StreamTracker::kStateIdle} , + {StreamState::CONNECTING, StreamTracker::kStateConnecting}, + {StreamState::CONNECTED, StreamTracker::kStateConnected}, + {StreamState::STARTING, StreamTracker::kStateStarting}, + {StreamState::STREAMING, StreamTracker::kStateStreaming}, + {StreamState::STOPPING, StreamTracker::kStateStopping}, + {StreamState::DISCONNECTING, StreamTracker::kStateDisconnecting}, + {StreamState::RECONFIGURING, StreamTracker::kStateReconfiguring} +}; + +StreamContext *StreamContexts::FindByType(StreamType stream_type) { + auto iter = std::find_if(strm_contexts.begin(), strm_contexts.end(), + [&stream_type](StreamContext *context) { + return ((context->stream_type.type == stream_type.type) + && (context->stream_type.direction == + stream_type.direction)); + }); + + if (iter == strm_contexts.end()) { + return nullptr; + } else { + return (*iter); + } +} + +std::vector StreamContexts::FindByAseAttachedState( + uint16_t ase_id, StreamAttachedState state) { + std::vector contexts_list; + for(auto i = strm_contexts.begin(); i != strm_contexts.end();i++) { + if(static_cast((*i)->attached_state) & + static_cast(state)) { + for(auto j = (*i)->stream_ids.begin(); j != (*i)->stream_ids.end();j++) { + if(j->ase_id == ase_id) { + contexts_list.push_back(*i); + break; + } + } + } + } + return contexts_list; +} + +StreamContext *StreamContexts::FindOrAddByType(StreamType stream_type) { + auto iter = std::find_if(strm_contexts.begin(), strm_contexts.end(), + [&stream_type](StreamContext *context) { + return ((context->stream_type.type == + stream_type.type) && + (context->stream_type.direction == + stream_type.direction)); + }); + + if (iter == strm_contexts.end()) { + StreamContext *context = new StreamContext(stream_type); + strm_contexts.push_back(context); + return context; + } else { + return (*iter); + } +} + +void StreamContexts::Remove(StreamType stream_type) { + for (auto it = strm_contexts.begin(); it != strm_contexts.end();) { + if (((*it)->stream_type.type = stream_type.type) && + ((*it)->stream_type.direction = stream_type.direction)) { + delete(*it); + it = strm_contexts.erase(it); + } else { + it++; + } + } +} + +bool StreamContexts::IsAseAttached(StreamType stream_type) { + return false; + +} + +StreamContext* StreamContexts::FindByAseId(uint16_t ase_id) { + auto iter = std::find_if(strm_contexts.begin(), strm_contexts.end(), + [&ase_id](StreamContext *context) { + auto it = std::find_if(context->stream_ids.begin(), + context->stream_ids.end(), + [&ase_id](StreamIdType id) { + return (id.ase_id == ase_id); + }); + if (it != context->stream_ids.end()) { + return true; + } else return false; + + }); + + if (iter == strm_contexts.end()) { + return nullptr; + } else { + return (*iter); + } +} + +StreamTracker* StreamTrackers::FindOrAddByType(int init_state_id, + UstreamManager *strm_mgr, + std::vector *connect_streams, + std::vector *reconfig_streams, + std::vector *streams, + StreamControlType ops_type) { + bool found = false; + auto iter = stream_trackers.begin(); + while (iter != stream_trackers.end() && !found) { + if((*iter)->GetControlType() == ops_type) { + if(ops_type == StreamControlType::Connect) { + // compare connection streams + std::vector *conn_strms = (*iter)->GetConnStreams(); + if(conn_strms->size() == connect_streams->size()) { + uint8_t len = connect_streams->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamConnect src = conn_strms->at(i); + StreamConnect dst = connect_streams->at(i); + if((src.stream_type.type == dst.stream_type.type) && + (src.stream_type.direction == dst.stream_type.direction)) { + LOG(WARNING) << __func__ << " StreamConnect found"; + found = true; + break; + } + } + } + } else if(ops_type == StreamControlType::Reconfig) { + // compare connection streams + std::vector *reconf_strms = (*iter)->GetReconfStreams(); + if(reconf_strms->size() == reconfig_streams->size()) { + uint8_t len = reconfig_streams->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamReconfig src = reconf_strms->at(i); + StreamReconfig dst = reconfig_streams->at(i); + if((src.stream_type.type == dst.stream_type.type) && + (src.stream_type.direction == dst.stream_type.direction)) { + LOG(WARNING) << __func__ << " StreamReconfig found"; + found = true; + break; + } + } + } + } else if(ops_type != StreamControlType::None && + ops_type != StreamControlType::UpdateStream) { + // compare connection streams + std::vector *strms = (*iter)->GetStreams(); + if(strms->size() == streams->size()) { + uint8_t len = streams->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamType src = strms->at(i); + StreamType dst = streams->at(i); + if((src.type == dst.type) && + (src.direction == dst.direction)) { + LOG(WARNING) << __func__ << " StreamType found"; + found = true; + break; + } + } + } + } + } + iter++; + } + + if (iter == stream_trackers.end()) { + StreamTracker *tracker = new StreamTracker(init_state_id, strm_mgr, + connect_streams, reconfig_streams, + streams, ops_type); + stream_trackers.push_back(tracker); + return tracker; + } else { + return (*iter); + } +} + +bool StreamTrackers::Remove(std::vector streams, + StreamControlType ops_type) { + return true; +} + +std::vector StreamTrackers::GetTrackersByStates( + std::vector *state_ids) { + vector trackers; + for (auto i = stream_trackers.begin(); + i != stream_trackers.end();i++) { + for (auto j = state_ids->begin(); j != state_ids->end();j++) { + if((*i)->StateId() == *j) { + LOG(WARNING) << __func__ << " tracker found"; + trackers.push_back(*i); + } + } + } + return trackers; +} + +void StreamTrackers::RemoveByStates(std::vector state_ids) { + for (auto i = stream_trackers.begin(); + i != stream_trackers.end();) { + bool found = false; + for (auto j = state_ids.begin(); j != state_ids.end();j++) { + if((*i)->StateId() == *j) { + LOG(WARNING) << __func__ << " tracker found"; + found = true; + break; + } + } + if(found) { + delete(*i); + i = stream_trackers.erase(i); + } else { + i++; + } + } +} + + +std::map > + StreamTrackers::GetTrackersByType( + std::vector *streams) { + std::vector req_types = *streams; + std::vector all_types; + std::map > tracker_and_type_map; + for (auto iter = stream_trackers.begin(); iter != stream_trackers.end(); + iter++) { + all_types.clear(); + if((*iter)->GetControlType() == StreamControlType::Connect) { + // compare connection streams + std::vector *conn_strms = (*iter)->GetConnStreams(); + uint8_t len = conn_strms->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamConnect src = conn_strms->at(i); + all_types.push_back(src.stream_type); + } + } else if((*iter)->GetControlType() == StreamControlType::Reconfig) { + // compare connection streams + std::vector *reconf_strms = (*iter)->GetReconfStreams(); + uint8_t len = reconf_strms->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamReconfig src = reconf_strms->at(i); + all_types.push_back(src.stream_type); + } + } else if((*iter)->GetControlType() == StreamControlType::UpdateStream) { + // compare connection streams + std::vector *update_streams = (*iter)->GetMetaUpdateStreams(); + uint8_t len = update_streams->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamUpdate src = update_streams->at(i); + all_types.push_back(src.stream_type); + } + } else if((*iter)->GetControlType() != StreamControlType::None) { + // compare connection streams + std::vector *strms = (*iter)->GetStreams(); + uint8_t len = strms->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamType src = strms->at(i); + all_types.push_back(src); + } + } + uint8_t count = 0; + std::vector filtered_types; + if(all_types.size() <= req_types.size()) { + filtered_types.clear(); + for (auto it = all_types.begin(); it != all_types.end(); it++) { + for (auto it_2 = req_types.begin(); it_2 != req_types.end();) { + if (((it_2)->type == it->type) && + ((it_2)->direction == it->direction)) { + filtered_types.push_back(*it_2); + tracker_and_type_map[*iter] = filtered_types; + it_2 = req_types.erase(it_2); + count++; + } else { + it_2++; + } + } + } + if(all_types.size() != count) { + LOG(ERROR) << __func__ << " invalid request"; + } + } else { + LOG(ERROR) << __func__ << " invalid request"; + } + } + if(req_types.size()) { + tracker_and_type_map[nullptr] = req_types; + } + return tracker_and_type_map; +} + + +StreamTracker *StreamTrackers::FindByStreamsType( + std::vector *streams) { + bool found = false; + auto iter = stream_trackers.begin(); + while (iter != stream_trackers.end()) { + if((*iter)->GetControlType() == StreamControlType::Connect) { + // compare connection streams + std::vector *conn_strms = (*iter)->GetConnStreams(); + if(conn_strms->size() == streams->size()) { + uint8_t len = streams->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamConnect src = conn_strms->at(i); + StreamType dst = streams->at(i); + if((src.stream_type.type == dst.type) && + (src.stream_type.direction == dst.direction)) { + LOG(WARNING) << __func__ << " StreamConnect found"; + found = true; + break; + } + } + } + } else if((*iter)->GetControlType() == StreamControlType::Reconfig) { + // compare connection streams + std::vector *reconf_strms = (*iter)->GetReconfStreams(); + if(reconf_strms->size() == streams->size()) { + uint8_t len = streams->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamReconfig src = reconf_strms->at(i); + StreamType dst = streams->at(i); + if((src.stream_type.type == dst.type) && + (src.stream_type.direction == dst.direction)) { + LOG(WARNING) << __func__ << " StreamReconfig found"; + found = true; + break; + } + } + } + } else if((*iter)->GetControlType() == StreamControlType::UpdateStream) { + // compare connection streams + std::vector *update_strms = (*iter)->GetMetaUpdateStreams(); + if(update_strms->size() == streams->size()) { + uint8_t len = streams->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamUpdate src = update_strms->at(i); + StreamType dst = streams->at(i); + if((src.stream_type.type == dst.type) && + (src.stream_type.direction == dst.direction)) { + LOG(WARNING) << __func__ << " StreamUpdate found"; + found = true; + break; + } + } + } + } else if((*iter)->GetControlType() != StreamControlType::None) { + // compare connection streams + std::vector *strms = (*iter)->GetStreams(); + if(strms->size() == streams->size()) { + uint8_t len = streams->size(); + for (uint8_t i = 0; i < len ; i++) { + StreamType src = strms->at(i); + StreamType dst = streams->at(i); + if((src.type == dst.type) && + (src.direction == dst.direction)) { + LOG(WARNING) << __func__ << " StreamType found"; + found = true; + break; + } + } + } + } + if(found) break; + iter++; + } + + if (iter == stream_trackers.end()) { + return nullptr; + } else { + return (*iter); + } +} + +bool StreamTrackers::ChangeOpType( StreamType stream_type, + StreamControlType new_ops_type) { + return true; +} + +bool StreamTrackers::IsStreamTrackerValid(StreamTracker* tracker, + std::vector *state_ids) { + vector trackers_ = GetTrackersByStates(state_ids); + LOG(WARNING) << __func__; + if(trackers_.empty()) return false; + + for (auto it = trackers_.begin(); it != trackers_.end(); it++) { + if ((*it) == tracker) { + LOG(WARNING) << __func__ <<": Cached Tracker is valid"; + return true; + } + } + return false; +} + +bool UstreamManager::PushEventToTracker(uint32_t event, void *p_data, + std::vector *state_ids) { + vector trackers = stream_trackers.GetTrackersByStates( + state_ids); + if(trackers.empty()) return false; + + for (auto it = trackers.begin(); it != trackers.end(); it++) { + (*it)->ProcessEvent(event, p_data); + } + return true; +} + + +std::map > UstreamManager::SplitContextOnState( + std::vector *streams) { + StreamContexts *contexts = GetStreamContexts(); + std::vector req_types = *streams; + std::map > state_and_type_map; + + for (auto it = req_types.begin(); it != req_types.end();) { + StreamContext *context = contexts->FindOrAddByType(*it); + if (context) { + int state = state_map[context->stream_state]; + state_and_type_map[state].push_back(*it); + it = req_types.erase(it); + } else { + it++; + } + } + return state_and_type_map; +} + +void UstreamManager::ProcessEvent(uint32_t event, void *p_data) { + LOG(WARNING) << __func__ <<": Event: " << GetEventName(event) + <<", bt_addr: " << GetAddress(); + + std::vector stable_state_ids = { + StreamTracker::kStateConnected, + StreamTracker::kStateStreaming, + StreamTracker::kStateIdle + }; + + std::vector transient_state_ids = { + StreamTracker::kStateConnecting, + StreamTracker::kStateReconfiguring, + StreamTracker::kStateDisconnecting, + StreamTracker::kStateStarting, + StreamTracker::kStateStopping, + StreamTracker::kStateUpdating, + }; + StreamContexts *contexts = GetStreamContexts(); + + switch (event) { + + case BAP_CONNECT_REQ_EVT: { + BapConnect *evt_data = (BapConnect *) p_data; + std::vector conn_streams = evt_data->streams; + LOG(WARNING) << __func__ << ": size: " << conn_streams.size(); + + for (auto it = conn_streams.begin(); it != conn_streams.end();) { + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + if(context && context->stream_state != StreamState::DISCONNECTED) { + LOG(WARNING) << __func__ << ": Stream is not in disconnected state"; + it = conn_streams.erase(it); + } else { + it++; + } + } + + if(!conn_streams.size()) { + LOG(ERROR) << __func__ << ": All streams are not in disconnected state"; + break; + } + + // validate the combinations media Tx or voice TX|RX + StreamTracker *tracker = stream_trackers.FindOrAddByType( + StreamTracker::kStateIdle, + this, &conn_streams, nullptr, nullptr, + StreamControlType::Connect); + + if(tracker) { + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } + } break; + + case BAP_DISCONNECT_REQ_EVT: { + BapDisconnect *evt_data = (BapDisconnect *) p_data; + std::vector disc_streams = evt_data->streams; + BapDisconnect int_evt_data; + + for (auto it = disc_streams.begin(); it != disc_streams.end();) { + StreamContext *context = contexts->FindOrAddByType(*it); + if(!context || context->stream_state == StreamState::DISCONNECTED) { + LOG(WARNING) << __func__ << " Stream is already disconnected"; + it = disc_streams.erase(it); + } else { + it++; + } + } + + if(!disc_streams.size()) { + LOG(ERROR) << __func__ << " All streams are already disconnected"; + break; + } + + LOG(WARNING) << __func__ << " disc streams size " << disc_streams.size(); + + // validate the combinations media Tx or voice TX|RX + std::map > + tracker_and_type_list = + stream_trackers.GetTrackersByType(&disc_streams); + + + // check if all streams disconnection or subset of streams + // create the new tracker + for (auto itr = tracker_and_type_list.begin(); + itr != tracker_and_type_list.end(); itr++) { + if(itr->first == nullptr) { + std::map > + list = SplitContextOnState(&itr->second); + for (auto itr_2 = list.begin(); itr_2 != list.end(); itr_2++) { + StreamTracker *tracker = stream_trackers.FindOrAddByType( + itr_2->first, + this, nullptr, nullptr, &itr_2->second, + StreamControlType::Disconnect); + LOG(ERROR) << __func__ << " new tracker start "; + tracker->Start(); + int_evt_data.streams = itr_2->second; + tracker->ProcessEvent(event, &int_evt_data); + } + } else { + LOG(ERROR) << __func__ << " existing tracker start "; + StreamTracker *tracker = itr->first; + int_evt_data.streams = itr->second; + tracker->ProcessEvent(event, &int_evt_data); + } + } + } break; + + case BAP_START_REQ_EVT: { + BapStart *evt_data = (BapStart *) p_data; + std::vector start_streams = evt_data->streams; + LOG(WARNING) << __func__ << " start streams size " << start_streams.size(); + + for (auto it = start_streams.begin(); it != start_streams.end();) { + StreamContext *context = contexts->FindOrAddByType(*it); + if(!context || context->stream_state != StreamState::CONNECTED) { + LOG(WARNING) << __func__ << " Stream is not in connected state"; + it = start_streams.erase(it); + } else { + it++; + } + } + + if(!start_streams.size()) { + LOG(WARNING) << __func__ << " Not eligible for stream start"; + break; + } + + // validate the combinations media Tx or voice TX|RX + StreamTracker *tracker = stream_trackers.FindByStreamsType( + &start_streams); + // create new tracker + if(tracker == nullptr) { + StreamTracker *tracker = stream_trackers.FindOrAddByType( + StreamTracker::kStateConnected, + this, nullptr, nullptr, &start_streams, + StreamControlType::Start); + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } else { + tracker->ProcessEvent(event, p_data); + } + } break; + + case BAP_STOP_REQ_EVT: { + BapStop *evt_data = (BapStop *) p_data; + std::vector stop_streams = evt_data->streams; + LOG(WARNING) << __func__ << " stop streams size " << stop_streams.size(); + + for (auto it = stop_streams.begin(); it != stop_streams.end();) { + StreamContext *context = contexts->FindOrAddByType(*it); + if(!context || (context->stream_state != StreamState::STREAMING && + context->stream_state != StreamState::STARTING)) { + LOG(WARNING) << __func__ << " Stream is not in streaming state"; + it = stop_streams.erase(it); + } else { + it++; + } + } + + if(!stop_streams.size()) { + LOG(WARNING) << __func__ << " Not eligible for stream stop"; + break; + } + + StreamTracker *tracker = stream_trackers.FindByStreamsType( + &stop_streams); + // create the new tracker + if(tracker == nullptr) { + // validate the combinations media Tx or voice TX|RX + StreamTracker *tracker = stream_trackers.FindOrAddByType( + StreamTracker::kStateStreaming, + this, nullptr, nullptr, &stop_streams, + StreamControlType::Stop); + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } else { + tracker->ProcessEvent(event, p_data); + } + } break; + + case BAP_STREAM_UPDATE_REQ_EVT: { + BapStreamUpdate *evt_data = (BapStreamUpdate *) p_data; + std::vector update_streams = evt_data->update_streams; + std::vector streams; + + for (auto it = update_streams.begin(); it != update_streams.end();) { + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + if(!context || (context->stream_state != StreamState::STREAMING && + context->stream_state != StreamState::STARTING)) { + LOG(WARNING) << __func__ << " Stream is not in proper state"; + it = update_streams.erase(it); + } else { + it++; + } + } + + if(!update_streams.size()) { + LOG(WARNING) << __func__ << " All streams are not in proper state"; + break; + } + + for (auto it = update_streams.begin(); + it != update_streams.end(); it++) { + StreamType type = it->stream_type; + streams.push_back(type); + } + + LOG(WARNING) << __func__ << " update streams size " << streams.size(); + + StreamTracker *tracker = stream_trackers.FindByStreamsType( + &streams); + // create the new tracker + if(tracker == nullptr) { + // validate the combinations media Tx or voice TX|RX + StreamTracker *tracker = stream_trackers.FindOrAddByType( + StreamTracker::kStateStreaming, + this, nullptr, nullptr, nullptr, + StreamControlType::UpdateStream); + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } else { + tracker->ProcessEvent(event, p_data); + } + } break; + + case BAP_RECONFIG_REQ_EVT: { + BapReconfig *evt_data = (BapReconfig *) p_data; + std::vector reconf_streams = evt_data->streams; + std::vector streams; + + for (auto it = reconf_streams.begin(); it != reconf_streams.end();) { + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + if(!context || context->stream_state != StreamState::CONNECTED) { + LOG(WARNING) << __func__ << " Stream is not in Connected state"; + it = reconf_streams.erase(it); + } else { + it++; + } + } + + if(!reconf_streams.size()) { + LOG(WARNING) << __func__ << " All streams are not connected"; + break; + } + + for (auto it = reconf_streams.begin(); + it != reconf_streams.end(); it++) { + StreamType type = it->stream_type; + streams.push_back(type); + } + + LOG(WARNING) << __func__ << " reconf streams size " << streams.size(); + + StreamTracker *tracker = stream_trackers.FindByStreamsType( + &streams); + // create the new tracker + if(tracker == nullptr) { + // validate the combinations media Tx or voice TX|RX + StreamTracker *tracker = stream_trackers.FindOrAddByType( + StreamTracker::kStateConnected, + this, nullptr, &reconf_streams, nullptr, + StreamControlType::Reconfig); + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } else { + tracker->ProcessEvent(event, p_data); + } + } break; + + case PACS_CONNECTION_STATE_EVT: { + PacsConnectionState *pacs_state = (PacsConnectionState *) p_data; + UpdatePacsState(pacs_state->state); + int state = StreamTracker::kStateIdle; + if (pacs_state->state != ConnectionState::DISCONNECTED) { + if(PushEventToTracker(event, p_data, &transient_state_ids)) { + break; + } else { + + std::vector *context_list = contexts->GetAllContexts(); + std::vector streams; + + for (auto it = context_list->begin(); it != context_list->end(); it++) { + if((*it)->stream_state != StreamState::DISCONNECTED) { + streams.push_back((*it)->stream_type); + state = state_map[(*it)->stream_state]; + } + } + + StreamTracker *tracker = stream_trackers.FindByStreamsType( + &streams); + // create the new tracker + if(tracker == nullptr) { + // validate the combinations media Tx or voice TX|RX + StreamTracker *tracker = stream_trackers.FindOrAddByType( + state, + this, nullptr, nullptr, &streams, + StreamControlType::Disconnect); + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } else { + tracker->ProcessEvent(event, p_data); + } + } + } else { + + std::vector *context_list = contexts->GetAllContexts(); + std::vector disc_streams; + + for (auto it = context_list->begin(); it != context_list->end(); it++) { + if((*it)->stream_state != StreamState::DISCONNECTED) { + disc_streams.push_back((*it)->stream_type); + } + } + + LOG(WARNING) << __func__ << ": size: " << disc_streams.size(); + + // validate the combinations media Tx or voice TX|RX + std::map > + tracker_and_type_list = + stream_trackers.GetTrackersByType(&disc_streams); + + // check if all streams disconnection or subset of streams + // create the new tracker + for (auto itr = tracker_and_type_list.begin(); + itr != tracker_and_type_list.end(); itr++) { + if(itr->first == nullptr) { + std::map > + list = SplitContextOnState(&itr->second); + for (auto itr_2 = list.begin(); itr_2 != list.end(); itr_2++) { + StreamTracker *tracker = stream_trackers.FindOrAddByType( + itr_2->first, + this, nullptr, nullptr, &itr_2->second, + StreamControlType::Disconnect); + LOG(ERROR) << __func__ << " new tracker start "; + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } + } else { + LOG(ERROR) << __func__ << " existing tracker start "; + StreamTracker *tracker = itr->first; + tracker->ProcessEvent(event, p_data); + } + } + } + } break; + + case PACS_AUDIO_CONTEXT_RES_EVT: { + // update the same event to upper layers also + PacsAvailableContexts *contexts = (PacsAvailableContexts *) p_data; + ucl_callbacks->OnStreamAvailable( + address, + (contexts->available_contexts >> 16), + (contexts->available_contexts & 0xFFFF)); + std::vector state_ids = { StreamTracker::kStateStarting, + StreamTracker::kStateUpdating}; + PushEventToTracker(event, p_data, &state_ids); + } break; + + case PACS_DISCOVERY_RES_EVT: + case ASCS_DISCOVERY_RES_EVT: { + // validate the combinations media Tx or voice TX|RX + std::vector state_ids = { StreamTracker::kStateConnecting, + StreamTracker::kStateReconfiguring }; + PushEventToTracker(event, p_data, &state_ids); + } break; + + case ASCS_CONNECTION_STATE_EVT: { + AscsConnectionState *ascs_state = (AscsConnectionState *) p_data; + UpdateAscsState(ascs_state->state); + PushEventToTracker(event, p_data, &transient_state_ids); + } break; + + case ASCS_ASE_OP_FAILED_EVT: { + if(PushEventToTracker(event, p_data, &transient_state_ids)) { + break; + } + } break; + + case ASCS_ASE_STATE_EVT: { + if(PushEventToTracker(event, p_data, &transient_state_ids)) { + break; + } + // create strm trackers based on ase ID and push it to + // newly created tracker. + // TODO Handle Releasing , disabling, codec configured states + // initiated from remote device + + AscsState *ascs = ((AscsState *) p_data); + UcastAudioStream *audio_stream = nullptr; + // find out the stream for the given ase id + uint8_t ase_id = ascs->ase_params.ase_id; + std::vector streams; + int state = StreamTracker::kStateIdle; + + UcastAudioStreams *audio_strms = GetAudioStreams(); + audio_stream = audio_strms->FindByAseId(ase_id); + if(audio_stream) { + StreamType type = { + .type = audio_stream->audio_context, + .direction = audio_stream->direction + }; + streams.push_back(type); + state = audio_stream->overall_state; + } + + LOG(WARNING) << __func__ << " size " << streams.size(); + + if (ascs->ase_params.ase_state == ascs::ASE_STATE_RELEASING) { + + StreamTracker *tracker = stream_trackers.FindOrAddByType( + state, + this, nullptr, nullptr, &streams, + StreamControlType::Disconnect); + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } else if (ascs->ase_params.ase_state == ascs::ASE_STATE_DISABLING) { + + StreamTracker *tracker = stream_trackers.FindOrAddByType( + state, + this, nullptr, nullptr, &streams, + StreamControlType::Stop); + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } else if (ascs->ase_params.ase_state == + ascs::ASE_STATE_CODEC_CONFIGURED) { + // TODO reconfig from remote device + } else if (ascs->ase_params.ase_state == + ascs::ASE_STATE_QOS_CONFIGURED) { + // this can happen when CIS is lost and detected on remote side + // first so it will immediately transition to QOS configured. + StreamTracker *tracker = stream_trackers.FindOrAddByType( + state, + this, nullptr, nullptr, &streams, + StreamControlType::Stop); + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } + } break; + + case CIS_GROUP_STATE_EVT: { + // validate the combinations media Tx or voice TX|RX + std::vector state_ids = { StreamTracker::kStateStarting, + StreamTracker::kStateStopping, + StreamTracker::kStateDisconnecting }; + PushEventToTracker(event, p_data, &state_ids); + } break; + + case CIS_STATE_EVT: { + CisStreamState *data = (CisStreamState *) p_data; + + if(PushEventToTracker(event, p_data, &transient_state_ids)) { + break; + } + + // find out the stream for the given ase id + int state = StreamTracker::kStateIdle; + std::vector streams; + + UcastAudioStreams *audio_strms = GetAudioStreams(); + std::vector cis_streams = audio_strms->FindByCisId( + data->cig_id, data->cis_id); + + + if(cis_streams.empty()) break; + + for (auto it = cis_streams.begin(); it != cis_streams.end(); it++) { + StreamType type = { + .type = (*it)->audio_context, + .direction = (*it)->direction + }; + streams.push_back(type); + state = (*it)->overall_state; + } + + LOG(WARNING) << __func__ << " size " << streams.size(); + + // create strm trackers based on CIS ID and push it to + // newly created tracker. + // CIS disconnected + if(data->state == CisState::READY) { + StreamTracker *tracker = stream_trackers.FindOrAddByType( + state, + this, nullptr, nullptr, &streams, + StreamControlType::Stop); + tracker->Start(); + tracker->ProcessEvent(event, p_data); + } + } break; + + case BAP_TIME_OUT_EVT: { + BapTimeout* evt_data = (BapTimeout*) p_data; + + //Get Cached tracker and check if it is valid + if (stream_trackers.IsStreamTrackerValid(evt_data->tracker, + &transient_state_ids)) { + PushEventToTracker(event, p_data, &transient_state_ids); + } else { + LOG(WARNING) << __func__ << ": Tracker is not valid "; + } + } break; + + default: + break; + } + + // look for all stream trackers which are moved to stable states + // like connected, streaming, idle, and destroy those trackers here + stream_trackers.RemoveByStates(stable_state_ids); +} + +// TODO to introduce a queue for serializing the Audio stream establishment +UstreamManager* UstreamManagers::FindByAddress(const RawAddress& address) { + auto iter = std::find_if(strm_mgrs.begin(), strm_mgrs.end(), + [&address](UstreamManager *strm_mgr) { + return strm_mgr->GetAddress() == address; + }); + + return (iter == strm_mgrs.end()) ? nullptr : (*iter); +} + +UstreamManager* UstreamManagers::FindorAddByAddress(const RawAddress& address, + PacsClient *pacs_client, uint16_t pacs_client_id, + AscsClient *ascs_client, CisInterface *cis_intf, + UcastClientCallbacks* ucl_callbacks, + BapAlarm *bap_alarm) { + auto iter = std::find_if(strm_mgrs.begin(), strm_mgrs.end(), + [&address](UstreamManager *strm_mgr) { + return strm_mgr->GetAddress() == address; + }); + + if (iter == strm_mgrs.end()) { + UstreamManager *strm_mgr = new UstreamManager(address, pacs_client, + pacs_client_id, ascs_client, cis_intf, + ucl_callbacks, bap_alarm); + strm_mgrs.push_back(strm_mgr); + return strm_mgr; + } else { + return (*iter); + } +} + +std::vector *UstreamManagers::GetAllManagers() { + return &strm_mgrs; + +} + +void UstreamManagers::Remove(const RawAddress& address) { + for (auto it = strm_mgrs.begin(); it != strm_mgrs.end();) { + if ((*it)->GetAddress() == address) { + delete(*it); + it = strm_mgrs.erase(it); + } else { + it++; + } + } +} + +} // namespace ucast +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/bap/uclient_strm_tracker.cc b/le_audio/system/bt/bta/bap/uclient_strm_tracker.cc new file mode 100644 index 00000000000..7df7f5aaa4b --- /dev/null +++ b/le_audio/system/bt/bta/bap/uclient_strm_tracker.cc @@ -0,0 +1,3987 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +#include "bta_bap_uclient_api.h" +#include "ucast_client_int.h" +#include "bt_trace.h" +#include "btif/include/btif_bap_codec_utils.h" +#include "osi/include/properties.h" +#include "uclient_alarm.h" + +namespace bluetooth { +namespace bap { +namespace ucast { + +using bluetooth::bap::pacs::CodecIndex; +using bluetooth::bap::pacs::CodecBPS; +using bluetooth::bap::pacs::CodecConfig; +using bluetooth::bap::pacs::ConnectionState; +using bluetooth::bap::pacs::CodecSampleRate; +using bluetooth::bap::pacs::CodecChannelMode; +using bluetooth::bap::pacs::PacsClient; +using bluetooth::bap::cis::CisInterface; +using bluetooth::bap::ascs::AseCodecConfigOp; +using bluetooth::bap::ascs::AseQosConfigOp; +using bluetooth::bap::ascs::AseEnableOp; +using bluetooth::bap::ascs::AseStartReadyOp; +using bluetooth::bap::ascs::AseStopReadyOp; +using bluetooth::bap::ascs::AseDisableOp; +using bluetooth::bap::ascs::AseReleaseOp; +using bluetooth::bap::ascs::AseUpdateMetadataOp; + +using cis::IsoHciStatus; +using bluetooth::bap::alarm::BapAlarm; + +using bluetooth::bap::ucast::CONTENT_TYPE_MEDIA; +using bluetooth::bap::ucast::CONTENT_TYPE_CONVERSATIONAL; +using bluetooth::bap::ucast::CONTENT_TYPE_UNSPECIFIED; +using bluetooth::bap::ucast::CONTENT_TYPE_GAME; + +constexpr uint8_t LTV_TYPE_SAMP_FREQ = 0X01; +constexpr uint8_t LTV_TYPE_FRAME_DUR = 0x02; +constexpr uint8_t LTV_TYPE_CHNL_ALLOC = 0x03; +constexpr uint8_t LTV_TYPE_OCTS_PER_FRAME = 0X04; +constexpr uint8_t LTV_TYPE_FRAMES_PER_SDU = 0X05; +constexpr uint8_t LTV_TYPE_STRM_AUDIO_CONTEXTS = 0x02; + +constexpr uint8_t LTV_LEN_SAMP_FREQ = 0X02; +constexpr uint8_t LTV_LEN_FRAME_DUR = 0x02; +constexpr uint8_t LTV_LEN_CHNL_ALLOC = 0x05; +constexpr uint8_t LTV_LEN_OCTS_PER_FRAME = 0X03; +constexpr uint8_t LTV_LEN_FRAMES_PER_SDU = 0X02; +constexpr uint8_t LTV_LEN_STRM_AUDIO_CONTEXTS = 0x03; + +constexpr uint8_t LTV_VAL_SAMP_FREQ_8K = 0X01; +//constexpr uint8_t LTV_VAL_SAMP_FREQ_11K = 0X02; +constexpr uint8_t LTV_VAL_SAMP_FREQ_16K = 0X03; +//constexpr uint8_t LTV_VAL_SAMP_FREQ_22K = 0X04; +constexpr uint8_t LTV_VAL_SAMP_FREQ_24K = 0X05; +constexpr uint8_t LTV_VAL_SAMP_FREQ_32K = 0X06; +constexpr uint8_t LTV_VAL_SAMP_FREQ_441K = 0X07; +constexpr uint8_t LTV_VAL_SAMP_FREQ_48K = 0X08; +constexpr uint8_t LTV_VAL_SAMP_FREQ_882K = 0X09; +constexpr uint8_t LTV_VAL_SAMP_FREQ_96K = 0X0A; +constexpr uint8_t LTV_VAL_SAMP_FREQ_176K = 0X0B; +constexpr uint8_t LTV_VAL_SAMP_FREQ_192K = 0X0C; +//constexpr uint8_t LTV_VAL_SAMP_FREQ_384K = 0X0D; + +constexpr uint8_t LC3_CODEC_ID = 0x06; +constexpr uint8_t ASCS_CLIENT_ID = 0x01; + +constexpr uint8_t TGT_LOW_LATENCY = 0x01; +constexpr uint8_t TGT_BAL_LATENCY = 0x02; +constexpr uint8_t TGT_HIGH_RELIABLE = 0x03; + +std::map freq_to_ltv_map = { + {CodecSampleRate::CODEC_SAMPLE_RATE_8000, LTV_VAL_SAMP_FREQ_8K }, + {CodecSampleRate::CODEC_SAMPLE_RATE_16000, LTV_VAL_SAMP_FREQ_16K }, + {CodecSampleRate::CODEC_SAMPLE_RATE_24000, LTV_VAL_SAMP_FREQ_24K }, + {CodecSampleRate::CODEC_SAMPLE_RATE_32000, LTV_VAL_SAMP_FREQ_32K }, + {CodecSampleRate::CODEC_SAMPLE_RATE_44100, LTV_VAL_SAMP_FREQ_441K }, + {CodecSampleRate::CODEC_SAMPLE_RATE_48000, LTV_VAL_SAMP_FREQ_48K }, + {CodecSampleRate::CODEC_SAMPLE_RATE_88200, LTV_VAL_SAMP_FREQ_882K }, + {CodecSampleRate::CODEC_SAMPLE_RATE_96000, LTV_VAL_SAMP_FREQ_96K }, + {CodecSampleRate::CODEC_SAMPLE_RATE_176400, LTV_VAL_SAMP_FREQ_176K }, + {CodecSampleRate::CODEC_SAMPLE_RATE_192000, LTV_VAL_SAMP_FREQ_192K } +}; + +std::list directions = { + cis::DIR_FROM_AIR, + cis::DIR_TO_AIR +}; + +std::vector locations = { + AUDIO_LOC_LEFT, + AUDIO_LOC_RIGHT +}; + +// common functions used from Stream tracker state Handlers +uint8_t StreamTracker::ChooseBestCodec(StreamType stream_type, + std::vector *codec_qos_configs, + PacsDiscovery *pacs_discovery) { + bool codec_found = false; + uint8_t index = 0; + // check the stream direction, based on direction look for + // matching record from preferred list of upper layer and + // remote device sink or src pac records + std::vector *pac_records = nullptr; + if(stream_type.direction == ASE_DIRECTION_SINK) { + pac_records = &pacs_discovery->sink_pac_records; + } else if(stream_type.direction == ASE_DIRECTION_SRC) { + pac_records = &pacs_discovery->src_pac_records; + } + + if (!pac_records) { + LOG(ERROR) << __func__ << "pac_records is null"; + return 0xFF; + } + DeviceType dev_type = strm_mgr_->GetDevType(); + for (auto i = codec_qos_configs->begin(); i != codec_qos_configs->end() + ; i++, index++) { + if(dev_type == DeviceType::EARBUD || + dev_type == DeviceType::HEADSET_STEREO) { + if((*i).qos_config.ascs_configs.size() != 1) continue; + } else if(dev_type == DeviceType::HEADSET_SPLIT_STEREO) { + if((*i).qos_config.ascs_configs.size() != 2) continue; + } + for (auto j = pac_records->begin(); + j != pac_records->end();j++) { + CodecConfig *src = &((*i).codec_config); + CodecConfig *dst = &(*j); + if (IsCodecConfigEqual(src,dst)) { + LOG(WARNING) << __func__ << ": Checking for matching Codec"; + if (GetLc3QPreference(src) && + GetCapaVendorMetaDataLc3QPref(dst)) { + LOG(INFO) << __func__ << ": Matching Codec LC3Q Found " + << ", for direction: " << loghex(stream_type.direction); + uint8_t lc3qVer = GetCapaVendorMetaDataLc3QVer(dst); + UpdateVendorMetaDataLc3QPref(src, true); + UpdateVendorMetaDataLc3QVer(src, lc3qVer); + } else { + LOG(INFO) << __func__ << ": LC3Q not prefered, going with LC3 " + << "for direction: " << loghex(stream_type.direction); + } + codec_found = true; + break; + } + } + if(codec_found) break; + } + if(codec_found) return index; + else return 0xFF; +} + +// fine tuning the QOS params (RTN, MTL, PD) based on +// remote device preferences +bool StreamTracker::ChooseBestQos(QosConfig *src_config, + ascs::AseCodecConfigParams *rem_qos_prefs, + QosConfig *dst_config, + int stream_state, + uint8_t stream_direction) { + uint8_t final_rtn = 0xFF; + uint16_t final_mtl = 0xFFFF; + uint32_t req_pd = (src_config->ascs_configs[0].presentation_delay[0] | + src_config->ascs_configs[0].presentation_delay[1] << 8 | + src_config->ascs_configs[0].presentation_delay[2] << 16); + + uint32_t rem_pd_min = (rem_qos_prefs->pd_min[0] | + rem_qos_prefs->pd_min[1] << 8 | + rem_qos_prefs->pd_min[2] << 16); + + uint32_t rem_pd_max = (rem_qos_prefs->pd_max[0] | + rem_qos_prefs->pd_max[1] << 8 | + rem_qos_prefs->pd_max[2] << 16); + + uint32_t rem_pref_pd_min = (rem_qos_prefs->pref_pd_min[0] | + rem_qos_prefs->pref_pd_min[1] << 8 | + rem_qos_prefs->pref_pd_min[2] << 16); + + uint32_t rem_pref_pd_max = (rem_qos_prefs->pref_pd_max[0] | + rem_qos_prefs->pref_pd_max[1] << 8 | + rem_qos_prefs->pref_pd_max[2] << 16); + + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + std::vector streams = audio_strms->FindByCigId( + src_config->ascs_configs[0].cig_id, + stream_state); + + // check if the RTN and MTL is with in the limits + if(stream_direction == ASE_DIRECTION_SINK) { + if(src_config->cis_configs[0].rtn_m_to_s > rem_qos_prefs->pref_rtn) { + final_rtn = rem_qos_prefs->pref_rtn; + } + if(src_config->cig_config.max_tport_latency_m_to_s > rem_qos_prefs->mtl) { + final_mtl = rem_qos_prefs->mtl; + } + } else if(stream_direction == ASE_DIRECTION_SRC) { + if(src_config->cis_configs[0].rtn_s_to_m > rem_qos_prefs->pref_rtn) { + final_rtn = rem_qos_prefs->pref_rtn; + } + if(src_config->cig_config.max_tport_latency_s_to_m > rem_qos_prefs->mtl) { + final_mtl = rem_qos_prefs->mtl; + } + } + + LOG(INFO) << __func__ << " req_pd: " << loghex(req_pd) + << " rem_pd_min: " << loghex(rem_pd_min) + << " rem_pd_max: " << loghex(rem_pd_max) + << " rem_pref_pd_min: " << loghex(rem_pref_pd_min) + << " rem_pref_pd_max: " << loghex(rem_pref_pd_max); + + // check if PD is within the limits + if(rem_pref_pd_min && rem_pref_pd_max) { + if(req_pd < rem_pref_pd_min) { + memcpy(&dst_config->ascs_configs[0].presentation_delay, + &rem_qos_prefs->pref_pd_min, + sizeof(dst_config->ascs_configs[0].presentation_delay)); + } else if(req_pd > rem_pref_pd_max) { + memcpy(&dst_config->ascs_configs[0].presentation_delay, + &rem_qos_prefs->pref_pd_max, + sizeof(dst_config->ascs_configs[0].presentation_delay)); + } + } else { + if(req_pd != rem_pd_min) { + memcpy(&dst_config->ascs_configs[0].presentation_delay, + &rem_qos_prefs->pd_min, + sizeof(dst_config->ascs_configs[0].presentation_delay)); + } + } + + // check if anything to be updated for all streams + if(final_rtn == 0xFF && final_mtl == 0XFFFF) { + LOG(WARNING) << __func__ << " No fine tuning for QOS params"; + return true; + } else if(final_rtn != 0xFF) { + LOG(WARNING) << __func__ << " Updated RTN to " << loghex(final_rtn); + } else if(final_mtl != 0XFFFF) { + LOG(WARNING) << __func__ << " Updated MTL to " << loghex(final_mtl); + } + + for (auto i = streams.begin(); i != streams.end();i++) { + UcastAudioStream *stream = (*i); + if(stream_direction == ASE_DIRECTION_SINK) { + if(final_mtl != 0xFFFF) { + stream->qos_config.cig_config.max_tport_latency_m_to_s = final_mtl; + } + if(final_rtn != 0xFF) { + for (auto it = stream->qos_config.cis_configs.begin(); + it != stream->qos_config.cis_configs.end(); it++) { + (*it).rtn_m_to_s = final_rtn; + } + } + } else if(stream_direction == ASE_DIRECTION_SRC) { + if(final_mtl != 0xFFFF) { + stream->qos_config.cig_config.max_tport_latency_s_to_m = final_mtl; + } + if(final_rtn != 0xFF) { + for (auto it = stream->qos_config.cis_configs.begin(); + it != stream->qos_config.cis_configs.end(); it++) { + (*it).rtn_s_to_m = final_rtn; + } + } + } + } + return true; +} + +bool StreamTracker::HandlePacsConnectionEvent(void *p_data) { + PacsConnectionState *pacs_state = (PacsConnectionState *) p_data; + if(pacs_state->state == ConnectionState::CONNECTED) { + LOG(INFO) << __func__ << " PACS server connected"; + } else if(pacs_state->state == ConnectionState::DISCONNECTED) { + HandleInternalDisconnect(false); + } + return true; +} + +bool StreamTracker::HandlePacsAudioContextEvent( + PacsAvailableContexts *pacs_contexts) { + std::vector *update_streams = GetMetaUpdateStreams(); + uint8_t contexts_supported = 0; + + // check if supported audio contexts has required contexts + for(auto it = update_streams->begin(); it != update_streams->end(); it++) { + if(it->update_type == StreamUpdateType::STREAMING_CONTEXT) { + if(it->stream_type.direction == ASE_DIRECTION_SINK) { + if(it->update_value & pacs_contexts->available_contexts) { + contexts_supported++; + } + } else if(it->stream_type.direction == ASE_DIRECTION_SRC) { + if((static_cast(it->update_value) << 16) & + pacs_contexts->available_contexts) { + contexts_supported++; + } + } + } + } + + if(contexts_supported != update_streams->size()) { + LOG(ERROR) << __func__ << ": No Matching available Contexts found"; + return false; + } else { + return true; + } +} + +bool StreamTracker::HandleCisEventsInStreaming(void* p_data) { + CisStreamState *data = (CisStreamState *) p_data; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + if(data->state == CisState::READY) { + for(auto it = directions.begin(); it != directions.end(); ++it) { + if(data->direction & *it) { + // find out the stream here + UcastAudioStream *stream = audio_strms->FindByCisIdAndDir + (data->cig_id, data->cis_id, *it); + if(stream) { + stream->cis_state = data->state; + stream->cis_pending_cmd = CisPendingCmd::NONE; + } + } + } + TransitionTo(StreamTracker::kStateStopping); + } + return true; +} + +bool StreamTracker::HandleAscsConnectionEvent(void *p_data) { + AscsConnectionState *ascs_state = (AscsConnectionState *) p_data; + if(ascs_state->state == GattState::CONNECTED) { + LOG(INFO) << __func__ << "ASCS server connected"; + } else if(ascs_state->state == GattState::DISCONNECTED) { + // make all streams ASE state ot idle so that further processing + // can happen + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + std::vector *strms_list = audio_strms->GetAllStreams(); + + for (auto it = strms_list->begin(); it != strms_list->end(); it++) { + (*it)->ase_state = ascs::ASE_STATE_IDLE; + (*it)->ase_pending_cmd = AscsPendingCmd::NONE; + (*it)->overall_state = StreamTracker::kStateIdle; + } + HandleInternalDisconnect(false); + } + return true; +} + +bool StreamTracker::ValidateAseUpdate(void* p_data, + IntStrmTrackers *int_strm_trackers, + int exp_strm_state) { + AscsState *ascs = ((AscsState *) p_data); + + uint8_t ase_id = ascs->ase_params.ase_id; + + // check if current stream tracker is interested in this ASE update + if(int_strm_trackers->FindByAseId(ase_id) + == nullptr) { + LOG(INFO) << __func__ << "Not intended for this tracker"; + return false; + } + + // find out the stream for the given ase id + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + UcastAudioStream *stream = audio_strms->FindByAseId(ase_id); + + LOG(INFO) << __func__ << ": Streams Size = " << audio_strms->size() + << ": ASE Id = " << loghex(ase_id); + + if (stream == nullptr || stream->overall_state != exp_strm_state) { + LOG(WARNING) << __func__ << "No Audio Stream found"; + return false; + } + + stream->ase_state = ascs->ase_params.ase_state; + stream->ase_params = ascs->ase_params; + stream->ase_pending_cmd = AscsPendingCmd::NONE; + return true; +} + +bool StreamTracker::HandleRemoteDisconnect(uint32_t event, + void* p_data, int cur_state) { + UpdateControlType(StreamControlType::Disconnect); + std::vector streams; + + switch(cur_state) { + case StreamTracker::kStateConnecting: { + std::vector *conn_streams = GetConnStreams(); + + for (auto it = conn_streams->begin(); it != conn_streams->end(); it++) { + StreamType type = it->stream_type; + streams.push_back(type); + } + UpdateStreams(&streams); + } break; + case StreamTracker::kStateReconfiguring: { + std::vector *reconf_streams = GetReconfStreams(); + + for (auto it = reconf_streams->begin(); + it != reconf_streams->end(); it++) { + StreamType type = it->stream_type; + streams.push_back(type); + } + UpdateStreams(&streams); + } break; + } + + // update the state to disconnecting + TransitionTo(StreamTracker::kStateDisconnecting); + ProcessEvent(event, p_data); + return true; +} + +bool StreamTracker::StreamCanbeDisconnected(StreamContext *cur_context, + uint8_t ase_id) { + bool can_be_disconnected = false; + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + StreamAttachedState state = (StreamAttachedState) + (static_cast (StreamAttachedState::PHYSICAL) | + static_cast (StreamAttachedState::IDLE_TO_PHY) | + static_cast (StreamAttachedState::VIR_TO_PHY)); + + std::vector attached_contexts = + contexts->FindByAseAttachedState(ase_id, state); + + LOG(INFO) << __func__ <<": attached_contexts: size : " + << attached_contexts.size(); + if(cur_context->attached_state == StreamAttachedState::PHYSICAL || + cur_context->attached_state == StreamAttachedState::IDLE_TO_PHY || + cur_context->attached_state == StreamAttachedState::VIR_TO_PHY ) { + can_be_disconnected = true; + } + return can_be_disconnected; +} + +bool StreamTracker::HandleInternalDisconnect(bool release) { + + UpdateControlType(StreamControlType::Disconnect); + + std::vector streams; + + int cur_state = StateId(); + LOG(WARNING) << __func__ <<": cur_state: " << cur_state + <<", release: " << release; + switch(cur_state) { + case StreamTracker::kStateConnecting: { + std::vector *conn_streams = GetConnStreams(); + + for (auto it = conn_streams->begin(); it != conn_streams->end(); it++) { + StreamType type = it->stream_type; + streams.push_back(type); + } + UpdateStreams(&streams); + } break; + case StreamTracker::kStateReconfiguring: { + std::vector *reconf_streams = GetReconfStreams(); + + for (auto it = reconf_streams->begin(); + it != reconf_streams->end(); it++) { + StreamType type = it->stream_type; + streams.push_back(type); + } + UpdateStreams(&streams); + } break; + } + + if (release) { + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + std::vector ase_ops; + std::vector *disc_streams = GetStreams(); + + for (auto it = disc_streams->begin(); it != disc_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(*it); + + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + bool can_be_disconnected = StreamCanbeDisconnected(context, id->ase_id); + if (can_be_disconnected && + stream && stream->overall_state == cur_state && + stream->ase_state != ascs::ASE_STATE_IDLE && + stream->ase_state != ascs::ASE_STATE_RELEASING && + stream->ase_pending_cmd != AscsPendingCmd::RELEASE_ISSUED) { + LOG(WARNING) << __func__ + <<": ASE State : " << loghex(stream->ase_state); + AseReleaseOp release_op = { + .ase_id = stream->ase_id + }; + ase_ops.push_back(release_op); + stream->ase_pending_cmd = AscsPendingCmd::RELEASE_ISSUED; + // change the overall state to Disconnecting + stream->overall_state = StreamTracker::kStateDisconnecting; + } + } + } + + // send consolidated release command to ASCS client + if(ase_ops.size()) { + LOG(WARNING) << __func__ << ": Going For ASCS Release op"; + ascs_client->Release(ASCS_CLIENT_ID, strm_mgr_->GetAddress(), ase_ops); + } + } + // update the state to disconnecting + TransitionTo(StreamTracker::kStateDisconnecting); + return true; +} + +bool StreamTracker::HandleRemoteStop(uint32_t event, + void* p_data, int cur_state) { + AscsState *ascs = ((AscsState *) p_data); + uint8_t ase_id = ascs->ase_params.ase_id; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + UcastAudioStream *stream = audio_strms->FindByAseId(ase_id); + + if(!stream) return false; + + if(stream->direction & cis::DIR_TO_AIR) { + LOG(ERROR) << __func__ << ": Invalid State transition to Disabling" + << ": ASE Id = " << loghex(ase_id); + return false; + } + + UpdateControlType(StreamControlType::Stop); + + if(cur_state != StreamTracker::kStateStarting || + cur_state != StreamTracker::kStateStreaming) { + return false; + } + // update the state to stopping + TransitionTo(StreamTracker::kStateStopping); + ProcessEvent(event, p_data); + return true; +} + +bool StreamTracker::HandleAbruptStop(uint32_t event, void* p_data) { + AscsState *ascs = ((AscsState *) p_data); + uint8_t ase_id = ascs->ase_params.ase_id; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + UcastAudioStream *stream = audio_strms->FindByAseId(ase_id); + + if(!stream) return false; + stream->ase_pending_cmd = AscsPendingCmd::NONE; + + UpdateControlType(StreamControlType::Stop); + + // update the state to stopping + TransitionTo(StreamTracker::kStateStopping); + return true; +} + +bool StreamTracker::HandleRemoteReconfig(uint32_t event, + void* p_data, int cur_state) { + UpdateControlType(StreamControlType::Reconfig); + std::vector streams; + + if(cur_state != StreamTracker::kStateConnected) { + return false; + } + // update the state to Reconfiguring + TransitionTo(StreamTracker::kStateReconfiguring); + ProcessEvent(event, p_data); + return true; +} + +void StreamTracker::HandleAseOpFailedEvent(void *p_data) { + AscsOpFailed *ascs_op = ((AscsOpFailed *) p_data); + std::vector *ase_list = &ascs_op->ase_list; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + if(ascs_op->ase_op_id == ascs::AseOpId::CODEC_CONFIG) { + // treat it like internal failure + for (auto i = ase_list->begin(); i != ase_list->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((i)->ase_id); + if(stream) { + stream->ase_pending_cmd = AscsPendingCmd::NONE; + stream->overall_state = StreamTracker::kStateIdle; + } + } + HandleInternalDisconnect(false); + } else { + HandleInternalDisconnect(true); + } +} + +void StreamTracker::HandleAseStateEvent(void *p_data, + StreamControlType control_type, + IntStrmTrackers *int_strm_trackers) { + // check the state and if the state is codec configured for all ASEs + // then proceed with group creation + AscsState *ascs = reinterpret_cast (p_data); + + uint8_t ase_id = ascs->ase_params.ase_id; + + // check if current stream tracker is interested in this ASE update + if(int_strm_trackers->FindByAseId(ase_id) == nullptr) { + LOG(INFO) << __func__ << ": Not intended for this tracker"; + return; + } + + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + UcastAudioStream *stream = audio_strms->FindByAseId(ase_id); + + if(stream == nullptr) { + return; + } else { + stream->ase_state = ascs->ase_params.ase_state; + stream->ase_params = ascs->ase_params; + stream->ase_pending_cmd = AscsPendingCmd::NONE; + } + + if(ascs->ase_params.ase_state == ascs::ASE_STATE_CODEC_CONFIGURED) { + stream->pref_qos_params = ascs->ase_params.codec_config_params; + // find out the stream for the given ase id + LOG(INFO) << __func__ << ": Total Num Streams = " << audio_strms->size() + << ": ASE Id = " << loghex(ase_id); + + // decide on best QOS params by comparing the upper layer prefs + // and remote dev's preferences + int state = StreamTracker::kStateIdle; + + if(control_type == StreamControlType::Connect) { + state = StreamTracker::kStateConnecting; + } else if(control_type == StreamControlType::Reconfig) { + state = StreamTracker::kStateReconfiguring; + } + + // check for all trackers codec is configured or not + std::vector *all_trackers = + int_strm_trackers->GetTrackerList(); + uint8_t num_codec_configured = 0; + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + if(stream->ase_pending_cmd == AscsPendingCmd::NONE && + (stream->ase_state == ascs::ASE_STATE_CODEC_CONFIGURED || + (control_type == StreamControlType::Reconfig && + stream->ase_state == ascs::ASE_STATE_QOS_CONFIGURED))) { + num_codec_configured++; + } + } + + if(int_strm_trackers->size() != num_codec_configured) { + LOG(WARNING) << __func__ << " Codec Not Configured For All Streams"; + return; + } + + // check for all streams together so that final group params + // will be decided + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (stream) { + ChooseBestQos(&stream->req_qos_config, &stream->pref_qos_params, + &stream->qos_config, state, stream->direction); + } + } + CheckAndSendQosConfig(int_strm_trackers); + + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_QOS_CONFIGURED) { + // TODO update the state as connected using callbacks + // make the state transition to connected + + // check for all trackers QOS is configured or not + // if so update it as streams are connected + std::vector *all_trackers = + int_strm_trackers->GetTrackerList(); + uint8_t num_qos_configured = 0; + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if(stream && stream->ase_state == ascs::ASE_STATE_QOS_CONFIGURED && + stream->ase_pending_cmd == AscsPendingCmd::NONE) { + num_qos_configured++; + } + } + + if(int_strm_trackers->size() != num_qos_configured) { + LOG(WARNING) << __func__ << " QOS Not Configured For All Streams"; + return; + } + + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + StreamContext *context = contexts->FindOrAddByType( + (*i)->strm_type); + if(context->attached_state == StreamAttachedState::IDLE_TO_PHY || + context->attached_state == StreamAttachedState::VIR_TO_PHY) { + context->attached_state = StreamAttachedState::PHYSICAL; + LOG(INFO) << __func__ << " Attached state made physical"; + } + stream->overall_state = kStateConnected; + } + + // update the state to connected + TransitionTo(StreamTracker::kStateConnected); + + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_RELEASING) { + HandleRemoteDisconnect(ASCS_ASE_STATE_EVT, p_data, StateId()); + } +} + +bool StreamTracker::HandleStreamUpdate (int cur_state) { + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + std::vector ase_meta_ops; + std::vector *update_streams = GetMetaUpdateStreams(); + + for (auto it = update_streams->begin(); + it != update_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + std::vector meta_data; + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if(stream && stream->ase_state != ascs::ASE_STATE_ENABLING && + stream->ase_state != ascs::ASE_STATE_STREAMING) { + continue; + } + if(it->update_type == StreamUpdateType::STREAMING_CONTEXT) { + uint8_t len = LTV_LEN_STRM_AUDIO_CONTEXTS; + uint8_t type = LTV_TYPE_STRM_AUDIO_CONTEXTS; + uint16_t value = it->update_value; + if(stream) stream->audio_context = value; + meta_data.insert(meta_data.end(), &len, &len + 1); + meta_data.insert(meta_data.end(), &type, &type + 1); + meta_data.insert(meta_data.end(), ((uint8_t *)&value), + ((uint8_t *)&value) + sizeof(uint16_t)); + } + + AseUpdateMetadataOp meta_op = { + .ase_id = id->ase_id, + .meta_data_len = + static_cast (meta_data.size()), + .meta_data = meta_data // media or voice + }; + ase_meta_ops.push_back(meta_op); + } + } + + // send consolidated update meta command to ASCS client + if(ase_meta_ops.size()) { + LOG(WARNING) << __func__ << ": Going For ASCS Update MetaData op"; + ascs_client->UpdateStream(ASCS_CLIENT_ID, strm_mgr_->GetAddress(), + ase_meta_ops); + } else { + return false; + } + + if(cur_state == StreamTracker::kStateUpdating) { + for (auto it = update_streams->begin(); + it != update_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if (stream != nullptr) { + // change the connection state to disable issued + stream->ase_pending_cmd = AscsPendingCmd::UPDATE_METADATA_ISSUED; + // change the overall state to Updating + stream->overall_state = StreamTracker::kStateUpdating; + } + } + } + } + return true; +} + +bool StreamTracker::HandleStop(void* p_data, int cur_state) { + if(p_data != nullptr) { + BapStop *evt_data = (BapStop *) p_data; + UpdateStreams(&evt_data->streams); + } + UpdateControlType(StreamControlType::Stop); + + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + std::vector ase_ops; + std::vector *stop_streams = GetStreams(); + + for (auto it = stop_streams->begin(); + it != stop_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(*it); + + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + AseDisableOp disable_op = { + .ase_id = id->ase_id + }; + ase_ops.push_back(disable_op); + } + } + + // send consolidated disable command to ASCS client + if(ase_ops.size()) { + LOG(WARNING) << __func__ << ": Going For ASCS Disable op"; + ascs_client->Disable(ASCS_CLIENT_ID, strm_mgr_->GetAddress(), ase_ops); + } + + for (auto it = stop_streams->begin(); + it != stop_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(*it); + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if (stream != nullptr && stream->overall_state == cur_state) { + // change the connection state to disable issued + stream->ase_pending_cmd = AscsPendingCmd::DISABLE_ISSUED; + // change the overall state to stopping + stream->overall_state = StreamTracker::kStateStopping; + } + } + } + TransitionTo(StreamTracker::kStateStopping); + return true; +} + +bool StreamTracker::HandleDisconnect(void* p_data, int cur_state) { + // expect the disconnection for same set of streams connection + // has initiated ex: if connect is issued for media (tx), voice(tx & rx) + // then disconnect is expected for media (tx), voice(tx & rx). + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + BapDisconnect *evt_data = (BapDisconnect *) p_data; + + UpdateControlType(StreamControlType::Disconnect); + + UpdateStreams(&evt_data->streams); + + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + + std::vector ase_ops; + std::vector *disc_streams = GetStreams(); + + for (auto it = disc_streams->begin(); it != disc_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(*it); + + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + bool can_be_disconnected = StreamCanbeDisconnected(context, id->ase_id); + if(can_be_disconnected && + stream && stream->overall_state != StreamTracker::kStateIdle && + stream->overall_state != StreamTracker::kStateDisconnecting && + stream->ase_pending_cmd != AscsPendingCmd::RELEASE_ISSUED) { + AseReleaseOp release_op = { + .ase_id = id->ase_id + }; + ase_ops.push_back(release_op); + stream->ase_pending_cmd = AscsPendingCmd::RELEASE_ISSUED; + // change the overall state to starting + stream->overall_state = StreamTracker::kStateDisconnecting; + } + } + } + + // send consolidated release command to ASCS client + if(ase_ops.size()) { + LOG(WARNING) << __func__ << ": Going For ASCS Release op"; + ascs_client->Release(ASCS_CLIENT_ID, strm_mgr_->GetAddress(), ase_ops); + } + + TransitionTo(StreamTracker::kStateDisconnecting); + return true; +} + +void StreamTracker::CheckAndSendQosConfig(IntStrmTrackers *int_strm_trackers) { + + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + // check for all trackers CIG is created or not + // if so proceed with QOS config operaiton + std::vector *all_trackers = + int_strm_trackers->GetTrackerList(); + + std::vector ase_ops; + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + QosConfig *qos_config = &stream->qos_config; + if(!stream || stream->ase_pending_cmd != AscsPendingCmd::NONE) { + continue; + } + if(stream->direction & cis::DIR_TO_AIR) { + + AseQosConfigOp qos_config_op = { + .ase_id = (*i)->ase_id, + .cig_id = stream->cig_id, + .cis_id = stream->cis_id, + .sdu_interval = { qos_config->cig_config.sdu_interval_m_to_s[0], + qos_config->cig_config.sdu_interval_m_to_s[1], + qos_config->cig_config.sdu_interval_m_to_s[2] }, + .framing = qos_config->cig_config.framing, + .phy = LE_2M_PHY, + .max_sdu_size = qos_config->cis_configs[(*i)->cis_id].max_sdu_m_to_s, + .retrans_number = qos_config->cis_configs[(*i)->cis_id].rtn_m_to_s, + .trans_latency = qos_config->cig_config.max_tport_latency_m_to_s, + .present_delay = {qos_config->ascs_configs[0].presentation_delay[0], + qos_config->ascs_configs[0].presentation_delay[1], + qos_config->ascs_configs[0].presentation_delay[2]} + }; + ase_ops.push_back(qos_config_op); + + } else if(stream->direction & cis::DIR_FROM_AIR) { + AseQosConfigOp qos_config_op = { + .ase_id = (*i)->ase_id, + .cig_id = stream->cig_id, + .cis_id = stream->cis_id, + .sdu_interval = { qos_config->cig_config.sdu_interval_s_to_m[0], + qos_config->cig_config.sdu_interval_s_to_m[1], + qos_config->cig_config.sdu_interval_s_to_m[2] }, + .framing = qos_config->cig_config.framing, + .phy = LE_2M_PHY, + .max_sdu_size = qos_config->cis_configs[(*i)->cis_id].max_sdu_s_to_m, + .retrans_number = qos_config->cis_configs[(*i)->cis_id].rtn_s_to_m, + .trans_latency = qos_config->cig_config.max_tport_latency_s_to_m, + .present_delay = {qos_config->ascs_configs[0].presentation_delay[0], + qos_config->ascs_configs[0].presentation_delay[1], + qos_config->ascs_configs[0].presentation_delay[2]} + }; + ase_ops.push_back(qos_config_op); + } + } + + if(ase_ops.size()) { + LOG(WARNING) << __func__ << ": Going For ASCS QosConfig op"; + ascs_client->QosConfig(ASCS_CLIENT_ID, strm_mgr_->GetAddress(), ase_ops); + + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if(stream && stream->ase_pending_cmd == AscsPendingCmd::NONE) + stream->ase_pending_cmd = AscsPendingCmd::QOS_CONFIG_ISSUED; + } + } +} + + +void StreamTracker::CheckAndSendEnable(IntStrmTrackers *int_strm_trackers) { + + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + std::vector *start_streams = GetStreams(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector ase_ops; + // check for all trackers CIG is created or not + // if so proceed with QOS config operaiton + std::vector *all_trackers = + int_strm_trackers->GetTrackerList(); + + uint8_t num_cig_created = 0; + + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if(stream && stream->cig_state == CigState::CREATED) { + num_cig_created++; + } + } + + if(int_strm_trackers->size() != num_cig_created) { + LOG(WARNING) << __func__ << " All CIGs are not created"; + return; + } + + for(auto it = start_streams->begin(); it != start_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(*it); + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + std::vector meta_data; + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + uint8_t len = LTV_LEN_STRM_AUDIO_CONTEXTS; + uint8_t type = LTV_TYPE_STRM_AUDIO_CONTEXTS; + uint16_t value = (*it).audio_context; + if(stream) stream->audio_context = value; + meta_data.insert(meta_data.end(), &len, &len + 1); + meta_data.insert(meta_data.end(), &type, &type + 1); + meta_data.insert(meta_data.end(), ((uint8_t *)&value), + ((uint8_t *)&value) + sizeof(uint16_t)); + + AseEnableOp enable_op = { + .ase_id = id->ase_id, + .meta_data_len = + static_cast (meta_data.size()), + .meta_data = meta_data // media or voice + }; + ase_ops.push_back(enable_op); + } + } + + // send consolidated enable command to ASCS client + if(ase_ops.size()) { + LOG(WARNING) << __func__ << ": Going For ASCS Enable op"; + ascs_client->Enable(ASCS_CLIENT_ID, strm_mgr_->GetAddress(), ase_ops); + + for (auto it = start_streams->begin(); it != start_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(*it); + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if (stream != nullptr && stream->overall_state == + StreamTracker::kStateConnected) { + // change the connection state to enable issued + stream->ase_pending_cmd = AscsPendingCmd::ENABLE_ISSUED; + // change the overall state to starting + stream->overall_state = StreamTracker::kStateStarting; + } + } + } + } +} + +void StreamTracker::HandleCigStateEvent(uint32_t event, void *p_data, + IntStrmTrackers *int_strm_trackers) { + // check if the associated CIG state is created + // if so go for Enable Operation + CisGroupState *data = ((CisGroupState *) p_data); + + // check if current stream tracker is interested in this CIG update + std::vector int_trackers = + int_strm_trackers->FindByCigId(data->cig_id); + if(int_trackers.empty()) { + LOG(INFO) << __func__ << " Not intended for this tracker"; + return; + } + + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + if(data->state == CigState::CREATED) { + for (auto i = int_trackers.begin(); i != int_trackers.end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if(stream) { + stream->cis_pending_cmd = CisPendingCmd::NONE; + stream->cig_state = data->state; + stream->cis_state = CisState::READY; + } + } + CheckAndSendEnable(int_strm_trackers); + } else if(data->state == CigState::IDLE) { + // CIG state is idle means there is some failure + LOG(ERROR) << __func__ << ": CIG Creation Failed"; + for (auto i = int_trackers.begin(); i != int_trackers.end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if(stream) { + stream->cig_state = CigState::INVALID; + stream->cis_state = CisState::INVALID; + stream->cis_pending_cmd = CisPendingCmd::NONE;; + } + } + HandleInternalDisconnect(false); + return; + } +} + +bool StreamTracker::PrepareCodecConfigPayload( + std::vector *ase_ops, + UcastAudioStream *stream) { + std::vector codec_params; + uint8_t tgt_latency = TGT_HIGH_RELIABLE; + // check for sampling freq + for (auto it : freq_to_ltv_map) { + if(stream->codec_config.sample_rate == it.first) { + uint8_t len = LTV_LEN_SAMP_FREQ; + uint8_t type = LTV_TYPE_SAMP_FREQ; + uint8_t rate = it.second; + codec_params.insert(codec_params.end(), &len, &len + 1); + codec_params.insert(codec_params.end(), &type, &type + 1); + codec_params.insert(codec_params.end(), &rate, &rate + 1); + break; + } + } + + // check for frame duration and fetch 5th byte + uint8_t frame_duration = GetFrameDuration(&stream->codec_config); + uint8_t len = LTV_LEN_FRAME_DUR; + uint8_t type = LTV_TYPE_FRAME_DUR; + codec_params.insert(codec_params.end(), &len, &len + 1); + codec_params.insert(codec_params.end(), &type, &type + 1); + codec_params.insert(codec_params.end(), &frame_duration, + &frame_duration + 1); + + // audio chnl allcation + if(stream->audio_location) { + uint8_t len = LTV_LEN_CHNL_ALLOC; + uint8_t type = LTV_TYPE_CHNL_ALLOC; + uint32_t value = stream->audio_location; + codec_params.insert(codec_params.end(), &len, &len + 1); + codec_params.insert(codec_params.end(), &type, &type + 1); + codec_params.insert(codec_params.end(), ((uint8_t *)&value), + ((uint8_t *)&value) + sizeof(uint32_t)); + } + + // octets per frame + len = LTV_LEN_OCTS_PER_FRAME; + type = LTV_TYPE_OCTS_PER_FRAME; + uint16_t octs_per_frame = GetOctsPerFrame(&stream->codec_config); + codec_params.insert(codec_params.end(), &len, &len + 1); + codec_params.insert(codec_params.end(), &type, &type + 1); + codec_params.insert(codec_params.end(), ((uint8_t *)&octs_per_frame), + ((uint8_t *)&octs_per_frame) + sizeof(uint16_t)); + + // blocks per SDU + len = LTV_LEN_FRAMES_PER_SDU; + type = LTV_TYPE_FRAMES_PER_SDU; + uint8_t blocks_per_sdu = GetLc3BlocksPerSdu(&stream->codec_config); + // initialize it to 1 if it doesn't exists + if(!blocks_per_sdu) { + blocks_per_sdu = 1; + } + codec_params.insert(codec_params.end(), &len, &len + 1); + codec_params.insert(codec_params.end(), &type, &type + 1); + codec_params.insert(codec_params.end(), &blocks_per_sdu, + &blocks_per_sdu + 1); + + if(stream->audio_context == CONTENT_TYPE_MEDIA) { + tgt_latency = TGT_HIGH_RELIABLE; + } else if(stream->audio_context == CONTENT_TYPE_CONVERSATIONAL) { + tgt_latency = TGT_BAL_LATENCY; + } else if(stream->audio_context == CONTENT_TYPE_GAME) { + tgt_latency = TGT_LOW_LATENCY; + } + + AseCodecConfigOp codec_config_op = { + .ase_id = stream->ase_id, + .tgt_latency = tgt_latency, + .tgt_phy = LE_2M_PHY, + .codec_id = {LC3_CODEC_ID, 0, 0, 0, 0}, + .codec_params_len = + static_cast (codec_params.size()), + .codec_params = codec_params + }; + ase_ops->push_back(codec_config_op); + return true; +} + +alarm_t* StreamTracker::SetTimer(const char* alarmname, + BapTimeout* timeout, TimeoutReason reason, uint64_t ms) { + alarm_t* timer = nullptr; + + timeout->bd_addr = strm_mgr_->GetAddress(); + timeout->tracker = this; + timeout->reason = reason; + timeout->transition_state = StateId(); + + BapAlarm* bap_alarm = strm_mgr_->GetBapAlarm(); + if (bap_alarm != nullptr) { + timer = bap_alarm->Create(alarmname); + if (timer == nullptr) { + LOG(ERROR) << __func__ << ": Not able to create alarm"; + return nullptr; + } + LOG(INFO) << __func__ << ": starting " << alarmname; + bap_alarm->Start(timer, ms, timeout); + } + return timer; +} + +void StreamTracker::ClearTimer(alarm_t* timer, const char* alarmname) { + BapAlarm* bap_alarm = strm_mgr_->GetBapAlarm(); + + if (bap_alarm != nullptr && bap_alarm->IsScheduled(timer)) { + LOG(INFO) << __func__ << ": clear " << alarmname; + bap_alarm->Stop(timer); + } +} + +void StreamTracker::OnTimeout(void* data) { + BapTimeout* timeout = (BapTimeout *)data; + if (timeout == nullptr) { + LOG(INFO) << __func__ << ": timeout data null, return "; + return; + } + + bool isReleaseNeeded = false; + int stream_tracker_id = timeout->transition_state; + LOG(INFO) << __func__ << ": stream_tracker_ID: " << stream_tracker_id + << ", timeout reason: " << static_cast(timeout->reason); + + if (timeout->reason == TimeoutReason::STATE_TRANSITION) { + if (stream_tracker_id == StreamTracker::kStateConnecting) { + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector *conn_streams = GetConnStreams(); + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + LOG(WARNING) << __func__ << ": audio_strms: " << audio_strms->size() + << ", conn_streams: " << conn_streams->size(); + + for (auto it = conn_streams->begin(); it != conn_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + LOG(INFO) << __func__ << ": connection_state: " + << static_cast(context->connection_state); + + if(context->connection_state == IntConnectState::ASCS_DISCOVERED) { + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + + LOG(INFO) << __func__ << ": ase_state: " << stream->ase_state; + if (stream->ase_state != ascs::ASE_STATE_IDLE && + stream->ase_state != ascs::ASE_STATE_RELEASING) { + LOG(WARNING) << __func__ + << ": ascs state is neither idle nor releasing"; + isReleaseNeeded = true; + break; + } + } + } + } + LOG(INFO) << __func__ << ": isReleaseNeeded: " << isReleaseNeeded; + HandleInternalDisconnect(isReleaseNeeded); + } else if (stream_tracker_id != StreamTracker::kStateDisconnecting) { + //All other transient states + HandleInternalDisconnect(true); + } + } + LOG(INFO) << __func__ << ": Exit"; +} + +void StreamTracker::StateIdle::OnEnter() { + LOG(INFO) << __func__ << ": StreamTracker State: " << GetState(); + + UcastClientCallbacks* callbacks = strm_mgr_->GetUclientCbacks(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector strms; + StreamControlType control_type = tracker_.GetControlType(); + + if(control_type != StreamControlType::Disconnect && + control_type != StreamControlType::Connect) { + return; + } + + if(control_type == StreamControlType::Disconnect) { + std::vector *disc_streams = tracker_.GetStreams(); + LOG(WARNING) << __func__ << ": Disc Streams Size: " + << disc_streams->size(); + for (auto it = disc_streams->begin(); it != disc_streams->end(); it++) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + state.stream_type = *it; + state.stream_state = StreamState::DISCONNECTED; + strms.push_back(state); + StreamContext *context = contexts->FindOrAddByType(*it); + context->stream_state = StreamState::DISCONNECTED; + context->attached_state = StreamAttachedState::IDLE; + LOG(INFO) << __func__ << " Attached state made idle"; + context->stream_ids.clear(); + } + } else if(control_type == StreamControlType::Connect) { + std::vector *conn_streams = tracker_.GetConnStreams(); + uint32_t prev_state = tracker_.PreviousStateId(); + for (auto it = conn_streams->begin(); it != conn_streams->end(); it++) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + context->stream_state = StreamState::DISCONNECTED; + context->attached_state = StreamAttachedState::IDLE; + LOG(INFO) << __func__ << " Attached state made idle"; + context->stream_ids.clear(); + if(prev_state == StreamTracker::kStateConnecting) { + state.stream_type = it->stream_type; + state.stream_state = StreamState::DISCONNECTED; + strms.push_back(state); + } + } + } + callbacks->OnStreamState(strm_mgr_->GetAddress(), strms); +} + +void StreamTracker::StateIdle::OnExit() { + +} + +bool StreamTracker::StateIdle::ProcessEvent(uint32_t event, void* p_data) { + LOG(INFO) <<__func__ <<": BD Addr = " << strm_mgr_->GetAddress() + <<": State = " << GetState() + <<": Event = " << tracker_.GetEventName(event); + + GattPendingData *gatt_pending_data = strm_mgr_->GetGattPendingData(); + + switch (event) { + case BAP_CONNECT_REQ_EVT: { + BapConnect *evt_data = (BapConnect *) p_data; + // check if the PACS client is connected to remote device + PacsClient *pacs_client = strm_mgr_->GetPacsClient(); + uint16_t pacs_client_id = strm_mgr_->GetPacsClientId(); + ConnectionState pacs_state = strm_mgr_->GetPacsState(); + if(pacs_state == ConnectionState::DISCONNECTED || + pacs_state == ConnectionState::DISCONNECTING || + pacs_state == ConnectionState::CONNECTING) { + // move the state to connecting and initiate pacs connection + pacs_client->Connect(pacs_client_id, strm_mgr_->GetAddress(), + evt_data->is_direct); + if(gatt_pending_data->pacs_pending_cmd == GattPendingCmd::NONE) { + gatt_pending_data->pacs_pending_cmd = + GattPendingCmd::GATT_CONN_PENDING; + } + tracker_.TransitionTo(StreamTracker::kStateConnecting); + } else if(pacs_state == ConnectionState::CONNECTED) { + // pacs is already connected so initiate + // pacs service discovry now and move the state to connecting + pacs_client->StartDiscovery(pacs_client_id, strm_mgr_->GetAddress()); + tracker_.TransitionTo(StreamTracker::kStateConnecting); + } + } break; + default: + LOG(WARNING) << __func__ << "Unhandled Event" << loghex(event); + break; + } + return true; +} + +void StreamTracker::StateConnecting::OnEnter() { + LOG(INFO) << __func__ << ": StreamTracker State: " << GetState(); + + UcastClientCallbacks* callbacks = strm_mgr_->GetUclientCbacks(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector strms; + std::vector *conn_streams = tracker_.GetConnStreams(); + + StreamControlType control_type = tracker_.GetControlType(); + + if(control_type != StreamControlType::Connect) return; + + ConnectionState pacs_state = strm_mgr_->GetPacsState(); + + LOG(INFO) << __func__ << ": Conn Streams Size: " << conn_streams->size(); + + for (auto it = conn_streams->begin(); it != conn_streams->end(); it++) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + context->stream_state = StreamState::CONNECTING; + if(pacs_state == ConnectionState::DISCONNECTED || + pacs_state == ConnectionState::CONNECTING) { + context->connection_state = IntConnectState::PACS_CONNECTING; + } else if(pacs_state == ConnectionState::CONNECTED) { + context->connection_state = IntConnectState::PACS_DISCOVERING; + } + state.stream_type = it->stream_type; + state.stream_state = StreamState::CONNECTING; + strms.push_back(state); + } + callbacks->OnStreamState(strm_mgr_->GetAddress(), strms); + + TimeoutReason reason = TimeoutReason::STATE_TRANSITION; + state_transition_timer = tracker_.SetTimer("StateConnectingTimer", + &timeout, reason, ((conn_streams->size()) * + (static_cast(TimeoutVal::ConnectingTimeout)))); + if (state_transition_timer == nullptr) { + LOG(ERROR) << __func__ << ": StateConnecting: Alarm not allocated."; + return; + } +} + +void StreamTracker::StateConnecting::OnExit() { + tracker_.ClearTimer(state_transition_timer, "StateConnectingTimer"); +} + +void StreamTracker::StateConnecting::DeriveDeviceType( + PacsDiscovery *pacs_discovery) { + // derive the device type based on sink pac records + std::vector *pac_records = &pacs_discovery->sink_pac_records; + uint8_t max_chnl_count = 0; + + // chnl count Audio location Type of device No of ASEs + // 1 Left or Right Earbud 1 ASE per Earbud + // 1 Left and Right Stereo Headset ( 2 CIS) 2 ASEs + // 2 Left and Right Stereo Headset ( 1 CIS) 1 ASE + // 2 Left or Right Earbud 1 ASE per Earbud + + for (auto j = pac_records->begin(); j != pac_records->end();j++) { + CodecConfig *dst = &(*j); + if(static_cast (dst->channel_mode) & + static_cast (CodecChannelMode::CODEC_CHANNEL_MODE_STEREO)) { + max_chnl_count = 2; + } else if(!max_chnl_count && + static_cast (dst->channel_mode) & + static_cast (CodecChannelMode::CODEC_CHANNEL_MODE_MONO)) { + max_chnl_count = 1; + } + } + + if(pacs_discovery->sink_locations & ucast::AUDIO_LOC_LEFT && + pacs_discovery->sink_locations & ucast::AUDIO_LOC_RIGHT) { + if(max_chnl_count == 2) { + strm_mgr_->UpdateDevType(DeviceType::HEADSET_STEREO); + } else if (max_chnl_count == 1) { + strm_mgr_->UpdateDevType(DeviceType::HEADSET_SPLIT_STEREO); + } + } else if(pacs_discovery->sink_locations & ucast::AUDIO_LOC_LEFT || + pacs_discovery->sink_locations & ucast::AUDIO_LOC_RIGHT) { + strm_mgr_->UpdateDevType(DeviceType::EARBUD); + } +} + + +bool StreamTracker::StateConnecting::AttachStreamsToContext( + std::vector *all_trackers, + std::vector *streams, + uint8_t cis_count, + std::vector *ase_ops) { + PacsDiscovery *pacs_discovery_ = tracker_.GetPacsDiscovery(); + if (!pacs_discovery_) { + return false; + } + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + for(uint8_t i = 0; i < all_trackers->size()/cis_count ; i++) { + for(uint8_t j = 0; j < cis_count ; j++) { + IntStrmTracker *tracker = all_trackers->at(i*cis_count + j); + UcastAudioStream *stream = streams->at((i*cis_count + j)% streams->size()); + StreamContext *context = contexts->FindOrAddByType( + tracker->strm_type); + if(stream->overall_state == StreamTracker::kStateIdle) { + stream->audio_context = tracker->strm_type.audio_context; + stream->control_type = StreamControlType::Connect; + stream->ase_pending_cmd = AscsPendingCmd::NONE; + stream->cis_pending_cmd = CisPendingCmd::NONE; + stream->codec_config = tracker->codec_config; + stream->req_qos_config = tracker->qos_config; + stream->qos_config = tracker->qos_config; + stream->cig_id = tracker->cig_id; + stream->cis_id = tracker->cis_id; + + stream->cig_state = CigState::INVALID; + stream->cis_state = CisState::INVALID; + stream->overall_state = StreamTracker::kStateConnecting; + + if (stream->direction == ASE_DIRECTION_SINK) { + if(cis_count > 1) { + stream->audio_location = + pacs_discovery_->sink_locations & locations.at(j); + } else { + if(pacs_discovery_->sink_locations & ucast::AUDIO_LOC_LEFT && + pacs_discovery_->sink_locations & ucast::AUDIO_LOC_RIGHT) { + stream->audio_location = 0; + } else if(pacs_discovery_->sink_locations & ucast::AUDIO_LOC_LEFT || + pacs_discovery_->sink_locations & ucast::AUDIO_LOC_RIGHT) { + stream->audio_location = pacs_discovery_->sink_locations; + } + } + } else if (stream->direction == ASE_DIRECTION_SRC) { + if(cis_count > 1) { + stream->audio_location = + pacs_discovery_->src_locations & locations.at(j); + } else { + if(pacs_discovery_->src_locations & ucast::AUDIO_LOC_LEFT && + pacs_discovery_->src_locations & ucast::AUDIO_LOC_RIGHT) { + stream->audio_location = 0; + } else if(pacs_discovery_->src_locations & ucast::AUDIO_LOC_LEFT || + pacs_discovery_->src_locations & ucast::AUDIO_LOC_RIGHT) { + stream->audio_location = pacs_discovery_->src_locations; + } + } + } + tracker_.PrepareCodecConfigPayload(ase_ops, stream); + tracker->attached_state = context->attached_state = + StreamAttachedState::IDLE_TO_PHY; + LOG(INFO) << __func__ + << ": Physically attached"; + } else { + LOG(INFO) << __func__ + << ": Virtually attached"; + tracker->attached_state = context->attached_state = + StreamAttachedState::VIRTUAL; + } + tracker->ase_id = stream->ase_id; + + StreamIdType id = { + .ase_id = stream->ase_id, + .ase_direction = stream->direction, + .virtual_attach = false, + .cig_id = tracker->cig_id, + .cis_id = tracker->cis_id + }; + context->stream_ids.push_back(id); + } + } + return true; +} + +bool StreamTracker::StateConnecting::ProcessEvent(uint32_t event, void* p_data) { + LOG(INFO) <<__func__ <<": BD Addr = " << strm_mgr_->GetAddress() + <<": State = " << GetState() + <<": Event = " << tracker_.GetEventName(event); + + std::vector *conn_streams = tracker_.GetConnStreams(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + GattPendingData *gatt_pending_data = strm_mgr_->GetGattPendingData(); + + uint8_t num_conn_streams = 0; + if(conn_streams) { + num_conn_streams = conn_streams->size(); + } + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + + switch (event) { + case BAP_DISCONNECT_REQ_EVT: { + + // expect the disconnection for same set of streams connection + // has initiated ex: if connect is issued for media (tx), voice(tx & rx) + // then disconnect is expected for media (tx), voice(tx & rx). + + // based on connection state, issue the relevant commands and move + // the state to disconnecting + // issue the release opertion if for any stream ASE operation is + // initiated + + // upate the control type and streams also + BapDisconnect *evt_data = (BapDisconnect *) p_data; + + tracker_.UpdateControlType(StreamControlType::Disconnect); + + tracker_.UpdateStreams(&evt_data->streams); + + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + + std::vector ase_ops; + std::vector *disc_streams = tracker_.GetStreams(); + + LOG(WARNING) << __func__ << ": disc_streams: " << disc_streams->size(); + + for (auto it = disc_streams->begin(); it != disc_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(*it); + if(context->connection_state == IntConnectState::ASCS_DISCOVERED) { + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + bool can_be_disconnected = + tracker_.StreamCanbeDisconnected(context, id->ase_id); + if(can_be_disconnected && + stream->ase_state == ascs::ASE_STATE_CODEC_CONFIGURED && + stream->ase_pending_cmd != AscsPendingCmd::RELEASE_ISSUED) { + AseReleaseOp release_op = { + .ase_id = stream->ase_id + }; + ase_ops.push_back(release_op); + stream->ase_pending_cmd = AscsPendingCmd::RELEASE_ISSUED; + // change the overall state to starting + stream->overall_state = StreamTracker::kStateDisconnecting; + } + } + } + } + + LOG(INFO) << __func__ << ": ase_ops size: " << ase_ops.size(); + + // send consolidated release command to ASCS client + if(ase_ops.size()) { + LOG(WARNING) << __func__ << ": Going For ASCS Release op"; + ascs_client->Release(ASCS_CLIENT_ID, strm_mgr_->GetAddress(), ase_ops); + } + + tracker_.TransitionTo(StreamTracker::kStateDisconnecting); + + } break; + case PACS_CONNECTION_STATE_EVT: { + PacsConnectionState *pacs_state = (PacsConnectionState *) p_data; + + PacsClient *pacs_client = strm_mgr_->GetPacsClient(); + uint16_t pacs_client_id = strm_mgr_->GetPacsClientId(); + + LOG(WARNING) << __func__ + <<": pacs_state: " << static_cast(pacs_state->state); + if(pacs_state->state == ConnectionState::CONNECTED) { + // now send the PACS discovery + gatt_pending_data->pacs_pending_cmd = GattPendingCmd::NONE; + pacs_client->StartDiscovery(pacs_client_id, strm_mgr_->GetAddress()); + } else if(pacs_state->state == ConnectionState::DISCONNECTED) { + gatt_pending_data->pacs_pending_cmd = GattPendingCmd::NONE; + tracker_.HandleInternalDisconnect(false); + return false; + } + + for (uint8_t i = 0; i < num_conn_streams ; i++) { + StreamConnect conn_stream = conn_streams->at(i); + StreamContext *context = contexts->FindOrAddByType( + conn_stream.stream_type); + if(pacs_state->state == ConnectionState::CONNECTED) { + context->connection_state = IntConnectState::PACS_DISCOVERING; + } + } + } break; + + case PACS_DISCOVERY_RES_EVT: { + PacsDiscovery pacs_discovery_ = *((PacsDiscovery *) p_data); + GattState ascs_state = strm_mgr_->GetAscsState(); + GattPendingData *gatt_pending_data = strm_mgr_->GetGattPendingData(); + bool process_pacs_results = false; + + // check if this tracker already passed the pacs discovery stage + for (uint8_t i = 0; i < num_conn_streams ; i++) { + StreamConnect conn_stream = conn_streams->at(i); + StreamContext *context = contexts->FindOrAddByType( + conn_stream.stream_type); + if(context->connection_state == IntConnectState::PACS_DISCOVERING) { + process_pacs_results = true; + break; + } + } + + if(!process_pacs_results) break; + + bool context_supported = false; + // check the status + if(pacs_discovery_.status) { + tracker_.HandleInternalDisconnect(false); + LOG(ERROR) << __func__ << " PACS discovery failed"; + return false; + } + + tracker_.UpdatePacsDiscovery(pacs_discovery_); + + // Derive the device type based on pacs discovery results + DeriveDeviceType((PacsDiscovery *) p_data); + + // check if supported audio contexts has required contexts + for (auto it = conn_streams->begin(); it != conn_streams->end(); it++) { + StreamType stream = it->stream_type; + if(stream.direction == ASE_DIRECTION_SINK) { + if(stream.audio_context & pacs_discovery_.supported_contexts) { + context_supported = true; + } + } else if(stream.direction == ASE_DIRECTION_SRC) { + if((static_cast(stream.audio_context) << 16) & + pacs_discovery_.supported_contexts) { + context_supported = true; + } + } + } + + if(!context_supported) { + LOG(ERROR) << __func__ << " No Matching Supported Contexts found"; + tracker_.HandleInternalDisconnect(false); + break; + } + + // if not present send the BAP callback as disconnected + // compare the codec configs from upper layer to remote dev + // sink or src PACS records/capabilities. + + // go for ASCS discovery only when codec configs are decided + + for (uint8_t i = 0; i < num_conn_streams ; i++) { + StreamConnect conn_stream = conn_streams->at(i); + // TODO for now will pick directly first set of Codec and QOS configs + + uint8_t index = tracker_.ChooseBestCodec(conn_stream.stream_type, + &conn_stream.codec_qos_config_pair, + &pacs_discovery_); + if(index != 0XFF) { + CodecQosConfig entry = conn_stream.codec_qos_config_pair.at(index); + CodecConfig codec_config = entry.codec_config; + QosConfig qos_config = entry.qos_config; + + StreamContext *context = contexts->FindOrAddByType( + conn_stream.stream_type); + for (auto ascs_config = qos_config.ascs_configs.begin(); + ascs_config != qos_config.ascs_configs.end(); ascs_config++) { + int_strm_trackers_.FindOrAddBytrackerType(conn_stream.stream_type, + 0x00, ascs_config->cig_id, + ascs_config->cis_id, + codec_config, qos_config); + } + context->codec_config = codec_config; + context->req_qos_config = qos_config; + } else { + LOG(ERROR) << __func__ << " No Matching Codec Found For Stream"; + } + } + + // check if any match between upper layer codec and remote dev's + // pacs records + if(!int_strm_trackers_.size()) { + LOG(WARNING) << __func__ << "No Matching codec found for all streams"; + tracker_.HandleInternalDisconnect(false); + return false; + } + + if(ascs_state == GattState::CONNECTED) { + LOG(WARNING) << __func__ << ": Going For ASCS Service Discovery"; + // now send the ASCS discovery + ascs_client->StartDiscovery(ASCS_CLIENT_ID, strm_mgr_->GetAddress()); + } else if(ascs_state == GattState::DISCONNECTED) { + LOG(WARNING) << __func__ << ": Going For ASCS Conneciton"; + ascs_client->Connect(ASCS_CLIENT_ID, strm_mgr_->GetAddress(), false); + if(gatt_pending_data->ascs_pending_cmd == GattPendingCmd::NONE) { + gatt_pending_data->ascs_pending_cmd = + GattPendingCmd::GATT_CONN_PENDING; + } + } + + for (uint8_t i = 0; i < num_conn_streams ; i++) { + StreamConnect conn_stream = conn_streams->at(i); + StreamContext *context = contexts->FindOrAddByType( + conn_stream.stream_type); + if(ascs_state == GattState::CONNECTED) { + context->connection_state = IntConnectState::ASCS_DISCOVERING; + } else if(ascs_state == GattState::DISCONNECTED) { + context->connection_state = IntConnectState::ASCS_CONNECTING; + } + } + } break; + + case ASCS_CONNECTION_STATE_EVT: { + AscsConnectionState *ascs_state = (AscsConnectionState *) p_data; + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + + if(ascs_state->state == GattState::CONNECTED) { + LOG(INFO) << __func__ << " ASCS server connected"; + // now send the ASCS discovery + gatt_pending_data->ascs_pending_cmd = GattPendingCmd::NONE; + ascs_client->StartDiscovery(ASCS_CLIENT_ID, strm_mgr_->GetAddress()); + } else if(ascs_state->state == GattState::DISCONNECTED) { + LOG(INFO) << __func__ << " ASCS server Disconnected"; + gatt_pending_data->ascs_pending_cmd = GattPendingCmd::NONE; + tracker_.HandleInternalDisconnect(false); + return false; + } + + for (uint8_t i = 0; i < num_conn_streams ; i++) { + StreamConnect conn_stream = conn_streams->at(i); + StreamContext *context = contexts->FindOrAddByType( + conn_stream.stream_type); + if(ascs_state->state == GattState::CONNECTED) { + context->connection_state = IntConnectState::ASCS_DISCOVERING; + } + } + } break; + case ASCS_DISCOVERY_RES_EVT: { + AscsDiscovery ascs_discovery_ = *((AscsDiscovery *) p_data); + std::vector ase_ops; + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + std::vector sink_ase_list = ascs_discovery_.sink_ases_list; + std::vector src_ase_list = ascs_discovery_.src_ases_list; + // check the status + if(ascs_discovery_.status) { + tracker_.HandleInternalDisconnect(false); + return false; + } + + for (uint8_t i = 0; i < num_conn_streams ; i++) { + StreamConnect conn_stream = conn_streams->at(i); + StreamContext *context = contexts->FindOrAddByType( + conn_stream.stream_type); + context->connection_state = IntConnectState::ASCS_DISCOVERED; + } + + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + // create the UcastAudioStream for each ASEs (ase id) + // check if the entry is present, if not create and add it to list + // find out number of ASEs which are in IDLE state + for (auto & ase : sink_ase_list) { + audio_strms->FindOrAddByAseId(ase.ase_id, + ase.ase_state, ASE_DIRECTION_SINK); + } + + for (auto & ase : src_ase_list) { + audio_strms->FindOrAddByAseId(ase.ase_id, + ase.ase_state, ASE_DIRECTION_SRC); + } + + LOG(INFO) << __func__ << ": total num of audio strms: " + << audio_strms->size(); + + std::vector sink_int_trackers = + int_strm_trackers_.GetTrackerListByDir(ASE_DIRECTION_SINK); + + std::vector src_int_trackers = + int_strm_trackers_.GetTrackerListByDir(ASE_DIRECTION_SRC); + + std::vector state_ids = { StreamTracker::kStateIdle }; + + std::vector idle_sink_streams = + audio_strms->GetStreamsByStates(state_ids, + ASE_DIRECTION_SINK); + std::vector idle_src_streams = + audio_strms->GetStreamsByStates(state_ids, + ASE_DIRECTION_SRC); + LOG(INFO) << __func__ << ": Num of Sink Idle Streams = " + << idle_sink_streams.size() + << ": Num of Src Idle Streams = " + << idle_src_streams.size(); + + LOG(INFO) << __func__ << ": Num of Sink Internal Trackers = " + << sink_int_trackers.size() + << ": Num of Src Internal Trackers = " + << src_int_trackers.size(); + + LOG(INFO) << __func__ << ": Num of Conn Streams " + << loghex(num_conn_streams); + + // check how many stream connections are requested and + // how many streams(ASEs) are available for processing + // check if we have sufficient number of streams(ASEs) for + // the given set of connection requirement + DeviceType dev_type = strm_mgr_->GetDevType(); + uint8_t cis_count = 0; + if(dev_type == DeviceType::EARBUD || + dev_type == DeviceType::HEADSET_STEREO) { + cis_count = 1; + } else if(dev_type == DeviceType::HEADSET_SPLIT_STEREO) { + cis_count = 2; + } + + std::vector valid_state_ids = { + StreamTracker::kStateConnecting, + StreamTracker::kStateConnected, + StreamTracker::kStateStreaming, + StreamTracker::kStateReconfiguring, + StreamTracker::kStateDisconnecting, + StreamTracker::kStateStarting, + StreamTracker::kStateStopping + }; + + if(sink_int_trackers.size()) { + if(idle_sink_streams.size() >= sink_int_trackers.size()) { + AttachStreamsToContext(&sink_int_trackers, &idle_sink_streams, + cis_count, &ase_ops); + } else { + std::vector sink_int_trackers_1, + sink_int_trackers_2; + // split the sink_int_trackers into 2 lists now, one list + // is equal to idle_sink_streams as physical and other + // list as virtually attached + if(idle_sink_streams.size()) { // less num of free ASEs + for (uint8_t i = 0; i < idle_sink_streams.size() ; i++) { + IntStrmTracker *tracker = sink_int_trackers.at(i); + sink_int_trackers_1.push_back(tracker); + } + AttachStreamsToContext(&sink_int_trackers_1, &idle_sink_streams, + cis_count, &ase_ops); + for (uint8_t i = idle_sink_streams.size(); + i < sink_int_trackers.size() ; i++) { + IntStrmTracker *tracker = sink_int_trackers.at(i); + sink_int_trackers_2.push_back(tracker); + } + } + + std::vector all_active_sink_streams = + audio_strms->GetStreamsByStates(valid_state_ids, + ASE_DIRECTION_SINK); + + if(sink_int_trackers_2.size()) { + AttachStreamsToContext(&sink_int_trackers_2, &all_active_sink_streams, + cis_count, &ase_ops); + } else if(sink_int_trackers.size()) { + AttachStreamsToContext(&sink_int_trackers, &all_active_sink_streams, + cis_count, &ase_ops); + } + } + } + + // do the same procedure for src trackers as well + if(src_int_trackers.size()) { + if(idle_src_streams.size() >= src_int_trackers.size()) { + AttachStreamsToContext(&src_int_trackers, &idle_src_streams, + cis_count, &ase_ops); + } else { + std::vector src_int_trackers_1, + src_int_trackers_2; + // split the src_int_trackers into 2 lists now, one list + // is equal to idle_src_streams as physical and other + // list as virtually attached + if(idle_src_streams.size()) { // less num of free ASEs + for (uint8_t i = 0; i < idle_src_streams.size() ; i++) { + IntStrmTracker *tracker = src_int_trackers.at(i); + src_int_trackers_1.push_back(tracker); + } + AttachStreamsToContext(&src_int_trackers_1, &idle_src_streams, + cis_count, &ase_ops); + for (uint8_t i = idle_src_streams.size(); + i < src_int_trackers.size() ; i++) { + IntStrmTracker *tracker = src_int_trackers.at(i); + src_int_trackers_2.push_back(tracker); + } + } + + std::vector all_active_src_streams = + audio_strms->GetStreamsByStates(valid_state_ids, + ASE_DIRECTION_SRC); + + if(src_int_trackers_2.size()) { + AttachStreamsToContext(&src_int_trackers_2, &all_active_src_streams, + cis_count, &ase_ops); + } else if(src_int_trackers.size()) { + AttachStreamsToContext(&src_int_trackers, &all_active_src_streams, + cis_count, &ase_ops); + } + } + } + + // remove all duplicate internal stream trackers + int_strm_trackers_.RemoveVirtualAttachedTrackers(); + + // if the int strm trackers size is 0 then return as + // connected immediately + if(!int_strm_trackers_.size()) { + // update the state to connected + TransitionTo(StreamTracker::kStateConnected); + break; + } + + if(!ase_ops.empty()) { + LOG(WARNING) << __func__ << ": Going For ASCS CodecConfig op"; + ascs_client->CodecConfig(ASCS_CLIENT_ID, strm_mgr_->GetAddress(), + ase_ops); + } else { + tracker_.HandleInternalDisconnect(false); + break; + } + + // refresh the sink and src trackers + sink_int_trackers = + int_strm_trackers_.GetTrackerListByDir(ASE_DIRECTION_SINK); + + src_int_trackers = + int_strm_trackers_.GetTrackerListByDir(ASE_DIRECTION_SRC); + + LOG(INFO) << __func__ << ": Num of new Sink Internal Trackers = " + << sink_int_trackers.size() + << ": Num of new Src Internal Trackers = " + << src_int_trackers.size(); + + LOG(INFO) << __func__ << ": Num of new Sink Idle Streams = " + << idle_sink_streams.size() + << ": Num of new Src Idle Streams = " + << idle_src_streams.size(); + + // update the states to connecting or other internal states + if(sink_int_trackers.size()) { + for (uint8_t i = 0; i < sink_int_trackers.size() ; i++) { + UcastAudioStream *stream = idle_sink_streams.at(i); + stream->ase_pending_cmd = AscsPendingCmd::CODEC_CONFIG_ISSUED; + } + } + if(src_int_trackers.size()) { + for (uint8_t i = 0; i < src_int_trackers.size() ; i++) { + UcastAudioStream *stream = idle_src_streams.at(i); + stream->ase_pending_cmd = AscsPendingCmd::CODEC_CONFIG_ISSUED; + } + } + } break; + + case ASCS_ASE_STATE_EVT: { + tracker_.HandleAseStateEvent(p_data, StreamControlType::Connect, + &int_strm_trackers_); + } break; + + case ASCS_ASE_OP_FAILED_EVT: { + tracker_.HandleAseOpFailedEvent(p_data); + } break; + + case BAP_TIME_OUT_EVT: { + tracker_.OnTimeout(p_data); + } break; + + default: + LOG(WARNING) << __func__ << ": Un-handled event: " + << tracker_.GetEventName(event); + break; + } + return true; +} + + +void StreamTracker::StateConnected::OnEnter() { + LOG(INFO) << __func__ << ": StreamTracker State: " << GetState(); + + UcastClientCallbacks* callbacks = strm_mgr_->GetUclientCbacks(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector strms; + std::vector stream_configs; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + PacsDiscovery *pacs_discovery_ = tracker_.GetPacsDiscovery(); + StreamControlType control_type = tracker_.GetControlType(); + std::vector conv_streams; + + if(control_type != StreamControlType::Connect && + control_type != StreamControlType::Stop && + control_type != StreamControlType::Reconfig) { + return; + } + + if(control_type == StreamControlType::Connect) { + std::vector *conn_streams = tracker_.GetConnStreams(); + for (auto it = conn_streams->begin(); it != conn_streams->end(); it++) { + StreamType type = it->stream_type; + conv_streams.push_back(type); + } + LOG(WARNING) << __func__ << ": Conn Streams Size " << conn_streams->size(); + } else if(control_type == StreamControlType::Reconfig) { + std::vector *reconf_streams = tracker_.GetReconfStreams(); + for (auto it = reconf_streams->begin(); it != reconf_streams->end();it++) { + StreamType type = it->stream_type; + conv_streams.push_back(type); + } + LOG(WARNING) << __func__ << ": Reconfig Streams size " + << reconf_streams->size(); + } else { + conv_streams = *tracker_.GetStreams(); + } + + if(control_type == StreamControlType::Connect || + control_type == StreamControlType::Reconfig) { + for (auto it = conv_streams.begin(); it != conv_streams.end(); it++) { + StreamContext *context = contexts->FindOrAddByType(*it); + UcastAudioStream *stream = audio_strms->FindByStreamType( + (*it).type, (*it).direction); + // avoid duplicate updates + if(context && pacs_discovery_ && + context->stream_state != StreamState::CONNECTED) { + StreamConfigInfo config; + memset(&config, 0, sizeof(config)); + config.stream_type = *it; + if(stream) { + config.codec_config = stream->codec_config; + config.qos_config = stream->qos_config; + context->qos_config = stream->qos_config; + } else { + config.codec_config = context->codec_config; + config.qos_config = context->req_qos_config; + context->qos_config = context->req_qos_config; + } + + //Keeping bits_per_sample as 24 always for LC3 + if (config.codec_config.codec_type == + CodecIndex::CODEC_INDEX_SOURCE_LC3) { + config.codec_config.bits_per_sample = + CodecBPS::CODEC_BITS_PER_SAMPLE_24; + } + + if(config.stream_type.direction == ASE_DIRECTION_SINK) { + config.audio_location = pacs_discovery_->sink_locations; + config.codecs_selectable = pacs_discovery_->sink_pac_records; + } else if(config.stream_type.direction == ASE_DIRECTION_SRC) { + config.audio_location = pacs_discovery_->src_locations; + config.codecs_selectable = pacs_discovery_->src_pac_records; + } + stream_configs.push_back(config); + } + } + + if(stream_configs.size()) { + callbacks->OnStreamConfig(strm_mgr_->GetAddress(), stream_configs); + } + } + + for (auto it = conv_streams.begin(); it != conv_streams.end(); it++) { + StreamContext *context = contexts->FindOrAddByType(*it); + // avoid duplicate updates + if( context->stream_state != StreamState::CONNECTED) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + state.stream_type = *it; + state.stream_state = StreamState::CONNECTED; + context->stream_state = StreamState::CONNECTED; + strms.push_back(state); + } + } + + if(strms.size()) { + callbacks->OnStreamState(strm_mgr_->GetAddress(), strms); + } +} + +void StreamTracker::StateConnected::OnExit() { + +} + +bool StreamTracker::StateConnected::ProcessEvent(uint32_t event, void* p_data) { + LOG(INFO) <<__func__ <<": BD Addr = " << strm_mgr_->GetAddress() + <<": State = " << GetState() + <<": Event = " << tracker_.GetEventName(event); + + switch (event) { + case BAP_DISCONNECT_REQ_EVT: { + tracker_.HandleDisconnect(p_data, StreamTracker::kStateConnected); + } break; + + case BAP_START_REQ_EVT: { + PacsClient *pacs_client = strm_mgr_->GetPacsClient(); + uint16_t pacs_client_id = strm_mgr_->GetPacsClientId(); + + BapStart *evt_data = (BapStart *) p_data; + + tracker_.UpdateControlType(StreamControlType::Start); + + tracker_.UpdateStreams(&evt_data->streams); + + pacs_client->GetAudioAvailability(pacs_client_id, + strm_mgr_->GetAddress()); + + tracker_.TransitionTo(StreamTracker::kStateStarting); + } break; + + case BAP_RECONFIG_REQ_EVT: { + BapReconfig *evt_data = (BapReconfig *) p_data; + + tracker_.UpdateControlType(StreamControlType::Reconfig); + tracker_.UpdateReconfStreams(&evt_data->streams); + + // check if codec reconfiguration or qos reconfiguration + PacsClient *pacs_client = strm_mgr_->GetPacsClient(); + uint16_t pacs_client_id = strm_mgr_->GetPacsClientId(); + + // pacs is already connected so initiate + // pacs service discovry now and move the state to reconfiguring + pacs_client->StartDiscovery(pacs_client_id, strm_mgr_->GetAddress()); + + tracker_.TransitionTo(StreamTracker::kStateReconfiguring); + + } break; + case PACS_CONNECTION_STATE_EVT: { + tracker_.HandlePacsConnectionEvent(p_data); + } break; + case ASCS_CONNECTION_STATE_EVT: { + tracker_.HandleAscsConnectionEvent(p_data); + } break; + case ASCS_ASE_STATE_EVT: { + AscsState *ascs = ((AscsState *) p_data); + if(ascs->ase_params.ase_state == ascs::ASE_STATE_RELEASING) { + tracker_.HandleRemoteDisconnect(ASCS_ASE_STATE_EVT, p_data, StateId()); + } else if(ascs->ase_params.ase_state == + ascs::ASE_STATE_CODEC_CONFIGURED) { + tracker_.HandleRemoteReconfig(ASCS_ASE_STATE_EVT, p_data, StateId()); + } + } break; + default: + LOG(WARNING) << __func__ << ": Un-handled event: " + << tracker_.GetEventName(event); + break; + } + return true; +} + + +void StreamTracker::StateStarting::OnEnter() { + LOG(INFO) << __func__ << ": StreamTracker State: " << GetState(); + + UcastClientCallbacks* callbacks = strm_mgr_->GetUclientCbacks(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector strms; + std::vector *start_streams = tracker_.GetStreams(); + uint8_t num_ases = 0; + + LOG(WARNING) << __func__ << ": Start Streams Size: " + << start_streams->size(); + + StreamControlType control_type = tracker_.GetControlType(); + + if(control_type != StreamControlType::Start) return; + + for (auto it = start_streams->begin(); it != start_streams->end(); it++) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + state.stream_type = *it; + state.stream_state = StreamState::STARTING; + strms.push_back(state); + StreamContext *context = contexts->FindOrAddByType(*it); + context->stream_state = StreamState::STARTING; + + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + int_strm_trackers_.FindOrAddBytrackerType(*it, + id->ase_id, id->cig_id, + id->cis_id, + context->codec_config, context->qos_config); + } + num_ases += context->stream_ids.size(); + } + callbacks->OnStreamState(strm_mgr_->GetAddress(), strms); + uint64_t tout = num_ases * + (static_cast(TimeoutVal::StartingTimeout)); + if(!tout) { + tout = static_cast(MaxTimeoutVal::StartingTimeout); + } + TimeoutReason reason = TimeoutReason::STATE_TRANSITION; + state_transition_timer = tracker_.SetTimer("StateStartingTimer", + &timeout, reason, tout); + if (state_transition_timer == nullptr) { + LOG(ERROR) << __func__ << ": StateStarting: Alarm not allocated."; + return; + } +} + +void StreamTracker::StateStarting::OnExit() { + tracker_.ClearTimer(state_transition_timer, "StateStartingTimer"); +} + +bool StreamTracker::CheckAndUpdateStreamingState( + IntStrmTrackers *int_strm_trackers) { + // to check for all internal trackers are moved to + // streaming state then update it upper layers + std::vector *all_trackers = + int_strm_trackers->GetTrackerList(); + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + uint8_t num_strms_in_streaming = 0; + + bool pending_cmds = false; + + // check if any pending commands are present + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if(stream && (stream->cis_pending_cmd != CisPendingCmd::NONE || + stream->ase_pending_cmd != AscsPendingCmd::NONE)) { + LOG(WARNING) << __func__ << ": cis_pending_cmd " + << loghex(static_cast (stream->cis_pending_cmd)); + LOG(WARNING) << __func__ << ": ase_pending_cmd " + << loghex(static_cast (stream->ase_pending_cmd)); + pending_cmds = true; + break; + } + } + + if(pending_cmds) { + LOG(WARNING) << __func__ << ": ASCS/CIS Pending commands left"; + return false; + } + + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + if(stream->ase_state == ascs::ASE_STATE_STREAMING && + stream->cis_state == CisState::ESTABLISHED) { + num_strms_in_streaming++; + } + } + + if(int_strm_trackers->size() != num_strms_in_streaming) { + LOG(WARNING) << __func__ << ": Not all streams moved to streaming"; + return false; + } + + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + stream->overall_state = StreamTracker::kStateStreaming; + } + + // all streams are moved to streaming state + TransitionTo(StreamTracker::kStateStreaming); + return true; +} + +bool StreamTracker::StateStarting::ProcessEvent(uint32_t event, void* p_data) { + LOG(INFO) <<__func__ <<": BD Addr = " << strm_mgr_->GetAddress() + <<": State = " << GetState() + <<": Event = " << tracker_.GetEventName(event); + + switch (event) { + case BAP_DISCONNECT_REQ_EVT: { + tracker_.HandleDisconnect(p_data, StreamTracker::kStateStarting); + } break; + case BAP_STOP_REQ_EVT: { + tracker_.HandleStop(p_data, StreamTracker::kStateStarting); + } break; + case BAP_STREAM_UPDATE_REQ_EVT: { + BapStreamUpdate *evt_data = (BapStreamUpdate *) p_data; + tracker_.UpdateMetaUpdateStreams(&evt_data->update_streams); + if(tracker_.HandlePacsAudioContextEvent(&pacs_contexts)) { + tracker_.HandleStreamUpdate(StreamTracker::kStateStarting); + } + } break; + case PACS_CONNECTION_STATE_EVT: { + tracker_.HandlePacsConnectionEvent(p_data); + } break; + case PACS_AUDIO_CONTEXT_RES_EVT: { + // check for all stream start requests, stream contexts are + // part of available contexts + pacs_contexts = *((PacsAvailableContexts *) p_data); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + std::vector *all_trackers = + int_strm_trackers_.GetTrackerList(); + bool ignore_event = false; + + std::vector *start_streams = tracker_.GetStreams(); + uint8_t contexts_supported = 0; + + // check if supported audio contexts has required contexts + for(auto it = start_streams->begin(); it != start_streams->end(); it++) { + if(it->direction == ASE_DIRECTION_SINK) { + if(it->audio_context & pacs_contexts.available_contexts) { + contexts_supported++; + } + } else if(it->direction == ASE_DIRECTION_SRC) { + if((static_cast(it->audio_context) << 16) & + pacs_contexts.available_contexts) { + contexts_supported++; + } + } + } + + if(contexts_supported != start_streams->size()) { + LOG(ERROR) << __func__ << ": No Matching available Contexts found"; + tracker_.TransitionTo(StreamTracker::kStateConnected); + break; + } + + for (auto it = start_streams->begin(); it != start_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(*it); + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if (stream != nullptr && stream->overall_state == + StreamTracker::kStateStarting) { + ignore_event = true; + break; + } + } + } + + if(ignore_event) break; + + // Now create the groups + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + QosConfig *qos_config = &stream->qos_config; + CisInterface *cis_intf = strm_mgr_->GetCisInterface(); + IsoHciStatus status = cis_intf->CreateCig(strm_mgr_->GetAddress(), + false, + qos_config->cig_config, + qos_config->cis_configs); + + LOG(WARNING) << __func__ << ": status: " + << loghex(static_cast(status)); + if( status == IsoHciStatus::ISO_HCI_SUCCESS) { + stream->cig_state = CigState::CREATED; + stream->cis_state = CisState::READY; + } else if (status == IsoHciStatus::ISO_HCI_IN_PROGRESS) { + stream->cis_pending_cmd = CisPendingCmd::CIG_CREATE_ISSUED; + } else { + LOG(ERROR) << __func__ << " CIG Creation Failed"; + } + } + tracker_.CheckAndSendEnable(&int_strm_trackers_); + } break; + + case CIS_GROUP_STATE_EVT: { + tracker_.HandleCigStateEvent(event, p_data, &int_strm_trackers_); + } break; + + case ASCS_CONNECTION_STATE_EVT: { + tracker_.HandleAscsConnectionEvent(p_data); + } break; + case ASCS_ASE_STATE_EVT: { + // to handle remote driven operations + // check the state and if the state is Enabling + // proceed with cis creation + AscsState *ascs = ((AscsState *) p_data); + + if(!tracker_.ValidateAseUpdate(p_data, &int_strm_trackers_, + StreamTracker::kStateStarting)) { + break; + } + + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + if (ascs->ase_params.ase_state == ascs::ASE_STATE_ENABLING) { + // change the connection state to ENABLING + CisInterface *cis_intf = strm_mgr_->GetCisInterface(); + + // check for Enabling notification is received for all ASEs + std::vector *all_trackers = + int_strm_trackers_.GetTrackerList(); + + uint8_t num_enabling_notify = 0; + + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + if(stream->ase_state == ascs::ASE_STATE_ENABLING) { + num_enabling_notify++; + } + } + + if(int_strm_trackers_.size() != num_enabling_notify) { + LOG(WARNING) << __func__ + << "Enabling notification is not received for all strms"; + break; + } + + // As it single group use cases, always single group start request + // will come to BAP layer + IsoHciStatus status; + std::vector cis_ids; + uint8_t cigId; + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (std::find(cis_ids.begin(), cis_ids.end(), + stream->cis_id) == cis_ids.end()) { + cis_ids.push_back(stream->cis_id); + cigId = stream->cig_id; + } + } + if(cis_ids.size()) { + LOG(WARNING) << __func__ << ": Going For CIS Creation "; + status = cis_intf->CreateCis(cigId, + cis_ids, + strm_mgr_->GetAddress()); + } + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + stream->cis_retry_count = 0; + if( status == IsoHciStatus::ISO_HCI_SUCCESS) { + stream->cis_state = CisState::ESTABLISHED; + } else if (status == IsoHciStatus::ISO_HCI_IN_PROGRESS) { + // change the connection state to CIS create issued + stream->cis_pending_cmd = CisPendingCmd::CIS_CREATE_ISSUED; + } else { + LOG(WARNING) << __func__ << "CIS create Failed"; + } + } + } else if (ascs->ase_params.ase_state == ascs::ASE_STATE_STREAMING) { + tracker_.CheckAndUpdateStreamingState(&int_strm_trackers_); + + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_RELEASING) { + tracker_.HandleRemoteDisconnect(ASCS_ASE_STATE_EVT, p_data, StateId()); + + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_DISABLING) { + tracker_.HandleRemoteStop(ASCS_ASE_STATE_EVT, p_data, StateId()); + } + } break; + + case ASCS_ASE_OP_FAILED_EVT: { + tracker_.HandleAseOpFailedEvent(p_data); + } break; + + case CIS_STATE_EVT: { + CisStreamState *data = (CisStreamState *) p_data; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + // check if current stream tracker is interested in this CIG update + std::vector int_trackers = + int_strm_trackers_.FindByCisId(data->cig_id, data->cis_id); + if(int_trackers.empty()) { + LOG(INFO) << __func__ << ": Not intended for this tracker"; + break; + } + + if(data->state == CisState::ESTABLISHED) { + // find out the CIS is bidirectional or from air direction + // cis, send Receiver start ready as set up data path + // is already completed during CIG creation + if(data->direction & cis::DIR_FROM_AIR) { + // setup the datapath for RX + // find out the stream here + UcastAudioStream *stream = audio_strms->FindByCisIdAndDir + (data->cig_id, data->cis_id, + cis::DIR_FROM_AIR); + LOG(WARNING) << __func__ << " DIR_FROM_AIR " + << loghex(static_cast (cis::DIR_FROM_AIR)); + + if(stream && int_strm_trackers_.FindByAseId(stream->ase_id)) { + LOG(INFO) << __func__ << ": Stream Direction " + << loghex(static_cast (stream->direction)); + + LOG(INFO) << __func__ << ": Stream ASE Id " + << loghex(static_cast (stream->ase_id)); + + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + AseStartReadyOp start_ready_op = { + .ase_id = stream->ase_id + }; + std::vector ase_ops; + ase_ops.push_back(start_ready_op); + ascs_client->StartReady(ASCS_CLIENT_ID, + strm_mgr_->GetAddress(), ase_ops); + stream->cis_state = data->state; + stream->cis_pending_cmd = CisPendingCmd::NONE; + stream->ase_pending_cmd = AscsPendingCmd::START_READY_ISSUED; + } + } + + if(data->direction & cis::DIR_TO_AIR) { + UcastAudioStream *stream = audio_strms->FindByCisIdAndDir + (data->cig_id, data->cis_id, + cis::DIR_TO_AIR); + if(stream) { + stream->cis_state = data->state; + stream->cis_pending_cmd = CisPendingCmd::NONE; + } + } + + tracker_.CheckAndUpdateStreamingState(&int_strm_trackers_); + + } else if (data->state == CisState::READY) { // CIS creation failed + CisInterface *cis_intf = strm_mgr_->GetCisInterface(); + UcastAudioStream *stream = audio_strms->FindByCisIdAndDir + (data->cig_id, data->cis_id, + data->direction); + if(stream && stream->cis_retry_count < 2) { + std::vector cisIds = {stream->cis_id}; + LOG(WARNING) << __func__ << ": Going For Retrial of CIS Creation "; + IsoHciStatus status = cis_intf->CreateCis( + stream->cig_id, + cisIds, + strm_mgr_->GetAddress()); + + if( status == IsoHciStatus::ISO_HCI_SUCCESS) { + stream->cis_state = CisState::ESTABLISHED; + tracker_.CheckAndUpdateStreamingState(&int_strm_trackers_); + } else if (status == IsoHciStatus::ISO_HCI_IN_PROGRESS) { + // change the connection state to CIS create issued + stream->cis_retry_count++; + stream->cis_pending_cmd = CisPendingCmd::CIS_CREATE_ISSUED; + } else { + stream->cis_retry_count = 0; + LOG(WARNING) << __func__ << "CIS create Failed"; + } + } else { + if(stream) { + stream->cis_retry_count = 0; + stream->cis_state = data->state; + stream->cis_pending_cmd = CisPendingCmd::NONE; + } + } + } else { // transient states + UcastAudioStream *stream = audio_strms->FindByCisIdAndDir + (data->cig_id, data->cis_id, + data->direction); + if(stream) stream->cis_state = data->state; + } + } break; + + case BAP_TIME_OUT_EVT: { + tracker_.OnTimeout(p_data); + } break; + + default: + LOG(WARNING) << __func__ << ": Un-handled event: " + << tracker_.GetEventName(event); + break; + } + return true; +} + +void StreamTracker::StateUpdating::OnEnter() { + LOG(INFO) << __func__ << ": StreamTracker State: " << GetState(); + + UcastClientCallbacks* callbacks = strm_mgr_->GetUclientCbacks(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector strms; + std::vector *update_streams = tracker_.GetMetaUpdateStreams(); + uint8_t num_ases = 0; + + LOG(WARNING) << __func__ << ": Start Streams Size " + << update_streams->size(); + + StreamControlType control_type = tracker_.GetControlType(); + + if(control_type != StreamControlType::UpdateStream) return; + + for (auto it = update_streams->begin(); it != update_streams->end(); it++) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + state.stream_type = it->stream_type; + state.stream_state = StreamState::UPDATING; + strms.push_back(state); + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + context->stream_state = StreamState::UPDATING; + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + int_strm_trackers_.FindOrAddBytrackerType(it->stream_type, + id->ase_id, id->cig_id, + id->cis_id, + context->codec_config, context->qos_config); + } + num_ases += context->stream_ids.size(); + } + callbacks->OnStreamState(strm_mgr_->GetAddress(), strms); + + uint64_t tout = num_ases * + (static_cast(TimeoutVal::UpdatingTimeout)); + if(!tout) { + tout = static_cast(MaxTimeoutVal::UpdatingTimeout); + } + TimeoutReason reason = TimeoutReason::STATE_TRANSITION; + state_transition_timer = tracker_.SetTimer("StateUpdatingTimer", + &timeout, reason, tout); + if (state_transition_timer == nullptr) { + LOG(ERROR) << __func__ << ": StateUpdating: Alarm not allocated."; + return; + } +} + +void StreamTracker::StateUpdating::OnExit() { + tracker_.ClearTimer(state_transition_timer, "StateUpdatingTimer"); +} + +bool StreamTracker::StateUpdating::ProcessEvent(uint32_t event, void* p_data) { + LOG(INFO) <<__func__ <<": BD Addr = " << strm_mgr_->GetAddress() + <<": State = " << GetState() + <<": Event = " << tracker_.GetEventName(event); + + switch (event) { + case BAP_DISCONNECT_REQ_EVT: { + tracker_.HandleDisconnect(p_data, StreamTracker::kStateUpdating); + } break; + case BAP_STOP_REQ_EVT: { + tracker_.HandleStop(p_data, StreamTracker::kStateUpdating); + } break; + case PACS_CONNECTION_STATE_EVT: { + tracker_.HandlePacsConnectionEvent(p_data); + } break; + case ASCS_CONNECTION_STATE_EVT: { + tracker_.HandleAscsConnectionEvent(p_data); + } break; + case PACS_AUDIO_CONTEXT_RES_EVT: { + // check for all stream start requests, stream contexts are + // part of available contexts + PacsAvailableContexts *pacs_contexts = (PacsAvailableContexts *) p_data; + if(!tracker_.HandlePacsAudioContextEvent(pacs_contexts) || + !tracker_.HandleStreamUpdate(StreamTracker::kStateUpdating)) { + tracker_.TransitionTo(StreamTracker::kStateStreaming); + } + } break; + case ASCS_ASE_STATE_EVT: { + AscsState *ascs = ((AscsState *) p_data); + if(!tracker_.ValidateAseUpdate(p_data, &int_strm_trackers_, + StreamTracker::kStateUpdating)) { + break; + } + if(ascs->ase_params.ase_state == ascs::ASE_STATE_STREAMING) { + tracker_.CheckAndUpdateStreamingState(&int_strm_trackers_); + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_RELEASING) { + tracker_.HandleRemoteDisconnect(ASCS_ASE_STATE_EVT, p_data, StateId()); + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_DISABLING) { + tracker_.HandleRemoteStop(ASCS_ASE_STATE_EVT, p_data, StateId()); + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_QOS_CONFIGURED) { + // this can happen when CIS is lost and detected on remote side + // first so it will immediately transition to QOS configured. + tracker_.HandleAbruptStop(ASCS_ASE_STATE_EVT, p_data); + } + } break; + case CIS_STATE_EVT: { + // handle sudden CIS Disconnection + tracker_.HandleCisEventsInStreaming(p_data); + } break; + default: + LOG(WARNING) << __func__ << ": Un-handled event: " + << tracker_.GetEventName(event); + break; + } + return true; +} + +void StreamTracker::StateStreaming::OnEnter() { + LOG(INFO) << __func__ << ": StreamTracker State: " << GetState(); + + UcastClientCallbacks* callbacks = strm_mgr_->GetUclientCbacks(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector strms; + std::vector *start_streams = tracker_.GetStreams(); + std::vector *update_streams = tracker_.GetMetaUpdateStreams(); + LOG(WARNING) << __func__ << ": Start Streams Size " + << start_streams->size(); + + LOG(WARNING) << __func__ << ": Update Streams Size " + << update_streams->size(); + + StreamControlType control_type = tracker_.GetControlType(); + + if(control_type == StreamControlType::Start) { + for (auto it = start_streams->begin(); it != start_streams->end(); it++) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + state.stream_type = *it; + state.stream_state = StreamState::STREAMING; + strms.push_back(state); + StreamContext *context = contexts->FindOrAddByType(*it); + context->stream_state = StreamState::STREAMING; + } + } else if(control_type == StreamControlType::UpdateStream) { + for (auto it = update_streams->begin(); it != update_streams->end(); it++) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + state.stream_type = it->stream_type; + state.stream_state = StreamState::STREAMING; + strms.push_back(state); + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + context->stream_state = StreamState::STREAMING; + } + } + if(strms.size()) { + callbacks->OnStreamState(strm_mgr_->GetAddress(), strms); + } +} + +void StreamTracker::StateStreaming::OnExit() { + +} + +bool StreamTracker::StateStreaming::ProcessEvent(uint32_t event, void* p_data) { + LOG(INFO) <<__func__ <<": BD Addr = " << strm_mgr_->GetAddress() + <<": State = " << GetState() + <<": Event = " << tracker_.GetEventName(event); + + switch (event) { + case BAP_DISCONNECT_REQ_EVT: { + tracker_.HandleDisconnect(p_data, StreamTracker::kStateStreaming); + } break; + case BAP_STOP_REQ_EVT: { + tracker_.HandleStop(p_data, StreamTracker::kStateStreaming); + } break; + case BAP_STREAM_UPDATE_REQ_EVT: { + PacsClient *pacs_client = strm_mgr_->GetPacsClient(); + uint16_t pacs_client_id = strm_mgr_->GetPacsClientId(); + BapStreamUpdate *evt_data = (BapStreamUpdate *) p_data; + tracker_.UpdateControlType(StreamControlType::UpdateStream); + tracker_.UpdateMetaUpdateStreams(&evt_data->update_streams); + pacs_client->GetAudioAvailability(pacs_client_id, + strm_mgr_->GetAddress()); + tracker_.TransitionTo(StreamTracker::kStateUpdating); + } break; + case PACS_CONNECTION_STATE_EVT: { + tracker_.HandlePacsConnectionEvent(p_data); + } break; + case ASCS_CONNECTION_STATE_EVT: { + tracker_.HandleAscsConnectionEvent(p_data); + } break; + case ASCS_ASE_STATE_EVT: { + AscsState *ascs = ((AscsState *) p_data); + uint8_t ase_id = ascs->ase_params.ase_id; + // find out the stream for the given ase id + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + UcastAudioStream *stream = audio_strms->FindByAseId(ase_id); + if (stream) { + stream->ase_state = ascs->ase_params.ase_state; + stream->ase_params = ascs->ase_params; + stream->ase_pending_cmd = AscsPendingCmd::NONE; + if(ascs->ase_params.ase_state == ascs::ASE_STATE_RELEASING) { + tracker_.HandleRemoteDisconnect(ASCS_ASE_STATE_EVT, p_data, StateId()); + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_DISABLING) { + tracker_.HandleRemoteStop(ASCS_ASE_STATE_EVT, p_data, StateId()); + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_QOS_CONFIGURED){ + // this can happen when CIS is lost and detected on remote side + // first so it will immediately transition to QOS configured. + tracker_.HandleAbruptStop(ASCS_ASE_STATE_EVT, p_data); + } + } + } break; + case CIS_STATE_EVT: { + // handle sudden CIS Disconnection + tracker_.HandleCisEventsInStreaming(p_data); + } break; + default: + LOG(WARNING) << __func__ << ": Un-handled event: " + << tracker_.GetEventName(event); + break; + } + return true; +} + +void StreamTracker::StateStopping::OnEnter() { + LOG(INFO) << __func__ << ": StreamTracker State: " << GetState(); + + UcastClientCallbacks* callbacks = strm_mgr_->GetUclientCbacks(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector strms; + std::vector *stop_streams = tracker_.GetStreams(); + uint8_t num_ases = 0; + + LOG(WARNING) << __func__ << ": Stop Streams Size : " + << stop_streams->size(); + StreamControlType control_type = tracker_.GetControlType(); + + if(control_type != StreamControlType::Stop) return; + + for (auto it = stop_streams->begin(); + it != stop_streams->end(); it++) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + state.stream_type = *it; + state.stream_state = StreamState::STOPPING; + strms.push_back(state); + StreamContext *context = contexts->FindOrAddByType(*it); + context->stream_state = StreamState::STOPPING; + + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + int_strm_trackers_.FindOrAddBytrackerType(*it, + id->ase_id, id->cig_id, + id->cis_id, + context->codec_config, context->qos_config); + } + num_ases += context->stream_ids.size(); + } + callbacks->OnStreamState(strm_mgr_->GetAddress(), strms); + + uint64_t tout = num_ases * + (static_cast(TimeoutVal::StoppingTimeout)); + if(!tout) { + tout = static_cast(MaxTimeoutVal::StoppingTimeout); + } + + TimeoutReason reason = TimeoutReason::STATE_TRANSITION; + state_transition_timer = tracker_.SetTimer("StateStoppingTimer", + &timeout, reason, tout); + if (state_transition_timer == nullptr) { + LOG(ERROR) << __func__ << ": StateStopping: Alarm not allocated."; + return; + } +} + +void StreamTracker::StateStopping::OnExit() { + tracker_.ClearTimer(state_transition_timer, "StateStoppingTimer"); +} + +bool StreamTracker::StateStopping::TerminateCisAndCig(UcastAudioStream *stream) { + + CisInterface *cis_intf = strm_mgr_->GetCisInterface(); + uint8_t num_strms_in_qos_configured = 0; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + std::vector all_trackers = + int_strm_trackers_.FindByCigIdAndDir(stream->cig_id, + stream->direction); + + for(auto i = all_trackers.begin(); i != all_trackers.end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + if(stream->ase_state == ascs::ASE_STATE_QOS_CONFIGURED) { + num_strms_in_qos_configured++; + } + } + + if(all_trackers.size() != num_strms_in_qos_configured) { + LOG(WARNING) << __func__ << "Not All Streams Moved to QOS Configured"; + return false; + } + + for (auto i = all_trackers.begin(); i != all_trackers.end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + if(stream->cis_pending_cmd == CisPendingCmd::NONE && + stream->cis_state == CisState::ESTABLISHED && + stream->ase_state == ascs::ASE_STATE_QOS_CONFIGURED) { + LOG(WARNING) << __func__ << ": Going For CIS Disconnect "; + IsoHciStatus status = cis_intf->DisconnectCis(stream->cig_id, + stream->cis_id, + stream->direction); + if(status == IsoHciStatus::ISO_HCI_SUCCESS) { + stream->cis_pending_cmd = CisPendingCmd::NONE; + stream->cis_state = CisState::READY; + } else if(status == IsoHciStatus::ISO_HCI_IN_PROGRESS) { + stream->cis_pending_cmd = CisPendingCmd::CIS_DESTROY_ISSUED; + } else { + LOG(WARNING) << __func__ << ": CIS Disconnect Failed"; + } + } + + if(stream->cis_state == CisState::READY) { + if(stream->cig_state == CigState::CREATED && + stream->cis_pending_cmd == CisPendingCmd::NONE) { + LOG(WARNING) << __func__ << ": Going For CIG Removal"; + IsoHciStatus status = cis_intf->RemoveCig(strm_mgr_->GetAddress(), + stream->cig_id); + if( status == IsoHciStatus::ISO_HCI_SUCCESS) { + stream->cig_state = CigState::INVALID; + stream->cis_state = CisState::INVALID; + } else if (status == IsoHciStatus::ISO_HCI_IN_PROGRESS) { + stream->cis_pending_cmd = CisPendingCmd::CIG_REMOVE_ISSUED; + } else { + LOG(WARNING) << __func__ << ": CIG removal Failed"; + } + } + } + } + return true; +} + +bool StreamTracker::StateStopping::CheckAndUpdateStoppedState() { + // to check for all internal trackers are moved to + // cis destroyed state then update the callback + uint8_t num_strms_in_stopping = 0; + bool pending_cmds = false; + + std::vector *all_trackers = + int_strm_trackers_.GetTrackerList(); + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + // check if any pending commands are present + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if(stream && (stream->cis_pending_cmd != CisPendingCmd::NONE || + stream->ase_pending_cmd != AscsPendingCmd::NONE)) { + pending_cmds = true; + break; + } + } + + if(pending_cmds) return false; + + for(auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if(stream && (stream->cig_state == CigState::IDLE || + stream->cig_state == CigState::INVALID) && + (stream->cis_state == CisState::READY || + stream->cis_state == CisState::INVALID) && + stream->ase_state == ascs::ASE_STATE_QOS_CONFIGURED) { + num_strms_in_stopping++; + } + } + + if(int_strm_trackers_.size() != num_strms_in_stopping) { + LOG(WARNING) << __func__ << "Not All Streams Moved to Stopped State"; + return false; + } + + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + stream->overall_state = StreamTracker::kStateConnected; + } + + tracker_.TransitionTo(StreamTracker::kStateConnected); + return true; +} + +bool StreamTracker::StateStopping::ProcessEvent(uint32_t event, void* p_data) { + LOG(INFO) <<__func__ <<": BD Addr = " << strm_mgr_->GetAddress() + <<": State = " << GetState() + <<": Event = " << tracker_.GetEventName(event); + + switch (event) { + case BAP_DISCONNECT_REQ_EVT:{ + tracker_.HandleDisconnect(p_data, StreamTracker::kStateStopping); + } break; + case PACS_CONNECTION_STATE_EVT: { + tracker_.HandlePacsConnectionEvent(p_data); + } break; + case ASCS_CONNECTION_STATE_EVT: { + tracker_.HandleAscsConnectionEvent(p_data); + } break; + case ASCS_ASE_STATE_EVT: { + // to handle remote driven operations + AscsState *ascs = ((AscsState *) p_data); + + if(!tracker_.ValidateAseUpdate(p_data, &int_strm_trackers_, + StreamTracker::kStateStopping)) { + break; + } + + // find out the stream for the given ase id + uint8_t ase_id = ascs->ase_params.ase_id; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + UcastAudioStream *stream = audio_strms->FindByAseId(ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + break; + } + + if(ascs->ase_params.ase_state == ascs::ASE_STATE_DISABLING) { + if(stream->direction & cis::DIR_FROM_AIR) { + LOG(INFO) << __func__ << " Sending Stop Ready "; + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + AseStopReadyOp stop_ready_op = { + .ase_id = stream->ase_id + }; + std::vector ase_ops; + ase_ops.push_back(stop_ready_op); + ascs_client->StopReady(ASCS_CLIENT_ID, + strm_mgr_->GetAddress(), ase_ops); + stream->ase_pending_cmd = AscsPendingCmd::STOP_READY_ISSUED; + } else { + LOG(ERROR) << __func__ << ": Invalid State transition to Disabling" + << ": ASE Id = " << loghex(ase_id); + } + + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_QOS_CONFIGURED) { + + stream->ase_pending_cmd = AscsPendingCmd::NONE; + // stopped state then issue CIS disconnect + TerminateCisAndCig(stream); + CheckAndUpdateStoppedState(); + + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_RELEASING) { + tracker_.HandleRemoteDisconnect(ASCS_ASE_STATE_EVT, p_data, StateId()); + } + } break; + + case ASCS_ASE_OP_FAILED_EVT: { + tracker_.HandleAseOpFailedEvent(p_data); + } break; + + case CIS_STATE_EVT: { + CisStreamState *data = (CisStreamState *) p_data; + CisInterface *cis_intf = strm_mgr_->GetCisInterface(); + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + // check if current stream tracker is interested in this CIG update + std::vector int_trackers = + int_strm_trackers_.FindByCisId(data->cig_id, data->cis_id); + if(int_trackers.empty()) { + LOG(INFO) << __func__ << "Not intended for this tracker"; + break; + } + if(data->state == CisState::ESTABLISHED) { + for(auto it = directions.begin(); it != directions.end(); ++it) { + if(data->direction & *it) { + // find out the stream here + UcastAudioStream *stream = audio_strms->FindByCisIdAndDir + (data->cig_id, data->cis_id, *it); + if(stream) { + stream->cis_state = data->state; + stream->cis_pending_cmd = CisPendingCmd::NONE; + if(int_strm_trackers_.FindByAseId(stream->ase_id)) { + TerminateCisAndCig(stream); + } + } + } + } + CheckAndUpdateStoppedState(); + + } else if(data->state == CisState::READY) { + for(auto it = directions.begin(); it != directions.end(); ++it) { + if(data->direction & *it) { + // find out the stream here + UcastAudioStream *stream = audio_strms->FindByCisIdAndDir + (data->cig_id, data->cis_id, *it); + if(stream) { + stream->cis_state = data->state; + stream->cis_pending_cmd = CisPendingCmd::NONE; + if(stream->cig_state == CigState::CREATED && + stream->cis_pending_cmd == CisPendingCmd::NONE) { + IsoHciStatus status = cis_intf->RemoveCig( + strm_mgr_->GetAddress(), + stream->cig_id); + if( status == IsoHciStatus::ISO_HCI_SUCCESS) { + stream->cig_state = CigState::INVALID; + stream->cis_state = CisState::INVALID; + } else if (status == IsoHciStatus::ISO_HCI_IN_PROGRESS) { + stream->cis_pending_cmd = CisPendingCmd::CIG_REMOVE_ISSUED; + } else { + LOG(WARNING) << __func__ << ": CIG removal Failed"; + } + } + } + } + } + CheckAndUpdateStoppedState(); + } else { // transient states + for(auto it = directions.begin(); it != directions.end(); ++it) { + if(data->direction & *it) { + // find out the stream here + UcastAudioStream *stream = audio_strms->FindByCisIdAndDir + (data->cig_id, data->cis_id, *it); + if(stream) stream->cis_state = data->state; + } + } + } + } break; + + case CIS_GROUP_STATE_EVT: { + CisGroupState *data = ((CisGroupState *) p_data); + // check if current stream tracker is interested in this CIG update + std::vector int_trackers = + int_strm_trackers_.FindByCigId(data->cig_id); + if(int_trackers.empty()) { + LOG(INFO) << __func__ << "Not intended for this tracker"; + break; + } + + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + if(data->state == CigState::CREATED) { + for (auto i = int_trackers.begin(); i != int_trackers.end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (stream) { + // check if this is a CIG created event due to CIG create + // issued during starting state + stream->cis_pending_cmd = CisPendingCmd::NONE; + stream->cig_state = data->state; + stream->cis_state = CisState::READY; + TerminateCisAndCig(stream); + } + } + CheckAndUpdateStoppedState(); + + } else if(data->state == CigState::IDLE) { + for (auto i = int_trackers.begin(); i != int_trackers.end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (stream) { + stream->cig_state = CigState::INVALID; + stream->cis_state = CisState::INVALID; + stream->cis_pending_cmd = CisPendingCmd::NONE; + } + } + CheckAndUpdateStoppedState(); + } + } break; + case BAP_TIME_OUT_EVT: { + tracker_.OnTimeout(p_data); + } break; + + default: + LOG(WARNING) << __func__ << ": Un-handled event: " + << tracker_.GetEventName(event); + break; + } + return true; +} + +bool StreamTracker::StateDisconnecting::TerminateGattConnection() { + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + GattPendingData *gatt_pending_data = strm_mgr_->GetGattPendingData(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector *all_contexts = contexts->GetAllContexts(); + bool any_context_active = false; + bool disc_issued = false; + std::vector ids = { StreamTracker::kStateIdle }; + std::vector idle_streams = + audio_strms->GetStreamsByStates( + ids, ASE_DIRECTION_SINK | ASE_DIRECTION_SRC); + + LOG(WARNING) << __func__ <<": Total Streams size: " << audio_strms->size() + <<": Idle Streams size: " << idle_streams.size(); + + // check if any of the contexts stream state is connected + for (auto it = all_contexts->begin(); it != all_contexts->end(); it++) { + if((*it)->stream_state != StreamState::DISCONNECTING && + (*it)->stream_state != StreamState::DISCONNECTED) { + LOG(INFO) << __func__ <<": Other contexts are active,not to disc Gatt"; + any_context_active = true; + break; + } + } + if(!any_context_active && + (!audio_strms->size() || audio_strms->size() == idle_streams.size())) { + + // check if gatt connection can be tear down for ascs & pacs clients + // all streams are in idle state + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + PacsClient *pacs_client = strm_mgr_->GetPacsClient(); + uint16_t pacs_client_id = strm_mgr_->GetPacsClientId(); + + ConnectionState pacs_state = strm_mgr_->GetPacsState(); + if((pacs_state == ConnectionState::CONNECTED && + gatt_pending_data->pacs_pending_cmd == GattPendingCmd::NONE) || + (gatt_pending_data->pacs_pending_cmd == + GattPendingCmd::GATT_CONN_PENDING)) { + LOG(WARNING) << __func__ << " Issue PACS server disconnect "; + pacs_client->Disconnect(pacs_client_id, strm_mgr_->GetAddress()); + gatt_pending_data->pacs_pending_cmd = GattPendingCmd::GATT_DISC_PENDING; + disc_issued = true; + } + + GattState ascs_state = strm_mgr_->GetAscsState(); + if((ascs_state == GattState::CONNECTED && + gatt_pending_data->ascs_pending_cmd == GattPendingCmd::NONE) || + (gatt_pending_data->ascs_pending_cmd == + GattPendingCmd::GATT_CONN_PENDING)) { + LOG(WARNING) << __func__ << " Issue ASCS server disconnect "; + ascs_client->Disconnect(ASCS_CLIENT_ID, strm_mgr_->GetAddress()); + gatt_pending_data->ascs_pending_cmd = GattPendingCmd::GATT_DISC_PENDING; + disc_issued = true; + } + } + return disc_issued; +} + +void StreamTracker::StateDisconnecting::OnEnter() { + LOG(INFO) << __func__ << ": StreamTracker State: " << GetState(); + + // check the previous state i.e connecting, starting, stopping + // or reconfiguring + + UcastClientCallbacks* callbacks = strm_mgr_->GetUclientCbacks(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector strms; + uint8_t num_ases = 0; + + std::vector *disc_streams = tracker_.GetStreams(); + LOG(WARNING) << __func__ << ": Disconection Streams Size: " + << disc_streams->size(); + + StreamControlType control_type = tracker_.GetControlType(); + + if(control_type != StreamControlType::Disconnect) { + return; + } + + for (auto it = disc_streams->begin(); it != disc_streams->end(); it++) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + state.stream_type = *it; + state.stream_state = StreamState::DISCONNECTING; + strms.push_back(state); + StreamContext *context = contexts->FindOrAddByType(*it); + context->stream_state = StreamState::DISCONNECTING; + if(context->connection_state == IntConnectState::ASCS_DISCOVERED) { + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + bool can_be_disconnected = tracker_. + StreamCanbeDisconnected(context, id->ase_id); + if(can_be_disconnected) { + int_strm_trackers_.FindOrAddBytrackerType(*it, + id->ase_id, id->cig_id, + id->cis_id, + context->codec_config, context->qos_config); + } + } + } + num_ases += context->stream_ids.size(); + } + callbacks->OnStreamState(strm_mgr_->GetAddress(), strms); + + uint64_t tout = num_ases * + (static_cast(TimeoutVal::DisconnectingTimeout)); + if(!tout ||tout > static_cast(MaxTimeoutVal::DisconnectingTimeout)) { + tout = static_cast(MaxTimeoutVal::DisconnectingTimeout); + } + + TimeoutReason reason = TimeoutReason::STATE_TRANSITION; + state_transition_timer = tracker_.SetTimer("StateDisconnectingTimer", + &timeout, reason, tout); + if (state_transition_timer == nullptr) { + LOG(ERROR) << __func__ << ": StateDisconnecting: Alarm not allocated."; + return; + } + + bool gatt_disc_pending = TerminateGattConnection(); + // check if there are no internal stream trackers, then update to + // upper layer as completely disconnected + if(!int_strm_trackers_.size() && !gatt_disc_pending) { + tracker_.TransitionTo(StreamTracker::kStateIdle); + } +} + +void StreamTracker::StateDisconnecting::ContinueDisconnection + (UcastAudioStream *stream) { + + // check ase state, return if state is not releasing or + if(stream->ase_state != ascs::ASE_STATE_IDLE && + stream->ase_state != ascs::ASE_STATE_CODEC_CONFIGURED && + stream->ase_state != ascs::ASE_STATE_RELEASING) { + LOG(WARNING) << __func__ << " Return as ASE is not moved to Right state"; + return; + } + + CisInterface *cis_intf = strm_mgr_->GetCisInterface(); + + // check if there is no pending CIS command then issue relevant + // CIS command based on CIS state + if(stream->cis_pending_cmd != CisPendingCmd::NONE) { + LOG(INFO) << __func__ << ": cis_pending_cmd is not NONE "; + return; + } + + if(stream->cis_state == CisState::ESTABLISHED) { + LOG(WARNING) << __func__ << ": Going For CIS disconnect "; + IsoHciStatus status = cis_intf->DisconnectCis(stream->cig_id, + stream->cis_id, + stream->direction); + if(status == IsoHciStatus::ISO_HCI_SUCCESS) { + stream->cis_pending_cmd = CisPendingCmd::NONE; + stream->cis_state = CisState::READY; + } else if(status == IsoHciStatus::ISO_HCI_IN_PROGRESS) { + stream->cis_pending_cmd = CisPendingCmd::CIS_DESTROY_ISSUED; + } else { + LOG(WARNING) << __func__ << ": CIS Disconnect Failed"; + } + } + + if(stream->cis_state == CisState::READY) { + if(stream->cig_state == CigState::CREATED && + stream->cis_pending_cmd == CisPendingCmd::NONE) { + LOG(WARNING) << __func__ << ": Going For CIG Removal"; + IsoHciStatus status = cis_intf->RemoveCig(strm_mgr_->GetAddress(), + stream->cig_id); + if( status == IsoHciStatus::ISO_HCI_SUCCESS) { + stream->cig_state = CigState::INVALID; + stream->cis_state = CisState::INVALID; + } else if (status == IsoHciStatus::ISO_HCI_IN_PROGRESS) { + stream->cis_pending_cmd = CisPendingCmd::CIG_REMOVE_ISSUED; + } else { + LOG(WARNING) << __func__ << ": CIG removal Failed"; + } + } + } +} + +bool StreamTracker::StateDisconnecting::CheckAndUpdateDisconnectedState() { + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + bool pending_cmds = false; + + std::vector *all_trackers = + int_strm_trackers_.GetTrackerList(); + + // check if any pending commands are present + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if(stream && (stream->cis_pending_cmd != CisPendingCmd::NONE || + stream->ase_pending_cmd != AscsPendingCmd::NONE)) { + pending_cmds = true; + break; + } + } + + if(pending_cmds) { + LOG(WARNING) << __func__ << " Pending ASCS/CIS cmds present "; + return false; + } + + TerminateGattConnection(); + + // check it needs to wait for ASCS & PACS disconnection also + GattPendingData *gatt_pending_data = strm_mgr_->GetGattPendingData(); + if(gatt_pending_data->ascs_pending_cmd != GattPendingCmd::NONE || + gatt_pending_data->pacs_pending_cmd != GattPendingCmd::NONE) { + LOG(WARNING) << __func__ << " Pending Gatt disc present "; + return false; + } + + // check for all trackers moved to idle and + // CIG state is idle if so update it as streams are disconnected + uint8_t num_strms_disconnected = 0; + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + if((stream->ase_state == ascs::ASE_STATE_IDLE || + stream->ase_state == ascs::ASE_STATE_CODEC_CONFIGURED) && + (stream->cig_state == CigState::IDLE || + stream->cig_state == CigState::INVALID) && + (stream->cis_state == CisState::READY || + stream->cis_state == CisState::INVALID)) { + num_strms_disconnected++; + } + } + + if(int_strm_trackers_.size() != num_strms_disconnected) { + LOG(WARNING) << __func__ << "Not disconnected for all streams"; + return false; + } else { + LOG(ERROR) << __func__ << "Disconnected for all streams"; + } + + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (stream) { + stream->overall_state = StreamTracker::kStateIdle; + } + } + + // update the state to idle + tracker_.TransitionTo(StreamTracker::kStateIdle); + return true; +} + +void StreamTracker::StateDisconnecting::OnExit() { + tracker_.ClearTimer(state_transition_timer, "StateDisconnectingTimer"); +} + +bool StreamTracker::StateDisconnecting::ProcessEvent(uint32_t event, + void* p_data) { + LOG(INFO) <<__func__ <<": BD Addr = " << strm_mgr_->GetAddress() + <<": State = " << GetState() + <<": Event = " << tracker_.GetEventName(event); + + switch (event) { + case PACS_CONNECTION_STATE_EVT: { + PacsConnectionState *pacs_state = (PacsConnectionState *) p_data; + GattPendingData *gatt_pending_data = strm_mgr_->GetGattPendingData(); + if(pacs_state->state == ConnectionState::DISCONNECTED) { + gatt_pending_data->pacs_pending_cmd = GattPendingCmd::NONE; + } + CheckAndUpdateDisconnectedState(); + } break; + case ASCS_CONNECTION_STATE_EVT: { + AscsConnectionState *ascs_state = (AscsConnectionState *) p_data; + GattPendingData *gatt_pending_data = strm_mgr_->GetGattPendingData(); + if(ascs_state->state == GattState::DISCONNECTED) { + // make all streams ASE state to idle so that further processing + // can happen + gatt_pending_data->ascs_pending_cmd = GattPendingCmd::NONE; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + std::vector *strms_list = + audio_strms->GetAllStreams(); + + for (auto it = strms_list->begin(); it != strms_list->end(); it++) { + + (*it)->ase_state = ascs::ASE_STATE_IDLE; + (*it)->ase_pending_cmd = AscsPendingCmd::NONE; + (*it)->overall_state = StreamTracker::kStateIdle; + ContinueDisconnection(*it); + } + } + CheckAndUpdateDisconnectedState(); + } break; + case ASCS_ASE_STATE_EVT: { // to handle remote driven operations + + // check for state releasing + // based on prev state do accordingly + AscsState *ascs = ((AscsState *) p_data); + + uint8_t ase_id = ascs->ase_params.ase_id; + + // check if current stream tracker is interested in this ASE update + if(int_strm_trackers_.FindByAseId(ase_id) + == nullptr) { + LOG(INFO) << __func__ << "Not intended for this tracker"; + break; + } + + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + UcastAudioStream *stream = audio_strms->FindByAseId(ase_id); + + if(stream == nullptr) { + break; + } else { + stream->ase_state = ascs->ase_params.ase_state; + stream->ase_params = ascs->ase_params; + } + + if(ascs->ase_params.ase_state == ascs::ASE_STATE_RELEASING) { + // find out the stream for the given ase id + LOG(WARNING) << __func__ << " ASE Id " << loghex(ase_id); + stream->ase_pending_cmd = AscsPendingCmd::NONE; + ContinueDisconnection(stream); + + } else if( ascs->ase_params.ase_state == + ascs::ASE_STATE_CODEC_CONFIGURED) { + // check if this is a codec config notification due to codec config + // issued during connecting state + if((tracker_.PreviousStateId() == StreamTracker::kStateConnecting || + tracker_.PreviousStateId() == StreamTracker::kStateReconfiguring) && + stream->ase_pending_cmd == AscsPendingCmd::CODEC_CONFIG_ISSUED && + stream->ase_state == ascs::ASE_STATE_CODEC_CONFIGURED) { + // mark int conn state as codec configured and issue release command + std::vector ase_ops; + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + AseReleaseOp release_op = { + .ase_id = stream->ase_id + }; + ase_ops.push_back(release_op); + stream->ase_pending_cmd = AscsPendingCmd::RELEASE_ISSUED; + ascs_client->Release(ASCS_CLIENT_ID, + strm_mgr_->GetAddress(), ase_ops); + break; // break the switch case + } else { + stream->ase_pending_cmd = AscsPendingCmd::NONE; + stream->overall_state = StreamTracker::kStateIdle; + ContinueDisconnection(stream); + CheckAndUpdateDisconnectedState(); + } + } else if(ascs->ase_params.ase_state == ascs::ASE_STATE_IDLE) { + // check for all trackers moved to idle and + // CIG state is idle if so update it as streams are disconnected + stream->ase_pending_cmd = AscsPendingCmd::NONE; + stream->overall_state = StreamTracker::kStateIdle; + ContinueDisconnection(stream); + CheckAndUpdateDisconnectedState(); + } + } break; + + case ASCS_ASE_OP_FAILED_EVT: { + AscsOpFailed *ascs_op = ((AscsOpFailed *) p_data); + std::vector *ase_list = &ascs_op->ase_list; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + + if(ascs_op->ase_op_id == ascs::AseOpId::RELEASE) { + // treat it like internal failure + for (auto i = ase_list->begin(); i != ase_list->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((i)->ase_id); + if(stream) { + stream->ase_state = ascs::ASE_STATE_IDLE; + stream->ase_pending_cmd = AscsPendingCmd::NONE; + stream->overall_state = StreamTracker::kStateIdle; + ContinueDisconnection(stream); + } + } + CheckAndUpdateDisconnectedState(); + } + } break; + + case CIS_GROUP_STATE_EVT: { + // check if the associated CIG state is created + // if so go for QOS config operation + CisGroupState *data = ((CisGroupState *) p_data); + + // check if current stream tracker is interested in this CIG update + std::vector int_trackers = + int_strm_trackers_.FindByCigId(data->cig_id); + if(int_trackers.empty()) { + LOG(INFO) << __func__ << "Not intended for this tracker"; + break; + } + + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + if(data->state == CigState::CREATED) { + for (auto i = int_trackers.begin(); i != int_trackers.end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (stream) { + stream->cis_pending_cmd = CisPendingCmd::NONE; + stream->cig_state = data->state; + stream->cis_state = CisState::READY; + // check if this is a CIG created event due to CIG create + // issued during starting state + ContinueDisconnection(stream); + } + } + CheckAndUpdateDisconnectedState(); + + } else if(data->state == CigState::IDLE) { + for (auto i = int_trackers.begin(); i != int_trackers.end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + stream->cig_state = CigState::INVALID; + stream->cis_state = CisState::INVALID; + stream->cis_pending_cmd = CisPendingCmd::NONE; + } + CheckAndUpdateDisconnectedState(); + } + } break; + case CIS_STATE_EVT: { + CisStreamState *data = (CisStreamState *) p_data; + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + // check if current stream tracker is interested in this CIG update + std::vector int_trackers = + int_strm_trackers_.FindByCisId(data->cig_id, data->cis_id); + if(int_trackers.empty()) { + LOG(INFO) << __func__ << "Not intended for this tracker"; + break; + } + + // go for CIS destroy or CIG removal based on CIS state + if(data->state == CisState::ESTABLISHED) { + for(auto it = directions.begin(); it != directions.end(); ++it) { + if(data->direction & *it) { + UcastAudioStream *stream = audio_strms->FindByCisIdAndDir + (data->cig_id, data->cis_id, *it); + if(stream) { + stream->cis_state = data->state; + stream->cis_pending_cmd = CisPendingCmd::NONE; + ContinueDisconnection(stream); + } + } + } + } else if(data->state == CisState::READY) { + for(auto it = directions.begin(); it != directions.end(); ++it) { + if(data->direction & *it) { + UcastAudioStream *stream = audio_strms->FindByCisIdAndDir + (data->cig_id, data->cis_id, *it); + if(stream) { + stream->cis_state = data->state; + stream->cis_pending_cmd = CisPendingCmd::NONE; + ContinueDisconnection(stream); + } + } + } + CheckAndUpdateDisconnectedState(); + } + } break; + + case BAP_TIME_OUT_EVT: { + BapTimeout* timeout = static_cast (p_data); + if (timeout == nullptr) { + LOG(INFO) << __func__ << ": timeout data null, return "; + break; + } + + int stream_tracker_id = timeout->transition_state; + LOG(INFO) << __func__ << ": stream_tracker_ID: " << stream_tracker_id + << ", timeout reason: " << static_cast(timeout->reason); + + std::vector *int_trackers = + int_strm_trackers_.GetTrackerList(); + if (timeout->reason == TimeoutReason::STATE_TRANSITION) { + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + for (auto i = int_trackers->begin(); i != int_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if(stream) { + stream->ase_state = ascs::ASE_STATE_IDLE; + stream->ase_pending_cmd = AscsPendingCmd::NONE; + stream->overall_state = StreamTracker::kStateIdle; + ContinueDisconnection(stream); + } + } + CheckAndUpdateDisconnectedState(); + } + } break; + default: + LOG(WARNING) << __func__ << ": Un-handled event: " + << tracker_.GetEventName(event); + break; + } + return true; +} + +void StreamTracker::StateReconfiguring::OnEnter() { + LOG(INFO) << __func__ << ": StreamTracker State: " << GetState(); + UcastClientCallbacks* callbacks = strm_mgr_->GetUclientCbacks(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + std::vector strms; + std::vector *reconfig_streams = tracker_.GetReconfStreams(); + uint8_t num_ases = 0; + + LOG(WARNING) << __func__ << ": Reconfig Streams Size: " + << reconfig_streams->size(); + + StreamControlType control_type = tracker_.GetControlType(); + + if(control_type != StreamControlType::Reconfig) return; + + for (auto it = reconfig_streams->begin(); + it != reconfig_streams->end(); it++) { + StreamStateInfo state; + memset(&state, 0, sizeof(state)); + state.stream_type = it->stream_type; + state.stream_state = StreamState::RECONFIGURING; + strms.push_back(state); + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + context->connection_state = IntConnectState::PACS_DISCOVERING; + context->stream_state = StreamState::RECONFIGURING; + num_ases += context->stream_ids.size(); + } + callbacks->OnStreamState(strm_mgr_->GetAddress(), strms); + + uint64_t tout = num_ases * + (static_cast(TimeoutVal::ReconfiguringTimeout)); + if(!tout ||tout > static_cast(MaxTimeoutVal::ReconfiguringTimeout)){ + tout = static_cast(MaxTimeoutVal::ReconfiguringTimeout); + } + + TimeoutReason reason = TimeoutReason::STATE_TRANSITION; + state_transition_timer = tracker_.SetTimer("StateReconfiguringTimer", + &timeout, reason, tout); + if (state_transition_timer == nullptr) { + LOG(ERROR) << __func__ << ": state_transition_timer: Alarm not allocated."; + return; + } +} + +void StreamTracker::StateReconfiguring::OnExit() { + tracker_.ClearTimer(state_transition_timer, "StateReconfiguringTimer"); +} + +bool StreamTracker::StateReconfiguring::ProcessEvent(uint32_t event, + void* p_data) { + LOG(INFO) <<__func__ <<": BD Addr = " << strm_mgr_->GetAddress() + <<": State = " << GetState() + <<": Event = " << tracker_.GetEventName(event); + + std::vector *reconf_streams = tracker_.GetReconfStreams(); + StreamContexts *contexts = strm_mgr_->GetStreamContexts(); + uint8_t num_reconf_streams = 0; + if(reconf_streams) { + num_reconf_streams = reconf_streams->size(); + } + UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams(); + AscsClient *ascs_client = strm_mgr_->GetAscsClient(); + + switch (event) { + case BAP_DISCONNECT_REQ_EVT: { + tracker_.HandleDisconnect(p_data, StreamTracker::kStateReconfiguring); + } break; + case PACS_DISCOVERY_RES_EVT: { + PacsDiscovery pacs_discovery_ = *((PacsDiscovery *) p_data); + GattState ascs_state = strm_mgr_->GetAscsState(); + uint8_t qos_reconfigs = 0; + + bool process_pacs_results = false; + + // check if this tracker already passed the pacs discovery stage + for (auto it = reconf_streams->begin(); + it != reconf_streams->end(); it++) { + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + if (context->connection_state == IntConnectState::PACS_DISCOVERING) { + context->connection_state = IntConnectState::ASCS_DISCOVERED; + process_pacs_results = true; + } + } + + if(!process_pacs_results) break; + + // check the status + if(pacs_discovery_.status) { + // send the BAP callback as connected as discovery failed + // during reconfiguring + tracker_.TransitionTo(StreamTracker::kStateConnected); + return false; + } + + tracker_.UpdatePacsDiscovery(pacs_discovery_); + + // check if supported audio contexts has required contexts + for (auto it = reconf_streams->begin(); + it != reconf_streams->end();) { + bool context_supported = false; + StreamType stream = it->stream_type; + if(stream.direction == ASE_DIRECTION_SINK) { + if(stream.audio_context & pacs_discovery_.supported_contexts) { + context_supported = true; + } + } else if(stream.direction == ASE_DIRECTION_SRC) { + if((static_cast(stream.audio_context) << 16) & + pacs_discovery_.supported_contexts) { + context_supported = true; + } + } + if(context_supported) { + it++; + } else { + it = reconf_streams->erase(it); + // TODO to update the disconnected callback + } + } + + if(reconf_streams->empty()) { + LOG(ERROR) << __func__ << " No Matching Sup Contexts found"; + LOG(ERROR) << __func__ << " Moving back to Connected state"; + tracker_.TransitionTo(StreamTracker::kStateConnected); + break; + } + + // check physical allocation for all reconfig requests + uint8_t num_phy_attached = 0; + uint8_t num_same_config_applied = 0; + // if not present send the BAP callback as disconnected + // compare the codec configs from upper layer to remote dev + // sink or src PACS records/capabilities. + for (auto it = reconf_streams->begin(); + it != reconf_streams->end(); it++) { + uint8_t index = tracker_.ChooseBestCodec(it->stream_type, + &it->codec_qos_config_pair, + &pacs_discovery_); + if(index != 0XFF) { + CodecQosConfig entry = it->codec_qos_config_pair.at(index); + StreamContext *context = contexts->FindOrAddByType( + it->stream_type); + if(context->attached_state == StreamAttachedState::PHYSICAL) { + num_phy_attached++; + // check if same config is already applied + if(IsCodecConfigEqual(&context->codec_config,&entry.codec_config)) { + num_same_config_applied++; + } + } + } else { + LOG(ERROR) << __func__ << " Matching Codec not found"; + } + } + + if(reconf_streams->size() == num_phy_attached && + num_phy_attached == num_same_config_applied) { + // update the state to connected + LOG(INFO) << __func__ << " Making state to Connected as Nothing to do"; + TransitionTo(StreamTracker::kStateConnected); + break; + } + + if(ascs_state != GattState::CONNECTED) { + break; + } + + for (auto it = reconf_streams->begin(); + it != reconf_streams->end(); it++) { + uint8_t index = tracker_.ChooseBestCodec(it->stream_type, + &it->codec_qos_config_pair, + &pacs_discovery_); + if(index != 0XFF) { + CodecQosConfig entry = it->codec_qos_config_pair.at(index); + StreamContext *context = contexts->FindOrAddByType( + it->stream_type); + CodecConfig codec_config = entry.codec_config; + QosConfig qos_config = entry.qos_config; + + if(context->attached_state == StreamAttachedState::VIRTUAL) { + std::vector phy_attached_contexts; + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + std::vector phy_attached_contexts; + phy_attached_contexts = contexts->FindByAseAttachedState( + id->ase_id, StreamAttachedState::PHYSICAL); + for (auto context_id = phy_attached_contexts.begin(); + context_id != phy_attached_contexts.end(); + context_id++) { + LOG(INFO) << __func__ << ":Attached state made virtual"; + (*context_id)->attached_state = StreamAttachedState::VIRTUAL; + } + } + LOG(INFO) << __func__ << ":Attached state made virtual to phy"; + context->attached_state = StreamAttachedState::VIR_TO_PHY; + context->codec_config = codec_config; + context->req_qos_config = qos_config; + } else if (context->attached_state == StreamAttachedState::PHYSICAL) { + LOG(INFO) << __func__ << ":Attached state is physical"; + // check if same config is already applied + if(IsCodecConfigEqual(&context->codec_config,&entry.codec_config)) { + if(it->reconf_type == StreamReconfigType::CODEC_CONFIG) { + it->reconf_type = StreamReconfigType::QOS_CONFIG; + } + } else { + context->codec_config = codec_config; + } + context->req_qos_config = qos_config; + } + + uint8_t ascs_config_index = 0; + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + int_strm_trackers_.FindOrAddBytrackerType(it->stream_type, + id->ase_id, + qos_config.ascs_configs[ascs_config_index].cig_id, + qos_config.ascs_configs[ascs_config_index].cis_id, + codec_config, + qos_config); + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + stream->cig_id = id->cig_id = + qos_config.ascs_configs[ascs_config_index].cig_id; + stream->cis_id = id->cis_id = + qos_config.ascs_configs[ascs_config_index].cis_id; + stream->cig_state = CigState::INVALID; + stream->cis_state = CisState::INVALID; + stream->codec_config = codec_config; + stream->req_qos_config = qos_config; + stream->qos_config = qos_config; + stream->audio_context = it->stream_type.audio_context; + ascs_config_index++; + } + } else { + LOG(ERROR) << __func__ << " Matching Codec not found"; + } + } + for (auto it = reconf_streams->begin(); + it != reconf_streams->end(); it++) { + if (it->reconf_type == StreamReconfigType::QOS_CONFIG) { + qos_reconfigs++; + } + } + + if(qos_reconfigs == num_reconf_streams) { + // now create the group + std::vector *all_trackers = + int_strm_trackers_.GetTrackerList(); + // check for all streams together so that final group params + // will be decided. + for (auto i = all_trackers->begin(); i != all_trackers->end();i++) { + UcastAudioStream *stream = audio_strms->FindByAseId((*i)->ase_id); + if (!stream) { + LOG(ERROR) << __func__ << "stream is null"; + continue; + } + tracker_.ChooseBestQos(&stream->req_qos_config, + &stream->pref_qos_params, + &stream->qos_config, + StreamTracker::kStateReconfiguring, + stream->direction); + } + tracker_.CheckAndSendQosConfig(&int_strm_trackers_); + } else { + // now send the ASCS codec config + std::vector ase_ops; + for (auto it = reconf_streams->begin(); + it != reconf_streams->end(); it++) { + if(it->reconf_type == StreamReconfigType::CODEC_CONFIG) { + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if (stream) { + tracker_.PrepareCodecConfigPayload(&ase_ops, stream); + } + } + } + } + + if(!ase_ops.empty()) { + LOG(WARNING) << __func__ << ": Going For ASCS CodecConfig op"; + ascs_client->CodecConfig(ASCS_CLIENT_ID, + strm_mgr_->GetAddress(), ase_ops); + } + + // update the states to connecting or other internal states + for (auto it = reconf_streams->begin(); + it != reconf_streams->end(); it++) { + if(it->reconf_type == StreamReconfigType::CODEC_CONFIG) { + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if (stream) { + stream->ase_pending_cmd = AscsPendingCmd::CODEC_CONFIG_ISSUED; + stream->overall_state = StreamTracker::kStateReconfiguring; + } + } + } else { + StreamContext *context = contexts->FindOrAddByType(it->stream_type); + for (auto id = context->stream_ids.begin(); + id != context->stream_ids.end(); id++) { + UcastAudioStream *stream = audio_strms->FindByAseId(id->ase_id); + if (stream) { + stream->overall_state = StreamTracker::kStateReconfiguring; + } + } + } + } + } + } break; + + case ASCS_ASE_STATE_EVT: { + tracker_.HandleAseStateEvent(p_data, StreamControlType::Reconfig, + &int_strm_trackers_); + } break; + + case ASCS_ASE_OP_FAILED_EVT: { + tracker_.HandleAseOpFailedEvent(p_data); + } break; + + case PACS_CONNECTION_STATE_EVT: { + tracker_.HandlePacsConnectionEvent(p_data); + } break; + + case ASCS_CONNECTION_STATE_EVT: { + tracker_.HandleAscsConnectionEvent(p_data); + } break; + + case BAP_TIME_OUT_EVT: { + tracker_.OnTimeout(p_data); + } break; + + default: + LOG(WARNING) << __func__ << ": Un-handled event: " + << tracker_.GetEventName(event); + break; + } + return true; +} + +} // namespace ucast +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/cc/bta_cc_main.cc b/le_audio/system/bt/bta/cc/bta_cc_main.cc new file mode 100644 index 00000000000..eedebcc9f3d --- /dev/null +++ b/le_audio/system/bt/bta/cc/bta_cc_main.cc @@ -0,0 +1,2334 @@ +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ +/* + * Copyright (C) 2003-2012 Broadcom Corporation + * + * 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 + */ +/****************************************************************************** + * + * This is the main implementation file for the BTA LE audio call Gateway. + * + ******************************************************************************/ + +#include "bta_api.h" +#include "btif_util.h" +#include "bt_target.h" +#include "bta_cc_api.h" +#include "gatts_ops_queue.h" +#include "btm_int.h" +#include "device/include/controller.h" + +#include "osi/include/properties.h" +#include "osi/include/alarm.h" +#include "osi/include/allocator.h" +#include "osi/include/osi.h" +#include "bta_sys.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#define MAX_URI_SIZE 50 +#define STANDARD_BEARER_UCI "un000" +#define MS_IN_SEC 1000 +#define CCS_DEFAULT_INDEX_VAL 0 +#define DEFAULT_INDICIES_COUNT 1 + +using bluetooth::Uuid; +using bluetooth::bap::GattsOpsQueue; +class CallControllerImpl; +static CallControllerImpl *cc_instance; +static bool gIsTerminatedInitiatedFromClient = false; +static int gTerminateIntiatedIndex = 0; +static bool gIsActiveCC = false; + +//GTBS UUID (4B: TBS, 4C: GTBS) +Uuid CALL_CONTROL_SERVER_UUID = Uuid::FromString("0000184C-0000-1000-8000-00805F9B34FB"); + +Uuid GTBS_CALL_BEARER_NAME_UUID = Uuid::FromString("00002bb3-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_BEARER_UCI = Uuid::FromString("00002bb4-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_BEARER_TECHNOLOGY = Uuid::FromString("00002bb5-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_BEARER_URI_SCHEMES = Uuid::FromString("00002bb6-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_SIGNAL_STRENGTH = Uuid::FromString("00002bb7-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_SIGNAL_STRENGTH_REPORTINTERVAL = Uuid::FromString("00002bb8-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_BEARER_LIST_CURRENT_CALLS = Uuid::FromString("00002bb9-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_CONTENT_CONTROLID = Uuid::FromString("00002bba-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_CALL_STATUS_FLAGS = Uuid::FromString("00002bbb-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_INCOMINGCALL_TARGET_URI = Uuid::FromString("00002bbc-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_CALL_STATE_UUID = Uuid::FromString("00002bbd-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_CALL_CONTROL_POINT_OPS = Uuid::FromString("00002bbe-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_CALL_CONTROL_POINT_OPTIONAL_OPS = Uuid::FromString("00002bbf-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_CALL_TERMINATION_REASON = Uuid::FromString("00002bc0-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_INCOMING_CALL = Uuid::FromString("00002bc1-0000-1000-8000-00805F9B34FB"); +Uuid GTBS_CALL_FRIENDLY_NAME = Uuid::FromString("00002bc2-0000-1000-8000-00805F9B34FB"); + + +Uuid GTBS_DESCRIPTOR_UUID = Uuid::FromString("00002902-0000-1000-8000-00805f9b34fb"); + +//global varibale +CcsControlServiceInfo_t ccsControlServiceInfo; +tCCS_CALL_STATE CallStateInfo; +std::map CallStatelist; +std::map BlccInfolist; +tCCS_CALL_CONTROL_POINT CallControllerOps; +tCCS_CALL_CONTROL_RESPONSE CallControllerResp; +tCCS_BEARER_LIST_CURRENT_CALLS BlccInfo; +tCCS_BEARER_PROVIDER_INFO BearerProviderInfo; +tCCS_CONTENT_CONTROL_ID CcidInfo; +tCCS_STATUS_FLAGS StatusFlags; +tCCS_INCOMING_CALL IncomingCallInfo; +tCCS_INCOMING_CALL_URI IncomingCallTargetUri; +tCCS_TERM_REASON TerminationReason; +tCCS_FRIENDLY_NAME FriendlyName; +tCCS_SUPP_OPTIONAL_OPCODES SupportedOptionalOpcodes; + +void BTCcCback(tBTA_GATTS_EVT event, tBTA_GATTS* param); +void ReverseByteOrder(unsigned char s[], int length); + +typedef base::Callback service)> + OnGtbsServiceAdded; + +static void OnGtbsServiceAddedCb(uint8_t status, int serverIf, + std::vector service); + +const char* bta_cc_event_str(uint32_t event) { + switch (event) { + CASE_RETURN_STR(CCS_NONE_EVENT) + CASE_RETURN_STR(CCS_INIT_EVENT) + CASE_RETURN_STR(CCS_CLEANUP_EVENT) + CASE_RETURN_STR(CCS_CALL_STATE_UPDATE) + CASE_RETURN_STR(CCS_BEARER_NAME_UPDATE) + CASE_RETURN_STR(CCS_BEARER_UCI_UPDATE) + CASE_RETURN_STR(CCS_BEARER_URI_SCHEMES_SUPPORTED) + CASE_RETURN_STR(CCS_UPDATE) + CASE_RETURN_STR(CCS_OPT_OPCODES) + CASE_RETURN_STR(CCS_BEARER_CURRENT_CALL_LIST_UPDATE) + CASE_RETURN_STR(CCS_BEARER_SIGNAL_STRENGTH_UPDATE) + CASE_RETURN_STR(CCS_SIGNAL_STRENGTH_REPORT_INTERVAL) + CASE_RETURN_STR(CCS_STATUS_FLAGS_UPDATE) + CASE_RETURN_STR(CCS_INCOMING_CALL_UPDATE) + CASE_RETURN_STR(CCS_INCOMING_TARGET_URI_UPDATE) + CASE_RETURN_STR(CCS_TERMINATION_REASON_UPDATE) + CASE_RETURN_STR(CCS_BEARER_TECHNOLOGY_UPDATE) + CASE_RETURN_STR(CCS_CCID_UPDATE) + CASE_RETURN_STR(CCS_ACTIVE_DEVICE_UPDATE) + CASE_RETURN_STR(CCS_CALL_CONTROL_RESPONSE) + CASE_RETURN_STR(CCS_NOTIFY_ALL) + CASE_RETURN_STR(CCS_WRITE_RSP) + CASE_RETURN_STR(CCS_READ_RSP) + CASE_RETURN_STR(CCS_DESCRIPTOR_WRITE_RSP) + CASE_RETURN_STR(CCS_DESCRIPTOR_READ_RSP) + CASE_RETURN_STR(CCS_CONNECTION) + CASE_RETURN_STR(CCS_DISCONNECTION) + CASE_RETURN_STR(CCS_CONNECTION_UPDATE) + CASE_RETURN_STR(CCS_CONGESTION_UPDATE) + CASE_RETURN_STR(CCS_PHY_UPDATE) + CASE_RETURN_STR(CCS_MTU_UPDATE) + CASE_RETURN_STR(CCS_SET_ACTIVE_DEVICE) + CASE_RETURN_STR(CCS_CONNECTION_CLOSE_EVENT) + CASE_RETURN_STR(CCS_BOND_STATE_CHANGE_EVENT) + default: + return (char*)"Unknown bta cc event"; + } +} + + +class CallControllerDevices { + private: + CallActiveDevice activeDevice; + //int max_connection; + public: + bool Add(CallControllerDeviceList device) { + if (devices.size() == MAX_CCS_CONNECTION) { + return false; + } + if (FindByAddress(device.peer_bda) != nullptr) return false; + + device.DeviceStateHandlerPointer[CCS_DISCONNECTED] = DeviceStateDisconnectedHandler; + device.DeviceStateHandlerPointer[CCS_CONNECTED] = DeviceStateConnectionHandler; + device.signal_strength_report_interval = 0; + std::string alarmName = device.peer_bda.ToString(); + alarmName.append("-CC_SSReportingTimer"); + + device.signal_strength_reporting_timer = alarm_new_periodic(alarmName.c_str()); + devices.push_back(device); + return true; + } + + void Remove(RawAddress& address) { + for (auto it = devices.begin(); it != devices.end();) { + if (it->peer_bda != address) { + ++it; + continue; + } + if (it == devices.end()) { + LOG(ERROR) << __func__ <<"no matching device"; + return; + } + //Cancel SSReporting timer + if (it->signal_strength_report_interval != 0 && + it->signal_strength_reporting_timer != nullptr) { + alarm_cancel(it->signal_strength_reporting_timer); + alarm_free(it->signal_strength_reporting_timer); + } + + it = devices.erase(it); + + return; + } + } + + void RemoveDevices() { + for (auto it = devices.begin(); it != devices.end();) { + it = devices.erase(it); + } + return; + } + + CallControllerDeviceList* FindByAddress(const RawAddress& address) { + auto iter = std::find_if(devices.begin(), devices.end(), + [&address](const CallControllerDeviceList& device) { + return device.peer_bda == address; + }); + + return (iter == devices.end()) ? nullptr : &(*iter); + } + + CallControllerDeviceList* FindByConnId(uint16_t conn_id) { + auto iter = std::find_if(devices.begin(), devices.end(), + [&conn_id](const CallControllerDeviceList& device) { + return device.conn_id == conn_id; + }); + + return (iter == devices.end()) ? nullptr : &(*iter); + } + + size_t size() { return (devices.size()); } + + std::vector GetRemoteDevices() { + return devices; + } + std::vector FindNotifyDevices(uint16_t handle) { + std::vector notify_devices; + for (size_t it = 0; it != devices.size(); it++){ + if(ccsControlServiceInfo.bearer_provider_name_handle == handle && + devices[it].bearer_provider_name_notify) { + notify_devices.push_back(devices[it]); + } else if(ccsControlServiceInfo.bearer_technology_handle == handle && + devices[it].bearer_technology_changed_notify) { + notify_devices.push_back(devices[it]); + } else if(ccsControlServiceInfo.bearer_signal_strength_handle == handle && + devices[it].bearer_signal_strength_notify) { + notify_devices.push_back(devices[it]); + } else if(ccsControlServiceInfo.bearer_list_currentcalls_handle == handle && + devices[it].bearer_current_calls_list_notify) { + notify_devices.push_back(devices[it]); + } else if(ccsControlServiceInfo.call_status_flags_handle == handle && + devices[it].status_flags_notify) { + notify_devices.push_back(devices[it]); + } else if(ccsControlServiceInfo.incoming_call_target_beareruri_handle == handle && + devices[it].incoming_call_target_URI_notify) { + notify_devices.push_back(devices[it]); + } else if(ccsControlServiceInfo.call_state_handle == handle && + devices[it].call_state_notify) { + notify_devices.push_back(devices[it]); + } else if(ccsControlServiceInfo.call_control_point_handle == handle && + devices[it].call_control_point_notify) { + notify_devices.push_back(devices[it]); + } else if(ccsControlServiceInfo.call_termination_reason_handle == handle && + devices[it].call_termination_reason_notify) { + notify_devices.push_back(devices[it]); + } else if(ccsControlServiceInfo.incoming_call_handle == handle && + devices[it].incoming_call_state_notify) { + notify_devices.push_back(devices[it]); + } else if(ccsControlServiceInfo.call_friendly_name_handle == handle && + devices[it].call_friendly_name_notify) { + notify_devices.push_back(devices[it]); + } + } + return notify_devices; + } + void AddSetActiveDevice(tCCS_SET_ACTIVE_DEVICE *device) { + if (device->set_id == activeDevice.set_id) { + activeDevice.address.push_back(device->address); + } else { + activeDevice.address.clear(); + activeDevice.set_id = device->set_id; + activeDevice.address.push_back(device->address); + } + } + + bool FindActiveDevice(CallControllerDeviceList *remoteDevice) { + bool flag = false; + for (auto& it : activeDevice.address) { + if(remoteDevice->peer_bda == it) { + flag = true; + break; + } + } + return flag; + } + static void SSReportingTimerExpired(void *data) { + LOG(INFO) << __func__ ; + CallControllerDeviceList* dev = (CallControllerDeviceList*)data; + if (dev == nullptr) { + LOG(ERROR) << __func__ << "no valid dev handle"; + return; + } + + //send Notification for Signal Strength + tcc_resp_t *rsp = new tcc_resp_t(); + if (rsp == NULL) { + LOG(ERROR) << __func__ << "Allocation error!"; + return; + } + std::vector _data; + _data.clear(); + rsp->remoteDevice = dev; + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.bearer_signal_strength_handle; + _data.push_back(BearerProviderInfo.signal); + rsp->oper.CallControllerOp.data = std::move(_data); + //force the Notification on timer expiry + rsp->force = true; + + if (dev->bearer_signal_strength_notify) { + LOG(INFO) << "Caling device state handler for SSReporting"; + dev->DeviceStateHandlerPointer[dev->state](CCS_NOTIFY_ALL, rsp); + } else { + LOG(INFO) << "SSReporting Interval set without Desc write for SSR"; + } + } + + static void SetupSSReportingInterval (CallControllerDeviceList* dev) { + LOG(INFO) << __func__ ; + if (alarm_is_scheduled(dev->signal_strength_reporting_timer)) { + alarm_cancel(dev->signal_strength_reporting_timer); + } + if (dev->signal_strength_report_interval != 0) { + alarm_set(dev->signal_strength_reporting_timer, + (period_ms_t)dev->signal_strength_report_interval*MS_IN_SEC, + SSReportingTimerExpired, + (void*)dev); + } + } + + bool UpdateSSReportingInterval(const RawAddress& address, uint8_t ssrInterval) { + bool ret = false; + CallControllerDeviceList *dev = FindByAddress(address); + + if (dev != nullptr) { + dev->signal_strength_report_interval = ssrInterval; + SetupSSReportingInterval(dev); + ret = true; + } + return ret; + } + + std::vector devices; +}; + + +class CallControllerImpl : public CallController { + bluetooth::call_control::CallControllerCallbacks* callbacks; + Uuid app_uuid; + int max_clients; + bool inband_ring_support; + + public: + CallControllerDevices remoteDevices; + virtual ~CallControllerImpl() = default; + + + CallControllerImpl(bluetooth::call_control::CallControllerCallbacks* callback, + Uuid uuid, int max_ccs_clients, bool inband_ringing_enabled) + :callbacks(callback), + app_uuid(uuid), + max_clients(max_ccs_clients), + inband_ring_support(inband_ringing_enabled) { + // HandleCcsEvent(CCS_INIT_EVENT, &app_uuid); + LOG(INFO) << "max_clients " << max_clients; + if (inband_ring_support) { + StatusFlags.supported_flags = (StatusFlags.supported_flags & 0x0000) | INBAND_RINGTONE_FEATURE_BIT; + } else { + StatusFlags.supported_flags = 0x0000; + } + LOG(INFO) << "CallControllerImpl gatts app register"; + BTA_GATTS_AppRegister(app_uuid, BTCcCback, true); + + } + + void Disconnect(const RawAddress& bd_addr) { + LOG(INFO) << __func__; + tCCS_CONNECTION_CLOSE ConnectClosingOp; + ConnectClosingOp.addr = bd_addr; + HandleCcsEvent(CCS_CONNECTION_CLOSE_EVENT, &ConnectClosingOp); + } + + void SetActiveDevice(const RawAddress& address, int setId) { + LOG(INFO) << __func__ ; + tCCS_SET_ACTIVE_DEVICE SetActiveDeviceOp; + SetActiveDeviceOp.set_id = setId; + SetActiveDeviceOp.address = address; + HandleCcsEvent(CCS_ACTIVE_DEVICE_UPDATE, &SetActiveDeviceOp); + } + + void CallState(int len, std::vector call_state_list) { + tCCS_CALL_STATE CallStateOp; + for (int k=0; k::iterator i = BlccInfolist.find(BlccInfo.call_index); + if (i != BlccInfolist.end()) { + LOG(INFO) << __func__ << " update existing Blcc"; + i->second = BlccInfo; + } else { + BlccInfolist.insert({BlccInfo.call_index, BlccInfo}); + } + std::map::iterator j = CallStatelist.find(CallStateOp.index); + if (j != CallStatelist.end()) { + j->second = CallStateOp; + } else { + CallStatelist.insert({CallStateOp.index, CallStateOp}); + } + //clear the term reason + if (CallStateOp.index == TerminationReason.index) { + TerminationReason.index = 0; + TerminationReason.reason = CC_TERM_INVALID_ORIG_URI; + } + } + } + HandleCcsEvent(CCS_CALL_STATE_UPDATE, &CallStatelist); + HandleCcsEvent(CCS_BEARER_CURRENT_CALL_LIST_UPDATE, &BlccInfolist); + } + + void BearerInfoName(uint8_t* name) { + LOG(INFO) << __func__ << name; + tCCS_BEARER_PROVIDER_INFO BearerProviderOp; + BearerProviderOp.length = strlen((char *)name); + memcpy(BearerProviderOp.name, name, BearerProviderOp.length+1); + HandleCcsEvent(CCS_BEARER_NAME_UPDATE, &BearerProviderOp); + } + + void UpdateBearerTechnology(int tech_type) { + LOG(INFO) << __func__ << " tech type: " <::iterator it; + tCCS_INCOMING_CALL IncomingCall; + int len = strlen((char *)Uri); + memcpy(IncomingCall.incoming_uri, Uri, len+1); + IncomingCall.index = index; + for (it = BlccInfolist.begin(); it != BlccInfolist.end(); it++) { + tCCS_BEARER_LIST_CURRENT_CALLS blccObj = it->second; + if (blccObj.call_index == index) { + memcpy(blccObj.call_uri, Uri, strlen((char *)Uri)+1); + break; + } + } + HandleCcsEvent(CCS_INCOMING_CALL_UPDATE, &IncomingCall); + //update Target URI as well with same Info + UpdateIncomingCallTargetUri(index, Uri); + } + + void UpdateIncomingCallTargetUri(int index, uint8_t* target_uri) { + LOG(INFO) << __func__ << " target_uri: " << target_uri; + tCCS_INCOMING_CALL_URI IncomingcallUri; + int len = strlen((char *)target_uri); + memcpy(IncomingcallUri.incoming_target_uri, target_uri, len+1); + IncomingcallUri.index = index; + HandleCcsEvent(CCS_INCOMING_TARGET_URI_UPDATE, &IncomingcallUri); + } + + void ContentControlId(uint32_t ccid) { + LOG(INFO) << __func__; + tCCS_CONTENT_CONTROL_ID ContentControlIdOp; + ContentControlIdOp.ccid = ccid; + HandleCcsEvent(CCS_CCID_UPDATE, &ContentControlIdOp); + } + + int GetDialingCallIndex() { + int retIndex = 0; + std::map::iterator it; + for (it = CallStatelist.begin(); it != CallStatelist.end(); it++) { + tCCS_CALL_STATE obj = it->second; + if (obj.state == CCS_STATE_DIALING || obj.state == CCS_STATE_ALERTING) { + LOG(INFO) << __func__ << " call state match: " << obj.index; + retIndex = obj.index; + break; + } + } + return retIndex; + } + void CallControlResponse(uint8_t op, uint8_t index, uint32_t status, const RawAddress& address) { + LOG(INFO) << __func__; + tCCS_CALL_CONTROL_RESPONSE CallControlResponse; + CallControllerResp.opcode = op; + CallControllerResp.response_status = status; + CallControllerResp.remote_address = address; + CallControllerResp.index = index; + if (status == CCS_STATUS_SUCCESS && op == CALL_ORIGINATE) { + //get proper call Index for call orignate status + CallControllerResp.index = GetDialingCallIndex(); + } + HandleCcsEvent(CCS_CALL_CONTROL_RESPONSE, &CallControlResponse); + } + + void CallControlInitializedCallback(uint8_t state) { + LOG(INFO) << __func__ << " state" << state; + callbacks->CallControlInitializedCallback(state); + if (state == 0) { + //Initialize local char values + memcpy(BearerProviderInfo.uci, STANDARD_BEARER_UCI, strlen(STANDARD_BEARER_UCI)); + } + } + + void ConnectionStateCallback(uint8_t state, const RawAddress& address) { + LOG(INFO) << __func__ << " state: " << state; + callbacks->ConnectionStateCallback(state, address); + } + + void CallControlPointChange(uint8_t op, uint8_t* indices, int count, char* uri, const RawAddress& address) { + LOG(INFO) << __func__ << " op: " < uri_data; + if (uri != NULL) { + LOG(INFO) << __func__ <<" uri=" << uri; + uri_data.insert(uri_data.begin(), uri, uri + strlen(uri)); + } + std::vector call_indicies; + for (int i=0; iCallControlCallback(op, call_indicies, count, uri_data, address); + } +}; + +void CallController::CleanUp() { + HandleCcsEvent(CCS_CLEANUP_EVENT, NULL); + delete cc_instance; + cc_instance = nullptr; + } + +CallController* CallController::Get() { + CHECK(cc_instance); + return cc_instance; +} + +void CallController::Initialize(bluetooth::call_control::CallControllerCallbacks* callbacks, + Uuid uuid, int max_ccs_clients, bool inband_ringing_enabled) { + if (cc_instance) { + LOG(ERROR) << "Already initialized!"; + } else { + cc_instance = new CallControllerImpl(callbacks, uuid, max_ccs_clients, inband_ringing_enabled); + } + char activeCC[PROPERTY_VALUE_MAX] = "false"; + if(osi_property_get("persist.vendor.service.bt.activeCC", activeCC, "false") && + !strcmp(activeCC, "true")) { + gIsActiveCC = true; + } +} + +bool CallController::IsCcServiceRunnig() { return cc_instance; } + +static std::vector CcAddService(int server_if) { + + std::vector ccs_services; + ccs_services.clear(); + //service + btgatt_db_element_t service = {}; + service.uuid = CALL_CONTROL_SERVER_UUID; + service.type = BTGATT_DB_PRIMARY_SERVICE; + ccs_services.push_back(service); + ccsControlServiceInfo.ccs_service_uuid = service.uuid; + + btgatt_db_element_t bearer_provider_name_char = {}; + bearer_provider_name_char.uuid = GTBS_CALL_BEARER_NAME_UUID; + bearer_provider_name_char.type = BTGATT_DB_CHARACTERISTIC; + bearer_provider_name_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_NOTIFY; + bearer_provider_name_char.permissions = GATT_PERM_READ; + ccs_services.push_back(bearer_provider_name_char); + ccsControlServiceInfo.bearer_provider_name_uuid = bearer_provider_name_char.uuid; + + btgatt_db_element_t bearer_provider_name_desc = {}; + bearer_provider_name_desc.uuid = GTBS_DESCRIPTOR_UUID; + bearer_provider_name_desc.type = BTGATT_DB_DESCRIPTOR; + bearer_provider_name_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(bearer_provider_name_desc); + + btgatt_db_element_t bearer_technology_char = {}; + bearer_technology_char.uuid = GTBS_BEARER_TECHNOLOGY; + bearer_technology_char.type = BTGATT_DB_CHARACTERISTIC; + bearer_technology_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_NOTIFY; + bearer_technology_char.permissions = GATT_PERM_READ; + ccs_services.push_back(bearer_technology_char); + ccsControlServiceInfo.bearer_technology_uuid = bearer_technology_char.uuid; + + btgatt_db_element_t bearer_technology_desc = {}; + bearer_technology_desc.uuid = GTBS_DESCRIPTOR_UUID; + bearer_technology_desc.type = BTGATT_DB_DESCRIPTOR; + bearer_technology_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(bearer_technology_desc); + + btgatt_db_element_t gtbs_cc_optional_opcode_char = {}; + gtbs_cc_optional_opcode_char.uuid = GTBS_CALL_CONTROL_POINT_OPTIONAL_OPS; + gtbs_cc_optional_opcode_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_cc_optional_opcode_char.properties = GATT_CHAR_PROP_BIT_READ; + gtbs_cc_optional_opcode_char.permissions = GATT_PERM_READ; + ccs_services.push_back(gtbs_cc_optional_opcode_char); + ccsControlServiceInfo.call_control_point_opcode_supported_uuid = gtbs_cc_optional_opcode_char.uuid; + + btgatt_db_element_t gtbs_call_state_char = {}; + gtbs_call_state_char.uuid = GTBS_CALL_STATE_UUID; + gtbs_call_state_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_call_state_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_NOTIFY; + gtbs_call_state_char.permissions = GATT_PERM_READ; + ccs_services.push_back(gtbs_call_state_char); + ccsControlServiceInfo.call_state_uuid = gtbs_call_state_char.uuid; + + btgatt_db_element_t gtbs_call_state_desc = {}; + gtbs_call_state_desc.uuid = GTBS_DESCRIPTOR_UUID; + gtbs_call_state_desc.type = BTGATT_DB_DESCRIPTOR; + gtbs_call_state_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_call_state_desc); + + btgatt_db_element_t gtbs_call_control_point_char = {}; + gtbs_call_control_point_char.uuid = GTBS_CALL_CONTROL_POINT_OPS; + gtbs_call_control_point_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_call_control_point_char.properties = GATT_CHAR_PROP_BIT_WRITE|GATT_CHAR_PROP_BIT_NOTIFY|GATT_CHAR_PROP_BIT_WRITE_NR; + gtbs_call_control_point_char.permissions = GATT_PERM_WRITE; + ccs_services.push_back(gtbs_call_control_point_char); + ccsControlServiceInfo.call_control_point_uuid = gtbs_call_control_point_char.uuid; + + btgatt_db_element_t gtbs_call_control_point_desc = {}; + gtbs_call_control_point_desc.uuid = GTBS_DESCRIPTOR_UUID; + gtbs_call_control_point_desc.type = BTGATT_DB_DESCRIPTOR; + gtbs_call_control_point_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_call_control_point_desc); + + btgatt_db_element_t gtbs_bearer_uci_char = {}; + gtbs_bearer_uci_char.uuid = GTBS_BEARER_UCI; + gtbs_bearer_uci_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_bearer_uci_char.properties = GATT_CHAR_PROP_BIT_READ; + gtbs_bearer_uci_char.permissions = GATT_PERM_READ; + ccs_services.push_back(gtbs_bearer_uci_char); + ccsControlServiceInfo.bearer_uci_uuid= gtbs_bearer_uci_char.uuid; + + btgatt_db_element_t gtbs_bearer_URI_schemes_char = {}; + gtbs_bearer_URI_schemes_char.uuid = GTBS_BEARER_URI_SCHEMES; + gtbs_bearer_URI_schemes_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_bearer_URI_schemes_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_NOTIFY; + gtbs_bearer_URI_schemes_char.permissions = GATT_PERM_READ; + + ccs_services.push_back(gtbs_bearer_URI_schemes_char); + ccsControlServiceInfo.bearer_uri_schemes_supported_uuid = gtbs_bearer_URI_schemes_char.uuid; + + btgatt_db_element_t gtbs_bearer_URI_schemes_desc = {}; + gtbs_bearer_URI_schemes_desc.uuid = GTBS_DESCRIPTOR_UUID; + gtbs_bearer_URI_schemes_desc.type = BTGATT_DB_DESCRIPTOR; + gtbs_bearer_URI_schemes_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_bearer_URI_schemes_desc); + + btgatt_db_element_t gtbs_signal_strength_char = {}; + gtbs_signal_strength_char.uuid = GTBS_SIGNAL_STRENGTH; + gtbs_signal_strength_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_signal_strength_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_NOTIFY; + gtbs_signal_strength_char.permissions = GATT_PERM_READ; + + ccs_services.push_back(gtbs_signal_strength_char); + ccsControlServiceInfo.bearer_signal_strength_uuid = gtbs_signal_strength_char.uuid; + + btgatt_db_element_t gtbs_signal_strength_desc = {}; + gtbs_signal_strength_desc.uuid = GTBS_DESCRIPTOR_UUID; + gtbs_signal_strength_desc.type = BTGATT_DB_DESCRIPTOR; + gtbs_signal_strength_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_signal_strength_desc); + + btgatt_db_element_t gtbs_signal_strength_report_interval_char = {}; + gtbs_signal_strength_report_interval_char.uuid = GTBS_SIGNAL_STRENGTH_REPORTINTERVAL; + gtbs_signal_strength_report_interval_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_signal_strength_report_interval_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_WRITE|GATT_CHAR_PROP_BIT_WRITE_NR; + gtbs_signal_strength_report_interval_char.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_signal_strength_report_interval_char); + ccsControlServiceInfo.bearer_signal_strength_report_interval_uuid = gtbs_signal_strength_report_interval_char.uuid; + + btgatt_db_element_t gtbs_list_current_calls_char = {}; + gtbs_list_current_calls_char.uuid = GTBS_BEARER_LIST_CURRENT_CALLS; + gtbs_list_current_calls_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_list_current_calls_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_NOTIFY; + gtbs_list_current_calls_char.permissions = GATT_PERM_READ; + ccs_services.push_back(gtbs_list_current_calls_char); + ccsControlServiceInfo.bearer_list_currentcalls_uuid = gtbs_list_current_calls_char.uuid; + + btgatt_db_element_t gtbs_list_current_calls_desc = {}; + gtbs_list_current_calls_desc.uuid = GTBS_DESCRIPTOR_UUID; + gtbs_list_current_calls_desc.type = BTGATT_DB_DESCRIPTOR; + gtbs_list_current_calls_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_list_current_calls_desc); + + btgatt_db_element_t gtbs_call_status_flags_char = {}; + gtbs_call_status_flags_char.uuid = GTBS_CALL_STATUS_FLAGS; + gtbs_call_status_flags_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_call_status_flags_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_NOTIFY; + gtbs_call_status_flags_char.permissions = GATT_PERM_READ; + ccs_services.push_back(gtbs_call_status_flags_char); + ccsControlServiceInfo.call_status_flags_uuid = gtbs_call_status_flags_char.uuid; + + btgatt_db_element_t gtbs_call_status_flags_desc = {}; + gtbs_call_status_flags_desc.uuid = GTBS_DESCRIPTOR_UUID; + gtbs_call_status_flags_desc.type = BTGATT_DB_DESCRIPTOR; + gtbs_call_status_flags_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_call_status_flags_desc); + + btgatt_db_element_t gtbs_incomingcall_target_bearer_URI_char = {}; + gtbs_incomingcall_target_bearer_URI_char.uuid = GTBS_INCOMINGCALL_TARGET_URI; + gtbs_incomingcall_target_bearer_URI_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_incomingcall_target_bearer_URI_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_NOTIFY; + gtbs_incomingcall_target_bearer_URI_char.permissions = GATT_PERM_READ; + ccs_services.push_back(gtbs_incomingcall_target_bearer_URI_char); + ccsControlServiceInfo.incoming_call_target_beareruri_uuid = gtbs_incomingcall_target_bearer_URI_char.uuid; + + btgatt_db_element_t gtbs_incomingcall_target_bearer_URI_desc = {}; + gtbs_incomingcall_target_bearer_URI_desc.uuid = GTBS_DESCRIPTOR_UUID; + gtbs_incomingcall_target_bearer_URI_desc.type = BTGATT_DB_DESCRIPTOR; + gtbs_incomingcall_target_bearer_URI_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_incomingcall_target_bearer_URI_desc); + + btgatt_db_element_t gtbs_incomingcall_char = {}; + gtbs_incomingcall_char.uuid = GTBS_INCOMING_CALL; + gtbs_incomingcall_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_incomingcall_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_NOTIFY; + gtbs_incomingcall_char.permissions = GATT_PERM_READ; + ccs_services.push_back(gtbs_incomingcall_char); + ccsControlServiceInfo.incoming_call_uuid = gtbs_incomingcall_char.uuid; + + btgatt_db_element_t gtbs_incomingcall_desc = {}; + gtbs_incomingcall_desc.uuid = GTBS_DESCRIPTOR_UUID; + gtbs_incomingcall_desc.type = BTGATT_DB_DESCRIPTOR; + gtbs_incomingcall_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_incomingcall_desc); + + btgatt_db_element_t gtbs_call_termination_reason_char = {}; + gtbs_call_termination_reason_char.uuid = GTBS_CALL_TERMINATION_REASON; + gtbs_call_termination_reason_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_call_termination_reason_char.properties = GATT_CHAR_PROP_BIT_NOTIFY; + ccs_services.push_back(gtbs_call_termination_reason_char); + ccsControlServiceInfo.call_termination_reason_uuid = gtbs_call_termination_reason_char.uuid; + + btgatt_db_element_t gtbs_call_termination_reason_desc = {}; + gtbs_call_termination_reason_desc.uuid = GTBS_DESCRIPTOR_UUID; + gtbs_call_termination_reason_desc.type = BTGATT_DB_DESCRIPTOR; + gtbs_call_termination_reason_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_call_termination_reason_desc); + + btgatt_db_element_t gtbs_call_friendly_name_char = {}; + gtbs_call_friendly_name_char.uuid = GTBS_CALL_FRIENDLY_NAME; + gtbs_call_friendly_name_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_call_friendly_name_char.properties = GATT_CHAR_PROP_BIT_READ|GATT_CHAR_PROP_BIT_NOTIFY; + gtbs_call_friendly_name_char.permissions = GATT_PERM_READ; + ccs_services.push_back(gtbs_call_friendly_name_char); + ccsControlServiceInfo.call_friendly_name_uuid = gtbs_call_friendly_name_char.uuid; + + btgatt_db_element_t gtbs_call_friendly_name_desc = {}; + gtbs_call_friendly_name_desc.uuid = GTBS_DESCRIPTOR_UUID; + gtbs_call_friendly_name_desc.type = BTGATT_DB_DESCRIPTOR; + gtbs_call_friendly_name_desc.permissions = GATT_PERM_READ|GATT_PERM_WRITE; + ccs_services.push_back(gtbs_call_friendly_name_desc); + + btgatt_db_element_t gtbs_ccid_char = {}; + gtbs_ccid_char.uuid = GTBS_CONTENT_CONTROLID; + gtbs_ccid_char.type = BTGATT_DB_CHARACTERISTIC; + gtbs_ccid_char.properties = GATT_CHAR_PROP_BIT_READ; + gtbs_ccid_char.permissions = GATT_PERM_READ; + ccs_services.push_back(gtbs_ccid_char); + ccsControlServiceInfo.gtbs_ccid_uuid = gtbs_ccid_char.uuid; + + return ccs_services; +} + +static void OnGtbsServiceAddedCb(uint8_t status, int serverIf, + std::vector service) { + + if (service[0].uuid == Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER) || + service[0].uuid == Uuid::From16Bit(UUID_SERVCLASS_GAP_SERVER)) { + LOG(INFO) << "%s: Attempt to register restricted service"<< __func__; + return; + } + + for(int i=0; i< (int)service.size(); i++) { + + if (service[i].uuid == CALL_CONTROL_SERVER_UUID) { + LOG(INFO) << __func__ << " GTBS service added attr handle: " << service[i].attribute_handle; + } else if(service[i].uuid == GTBS_CALL_BEARER_NAME_UUID) { + ccsControlServiceInfo.bearer_provider_name_handle = service[i++].attribute_handle; + ccsControlServiceInfo.bearer_provider_name_desc = service[i].attribute_handle; + LOG(INFO) << __func__ << " bearer_provider_name_attr: " + << ccsControlServiceInfo.bearer_provider_name_handle + << " bearer_provider_name_desc: " + << ccsControlServiceInfo.bearer_provider_name_desc; + } else if(service[i].uuid == GTBS_BEARER_TECHNOLOGY) { + ccsControlServiceInfo.bearer_technology_handle = service[i++].attribute_handle; + ccsControlServiceInfo.bearer_technology_desc = service[i].attribute_handle; + LOG(INFO) << __func__ << " bearer_technology_handle: " + << ccsControlServiceInfo.bearer_technology_handle + << " bearer_technology_desc: " + << ccsControlServiceInfo.bearer_technology_desc; + } else if(service[i].uuid == GTBS_CALL_CONTROL_POINT_OPTIONAL_OPS) { + ccsControlServiceInfo.call_control_point_opcode_supported_handle = + service[i].attribute_handle; + LOG(INFO) << __func__ << " call_control_point_opcode_supported_handle register: " + << ccsControlServiceInfo.call_control_point_opcode_supported_handle; + } else if (service[i].uuid == GTBS_CALL_STATE_UUID) { + + ccsControlServiceInfo.call_state_handle = service[i++].attribute_handle; + ccsControlServiceInfo.call_state_desc = service[i].attribute_handle; + LOG(INFO) << __func__ << " call_state_handle: " + << ccsControlServiceInfo.call_state_handle + << " call_state_handle desc: " + << ccsControlServiceInfo.call_state_desc; + } else if(service[i].uuid == GTBS_CALL_CONTROL_POINT_OPS) { + ccsControlServiceInfo.call_control_point_handle = service[i++].attribute_handle; + ccsControlServiceInfo.call_control_point_desc = service[i].attribute_handle; + LOG(INFO) << __func__ << " call_control_point_handle: " + << ccsControlServiceInfo.call_control_point_handle + << " call_control_point_desc: " + << ccsControlServiceInfo.call_control_point_desc; + } else if(service[i].uuid == GTBS_BEARER_UCI) { + ccsControlServiceInfo.bearer_uci_handle = service[i].attribute_handle; + LOG(INFO) << __func__ << " bearer_uci_handle: " + << ccsControlServiceInfo.bearer_uci_handle; + + } else if(service[i].uuid == GTBS_BEARER_URI_SCHEMES) { + ccsControlServiceInfo.bearer_uri_schemes_supported_handle = + service[i++].attribute_handle; + ccsControlServiceInfo.bearer_uri_schemes_supported_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << " bearer_uri_schemes_supported_handle: " + << ccsControlServiceInfo.bearer_uri_schemes_supported_handle + << " bearer_uri_schemes_supported_desc: " + << ccsControlServiceInfo.bearer_uri_schemes_supported_desc; + + } else if(service[i].uuid == GTBS_SIGNAL_STRENGTH) { + ccsControlServiceInfo.bearer_signal_strength_handle = + service[i++].attribute_handle; + ccsControlServiceInfo.bearer_signal_strength_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << " bearer_signal_strength_handle: " + << ccsControlServiceInfo.bearer_signal_strength_handle + << " bearer_signal_strength_desc: " + << ccsControlServiceInfo.bearer_signal_strength_desc; + + } else if(service[i].uuid == GTBS_SIGNAL_STRENGTH_REPORTINTERVAL) { + ccsControlServiceInfo.bearer_signal_strength_report_interval_handle = + service[i].attribute_handle; + + LOG(INFO) << __func__ << " bearer_signal_strength_report_interval_handle: " + << ccsControlServiceInfo.bearer_signal_strength_report_interval_handle; + + } else if(service[i].uuid == GTBS_BEARER_LIST_CURRENT_CALLS) { + ccsControlServiceInfo.bearer_list_currentcalls_handle = + service[i++].attribute_handle; + ccsControlServiceInfo.bearer_list_currentcalls_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << " bearer_list_currentcalls_handle: " + << ccsControlServiceInfo.bearer_list_currentcalls_handle + << " bearer_list_currentcalls_desc: " + << ccsControlServiceInfo.bearer_list_currentcalls_desc; + + } else if(service[i].uuid == GTBS_CALL_STATUS_FLAGS) { + ccsControlServiceInfo.call_status_flags_handle = service[i++].attribute_handle; + ccsControlServiceInfo.call_status_flags_desc = service[i].attribute_handle; + LOG(INFO) << __func__ << " call_status_flags_handle: " + << ccsControlServiceInfo.call_status_flags_handle + << " call_status_flags_desc: " + << ccsControlServiceInfo.call_status_flags_desc; + + } else if(service[i].uuid == GTBS_INCOMINGCALL_TARGET_URI) { + ccsControlServiceInfo.incoming_call_target_beareruri_handle = + service[i++].attribute_handle; + ccsControlServiceInfo.incoming_call_target_bearerURI_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << " incoming_call_target_beareruri_handle: " + << ccsControlServiceInfo.incoming_call_target_beareruri_handle + << " incoming_call_target_bearerURI_desc: " + << ccsControlServiceInfo.incoming_call_target_bearerURI_desc; + } else if(service[i].uuid == GTBS_INCOMING_CALL) { + ccsControlServiceInfo.incoming_call_handle = service[i++].attribute_handle; + ccsControlServiceInfo.incoming_call_desc = service[i].attribute_handle; + LOG(INFO) << __func__ << " incoming_call_handle: " + << ccsControlServiceInfo.incoming_call_handle + << " incoming_call_desc: " + << ccsControlServiceInfo.incoming_call_desc; + } else if(service[i].uuid == GTBS_CONTENT_CONTROLID) { + ccsControlServiceInfo.ccid_handle = service[i].attribute_handle; + LOG(INFO) << __func__ << " ccid_handle: " << ccsControlServiceInfo.ccid_handle; + //Declare the CC Initialization + cc_instance->CallControlInitializedCallback(0); + } else if(service[i].uuid == GTBS_CALL_TERMINATION_REASON) { + ccsControlServiceInfo.call_termination_reason_handle = + service[i++].attribute_handle; + ccsControlServiceInfo.call_termination_reason_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << " call_termination_reason_handle: " + << ccsControlServiceInfo.call_termination_reason_handle + << " call_termination_reason_desc: " + << ccsControlServiceInfo.call_termination_reason_desc; + } else if(service[i].uuid == GTBS_CALL_FRIENDLY_NAME) { + ccsControlServiceInfo.call_friendly_name_handle = service[i++].attribute_handle; + ccsControlServiceInfo.call_friendly_name_desc = service[i].attribute_handle; + LOG(INFO) << __func__ << " call_friendly_name_handle: " + << ccsControlServiceInfo.call_friendly_name_handle + << " call_friendly_name_desc: " + << ccsControlServiceInfo.call_friendly_name_desc; + } + } +} + +void PrintData(uint8_t data[], uint16_t len) { + for (int i=0; i _data; + _data.clear(); + uint8_t status = BT_STATUS_SUCCESS; + rsp->event = CCS_NONE_EVENT; + bool isCallControllerOpUsed = false; + switch (event) { + + case CCS_INIT_EVENT: + { + Uuid aap_uuid = bluetooth::Uuid::GetRandom(); + BTA_GATTS_AppRegister(aap_uuid, BTCcCback, true); + break; + } + + case CCS_CLEANUP_EVENT: + { + //unregister APP + BTA_GATTS_AppDeregister(ccsControlServiceInfo.server_if); + cc_instance->remoteDevices.RemoveDevices(); + break; + } + case BTA_GATTS_REG_EVT: + { + p_data = (tBTA_GATTS*)param; + if (p_data->reg_oper.status == BT_STATUS_SUCCESS) { + ccsControlServiceInfo.server_if = p_data->reg_oper.server_if; + std::vector service; + service = CcAddService(ccsControlServiceInfo.server_if); + if (service[0].uuid == Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER) || + service[0].uuid == Uuid::From16Bit(UUID_SERVCLASS_GAP_SERVER)) { + LOG(INFO) << __func__ << " service app register uuid is not valid"; + break; + } + LOG(INFO) << __func__ << " service app register"; + BTA_GATTS_AddService(ccsControlServiceInfo.server_if, service, base::Bind(&OnGtbsServiceAddedCb)); + } + break; + } + + case BTA_GATTS_DEREG_EVT: + { + break; + } + + case BTA_GATTS_CONF_EVT: { + p_data = (tBTA_GATTS*)param; + uint16_t conn_id = p_data->req_data.conn_id; + uint8_t status = p_data->req_data.status; + LOG(INFO) << __func__ << "conn_id :" << conn_id << "status:" << status; + if (status == BT_STATUS_SUCCESS) { + LOG(INFO) << __func__ << "Notification callback for conn_id :" << conn_id; + GattsOpsQueue::NotificationCallback(conn_id); + } + break; + } + + case BTA_GATTS_CONGEST_EVT: + { + p_data = (tBTA_GATTS*)param; + CallControllerDeviceList *remoteDevice; + remoteDevice = cc_instance->remoteDevices.FindByConnId(p_data->congest.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " connection entry not found conn_id: " + << p_data->congest.conn_id; + break; + } + // rsp->ConngestionOp.status = p_data->req_data.status; + rsp->remoteDevice = remoteDevice; + rsp->oper.CongestionOp.congested = p_data->congest.congested; + rsp->event = CCS_CONGESTION_UPDATE; + break; + } + case BTA_GATTS_MTU_EVT: { + p_data = (tBTA_GATTS*)param; + + CallControllerDeviceList *remoteDevice; + remoteDevice = cc_instance->remoteDevices.FindByConnId(p_data->congest.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " connection entry not found conn_id: " + << p_data->congest.conn_id; + break; + } + rsp->event = CCS_MTU_UPDATE; + rsp->remoteDevice = remoteDevice; + rsp->oper.MtuOp.mtu = p_data->req_data.p_data->mtu; + break; + } + case BTA_GATTS_CONNECT_EVT: { + + p_data = (tBTA_GATTS*)param; + LOG(INFO) << __func__ << " remote devices connected"; + /* + #if (!defined(BTA_SKIP_BLE_START_ENCRYPTION) || BTA_SKIP_BLE_START_ENCRYPTION == FALSE) + btif_gatt_check_encrypted_link(p_data->conn.remote_bda, + p_data->conn.transport); + #endif*/ + CallControllerDeviceList remoteDevice; + memset(&remoteDevice, 0, sizeof(remoteDevice)); + if(cc_instance->remoteDevices.FindByAddress(p_data->conn.remote_bda)) { + LOG(INFO) << __func__ << " remote devices already there is connected list"; + status = BT_STATUS_FAIL; + return; + } + remoteDevice.peer_bda = p_data->conn.remote_bda; + remoteDevice.conn_id = p_data->conn.conn_id; + if(cc_instance->remoteDevices.Add(remoteDevice) == false) { + LOG(INFO) << __func__ << " remote device is not added : max connection reached"; + // need to check disconnection required + break; + } + remoteDevice.state = CCS_DISCONNECTED; + + LOG(INFO) << __func__ << " remote devices connected conn_id: "<< remoteDevice.conn_id << + "bd_addr " << remoteDevice.peer_bda; + + rsp->event = CCS_CONNECTION; + rsp->remoteDevice = cc_instance->remoteDevices.FindByAddress(p_data->conn.remote_bda); + if (rsp->remoteDevice == NULL) { + LOG(INFO)<<__func__ << " remote dev is null"; + break; + } + break; + } + + case BTA_GATTS_DISCONNECT_EVT: { + LOG(INFO) << __func__ << " remote devices disconnected"; + p_data = (tBTA_GATTS*)param; + CallControllerDeviceList *remoteDevice; + remoteDevice = cc_instance->remoteDevices.FindByConnId(p_data->conn_update.conn_id); + if((!remoteDevice) ) { + status = BT_STATUS_FAIL; + break; + } + + rsp->remoteDevice->peer_bda = remoteDevice->peer_bda; + rsp->event = CCS_DISCONNECTION; + rsp->remoteDevice = remoteDevice; + break; + } + + case BTA_GATTS_STOP_EVT: + //Do nothing + break; + + case BTA_GATTS_DELELTE_EVT: + //Do nothing + break; + + case BTA_GATTS_READ_CHARACTERISTIC_EVT: { + p_data = (tBTA_GATTS*)param; + std::vector value; + CallControllerDeviceList *remoteDevice = + cc_instance->remoteDevices.FindByConnId(p_data->req_data.conn_id); + LOG(INFO) << __func__ << " charateristcs read handle " << + p_data->req_data.p_data->read_req.handle <<" trans_id : " << + p_data->req_data.trans_id; + + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " device not found ignore read operation"; + status = BT_STATUS_FAIL; + break; + } + + LOG(INFO) <<" offset: " << p_data->req_data.p_data->read_req.offset << + " long : " << p_data->req_data.p_data->read_req.is_long; + + tGATTS_RSP rsp_struct; + rsp_struct.attr_value.auth_req = 0; + rsp_struct.attr_value.handle = p_data->req_data.p_data->read_req.handle; + rsp_struct.attr_value.offset = p_data->req_data.p_data->read_req.offset; + + if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.call_state_handle) { + std::vector loc_desc_value; + std::map::iterator it; + for (it = CallStatelist.begin(); it != CallStatelist.end(); it++){ + tCCS_CALL_STATE obj = it->second; + loc_desc_value.push_back(obj.index); + loc_desc_value.push_back(obj.state); + loc_desc_value.push_back(obj.flags); + } + size_t count = std::min((size_t)GATT_MAX_ATTR_LEN, loc_desc_value.size()); + rsp_struct.attr_value.len = count; + memcpy(rsp_struct.attr_value.value, loc_desc_value.data(), rsp_struct.attr_value.len); + + + LOG(INFO) << __func__ << " CallStateInfo read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.call_control_point_opcode_supported_handle) { + + rsp_struct.attr_value.len = sizeof(SupportedOptionalOpcodes.supp_opcode); + memcpy(rsp_struct.attr_value.value, &SupportedOptionalOpcodes.supp_opcode, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " callcontrol_point_opcode_supported_handle read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_provider_name_handle) { + LOG(INFO) << __func__ << " BearerProviderInfo name read " << BearerProviderInfo.name; + rsp_struct.attr_value.len = strlen((char *)BearerProviderInfo.name); + LOG(INFO) << __func__ << " BearerProviderInfo name len: " <req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_list_currentcalls_handle) { + + std::vector loc_desc_value; + std::map::iterator it; + for (it = BlccInfolist.begin(); it != BlccInfolist.end(); it++){ + tCCS_BEARER_LIST_CURRENT_CALLS obj = it->second; + loc_desc_value.push_back(obj.list_length); + loc_desc_value.push_back(obj.call_index); + loc_desc_value.push_back(obj.call_state); + loc_desc_value.push_back(obj.call_flags); + + loc_desc_value.insert(loc_desc_value.end(), + obj.call_uri, obj.call_uri+(obj.list_length-3)); + } + size_t count = std::min((size_t)GATT_MAX_ATTR_LEN, loc_desc_value.size()); + rsp_struct.attr_value.len = count; + memcpy(rsp_struct.attr_value.value, loc_desc_value.data(), rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " BlccInfo read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_technology_handle) { + rsp_struct.attr_value.len = sizeof(BearerProviderInfo.technology_type); + memcpy(rsp_struct.attr_value.value, &BearerProviderInfo.technology_type, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " technology_type read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_uci_handle) { + rsp_struct.attr_value.len = strlen((char *)BearerProviderInfo.uci); + memcpy(rsp_struct.attr_value.value, BearerProviderInfo.uci, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " Bearer UCI read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_signal_strength_handle) { + rsp_struct.attr_value.len = sizeof(BearerProviderInfo.signal); + memcpy(rsp_struct.attr_value.value, &BearerProviderInfo.signal, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " signal strength read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_signal_strength_report_interval_handle) { + rsp_struct.attr_value.len = sizeof(BearerProviderInfo.signal_report_interval); + memcpy(rsp_struct.attr_value.value, &BearerProviderInfo.signal_report_interval, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " signal_report_interval read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_uri_schemes_supported_handle) { + rsp_struct.attr_value.len = strlen((const char*)BearerProviderInfo.bearer_schemes_list); + memcpy(rsp_struct.attr_value.value, BearerProviderInfo.bearer_schemes_list, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " bearer_schemes_list read"; + }else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.incoming_call_handle) { + rsp_struct.attr_value.len = 1 + strlen((char*)IncomingCallInfo.incoming_uri); + memcpy(rsp_struct.attr_value.value, &IncomingCallInfo, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " Incoming call read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.incoming_call_target_beareruri_handle) { + rsp_struct.attr_value.len = 1 + strlen((char*)IncomingCallTargetUri.incoming_target_uri); + memcpy(rsp_struct.attr_value.value, &IncomingCallTargetUri, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " Incoming Call target URI read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.ccid_handle) { + rsp_struct.attr_value.len = sizeof(CcidInfo); + memcpy(rsp_struct.attr_value.value, &CcidInfo, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " Content Control read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.call_status_flags_handle) { + rsp_struct.attr_value.len = sizeof(StatusFlags.supported_flags); + memcpy(rsp_struct.attr_value.value, &StatusFlags.supported_flags, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " Status flags read"; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.call_friendly_name_handle) { + rsp_struct.attr_value.len = 1 + strlen((char*)FriendlyName.name); + memcpy(rsp_struct.attr_value.value, &FriendlyName, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " Call friendly name read"; + }else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.call_termination_reason_handle) { + rsp_struct.attr_value.len = sizeof(TerminationReason); + memcpy(rsp_struct.attr_value.value, &TerminationReason, rsp_struct.attr_value.len); + LOG(INFO) << __func__ << " Termination Reason read"; + } + else { + LOG(INFO) << __func__ << " read request for unknow handle " << p_data->req_data.p_data->read_req.handle; + status = BT_STATUS_FAIL; + break; + } + LOG(INFO) << __func__ << " read request handle " << p_data->req_data.p_data->read_req.handle << + "connection id " << p_data->req_data.conn_id; + rsp->oper.ReadOp.char_handle = rsp_struct.attr_value.handle; + rsp->oper.ReadOp.trans_id = p_data->req_data.trans_id; + rsp->oper.ReadOp.status = BT_STATUS_SUCCESS; + rsp->event = CCS_READ_RSP; + rsp->remoteDevice = remoteDevice; + + memcpy((void*)&rsp->rsp_value, &rsp_struct, sizeof(rsp_struct)); + break; + } + + case BTA_GATTS_READ_DESCRIPTOR_EVT: { + LOG(INFO) << __func__ << " read descriptor"; + p_data = (tBTA_GATTS*)param; + LOG(INFO) << __func__ << " charateristcs read desc handle " << + p_data->req_data.p_data->read_req.handle << " offset : " + << p_data->req_data.p_data->read_req.offset; + CallControllerDeviceList *remoteDevice = + cc_instance->remoteDevices.FindByConnId(p_data->req_data.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " device not found ignore write"; + status = BT_STATUS_FAIL; + break; + } + uint16_t data = 0x00; + if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.call_state_desc) { + LOG(INFO) << __func__ << " call_state_desc read"; + data = remoteDevice->call_state_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.call_control_point_desc) { + LOG(INFO) << __func__ << " callcontrol_point_desc read"; + data = remoteDevice->call_control_point_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_provider_name_desc) { + LOG(INFO) << __func__ << " bearer_provider_name_desc read"; + data = remoteDevice->bearer_provider_name_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_signal_strength_desc) { + LOG(INFO) << __func__ << " bearer_signal_strength desc read"; + data = remoteDevice->bearer_signal_strength_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_list_currentcalls_desc) { + LOG(INFO) << __func__ << " bearer_list_currentcall read"; + data = remoteDevice->bearer_current_calls_list_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_technology_desc) { + LOG(INFO) << __func__ << " bearer_technology_desc read"; + data = remoteDevice->bearer_technology_changed_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_uci_desc) { + LOG(INFO) << __func__ << " bearer_uci_desc read"; + data = remoteDevice->bearer_uci_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_uri_schemes_supported_desc) { + LOG(INFO) << __func__ << " bearer_uri_schemes_supported_desc read"; + data = remoteDevice->bearer_uri_schemes_supported_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.call_friendly_name_desc) { + LOG(INFO) << __func__ << " call_friendly_name_desc read"; + data = remoteDevice->call_friendly_name_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.call_control_point_opcode_supported_desc) { + LOG(INFO) << __func__ << " call_control_point_opcode_supported_desc read"; + data = remoteDevice->call_control_point_opcode_supported_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.call_termination_reason_desc) { + LOG(INFO) << __func__ << " call_termination_reason_desc read"; + data = remoteDevice->call_termination_reason_notify; + } else if(p_data->req_data.p_data->read_req.handle == + ccsControlServiceInfo.bearer_signal_strength_report_interval_desc) { + LOG(INFO) << __func__ << " bearer_signal_strength_report_interval_desc read"; + data = remoteDevice->bearer_signal_strength_report_interval_notify; + } else { + LOG(INFO) << __func__ << " read request for unknown handle " << p_data->req_data.p_data->read_req.handle; + status = BT_STATUS_FAIL; + break; + } + rsp->rsp_value.attr_value.auth_req = 0; + rsp->rsp_value.attr_value.handle = p_data->req_data.p_data->read_req.handle; + rsp->rsp_value.attr_value.offset = p_data->req_data.p_data->read_req.offset; + *(uint16_t *)rsp->rsp_value.attr_value.value = (uint16_t)data; + rsp->rsp_value.attr_value.len = sizeof(data); + //cc response + rsp->oper.ReadDescOp.desc_handle = p_data->req_data.p_data->read_req.handle; + rsp->oper.ReadDescOp.trans_id = p_data->req_data.trans_id; + rsp->oper.ReadDescOp.status = BT_STATUS_SUCCESS; + rsp->remoteDevice = remoteDevice; + rsp->event = CCS_DESCRIPTOR_READ_RSP; + break; + } + + case BTA_GATTS_WRITE_CHARACTERISTIC_EVT: { + + p_data = (tBTA_GATTS*)param; + tGATT_WRITE_REQ req = p_data->req_data.p_data->write_req; + LOG(INFO) << __func__ << " write characteristics len: " << req.len; + PrintData(req.value, req.len); + CallControllerDeviceList *remoteDevice = + cc_instance->remoteDevices.FindByConnId(p_data->req_data.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " device not found ignore write"; + status = BT_STATUS_FAIL; + break; + } + if ( status != BT_STATUS_FAIL) { + rsp->oper.WriteOp.char_handle = req.handle; + rsp->oper.WriteOp.trans_id = p_data->req_data.trans_id; + rsp->oper.WriteOp.status = BT_STATUS_SUCCESS; + rsp->remoteDevice = remoteDevice; + rsp->oper.WriteOp.need_rsp = req.need_rsp; + rsp->oper.WriteOp.offset = req.offset; + rsp->oper.WriteOp.len = req.len; + rsp->oper.WriteOp.data = (uint8_t*) malloc(sizeof(uint8_t)*req.len); + memcpy(rsp->oper.WriteOp.data, req.value, req.len); + rsp->event = CCS_WRITE_RSP; + } + break; + } + + case BTA_GATTS_WRITE_DESCRIPTOR_EVT: { + + p_data = (tBTA_GATTS* )param; + std::vector write_desc_value; + write_desc_value.clear(); + uint16_t handle = 0; + uint16_t req_value = 0; + const auto& req = p_data->req_data.p_data->write_req; + CallControllerDeviceList *remoteDevice = + cc_instance->remoteDevices.FindByConnId(p_data->req_data.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " device not found ignore notification"; + break; + } + req_value = *(uint16_t* )req.value; + //need to initialized with proper error code + int status = BT_STATUS_SUCCESS; + LOG(INFO) << __func__ << " write descriptor :" << req.handle << + " is resp: " << req.need_rsp << "is prep: " << req.is_prep << " value " << req_value; + + if(req.handle == + ccsControlServiceInfo.call_state_desc) { + LOG(INFO) << __func__ << " call_state_desc descriptor write"; + remoteDevice->call_state_notify = req_value; + std::map::iterator it; + for (it = CallStatelist.begin(); it != CallStatelist.end(); it++){ + tCCS_CALL_STATE obj = it->second; + write_desc_value.push_back(obj.index); + write_desc_value.push_back(obj.state); + write_desc_value.push_back(obj.flags); + } + handle = ccsControlServiceInfo.call_state_handle; + } else if(req.handle == + ccsControlServiceInfo.bearer_provider_name_desc) { + remoteDevice->bearer_provider_name_notify = req_value; + LOG(INFO) << __func__ << " bearer_provider_name_desc descriptor write"; + write_desc_value.assign(BearerProviderInfo.name, + BearerProviderInfo.name + strlen((char*)BearerProviderInfo.name)); + handle = ccsControlServiceInfo.bearer_provider_name_handle; + } else if(req.handle == + ccsControlServiceInfo.call_control_point_desc) { + LOG(INFO) << __func__ << " callcontrol_point_desc write"; + write_desc_value.push_back(CallControllerResp.opcode); + write_desc_value.push_back(CallControllerResp.index); + write_desc_value.push_back(CallControllerResp.response_status); + + remoteDevice->call_control_point_notify = req_value; + handle = ccsControlServiceInfo.call_control_point_handle; + } else if(req.handle == + ccsControlServiceInfo.bearer_signal_strength_desc) { + LOG(INFO) << __func__ << " bearer_signal_strength desc write"; + write_desc_value.push_back(BearerProviderInfo.signal); + remoteDevice->bearer_signal_strength_notify = req_value; + handle = ccsControlServiceInfo.bearer_signal_strength_handle; + } else if(req.handle == + ccsControlServiceInfo.bearer_uri_schemes_supported_desc) { + remoteDevice->bearer_uri_schemes_supported_notify = req_value; + write_desc_value.assign(BearerProviderInfo.bearer_schemes_list, + BearerProviderInfo.bearer_schemes_list + strlen((char*)BearerProviderInfo.bearer_schemes_list)); + LOG(INFO) << __func__ << " bearer_schemes_list Desc write"; + } else if(req.handle == + ccsControlServiceInfo.bearer_list_currentcalls_desc) { + std::map::iterator it; + LOG(INFO) << __func__ << " bearer_list_currentcall desc write"; + for (it = BlccInfolist.begin(); it != BlccInfolist.end(); it++){ + tCCS_BEARER_LIST_CURRENT_CALLS obj = it->second; + write_desc_value.push_back(obj.list_length); + write_desc_value.push_back(obj.call_index); + write_desc_value.push_back(obj.call_state); + write_desc_value.push_back(obj.call_flags); + write_desc_value.insert(write_desc_value.end(), + obj.call_uri, obj.call_uri+(obj.list_length-3)); + } + remoteDevice->bearer_current_calls_list_notify = req_value; + handle = ccsControlServiceInfo.bearer_list_currentcalls_handle; + + } else if(req.handle == + ccsControlServiceInfo.bearer_technology_desc) { + LOG(INFO) << __func__ << " bearer_technology_desc write"; + remoteDevice->bearer_technology_changed_notify = req_value; + write_desc_value.push_back(BearerProviderInfo.technology_type); + handle = ccsControlServiceInfo.bearer_technology_handle; + } else if(req.handle == + ccsControlServiceInfo.call_friendly_name_desc) { + LOG(INFO) << __func__ << " call_friendly_name_desc read"; + remoteDevice->call_friendly_name_notify = req_value; + int len = 1 + strlen((char*)FriendlyName.name); + write_desc_value.assign((uint8_t*)&FriendlyName, (uint8_t*)&FriendlyName+len); + handle = ccsControlServiceInfo.call_friendly_name_handle; + } else if(req.handle == + ccsControlServiceInfo.call_termination_reason_desc) { + LOG(INFO) << __func__ << " call_termination_reason_desc write"; + remoteDevice->call_termination_reason_notify = req_value; + handle = ccsControlServiceInfo.call_termination_reason_handle; + write_desc_value.push_back(TerminationReason.index); + write_desc_value.push_back(TerminationReason.reason); + + handle = ccsControlServiceInfo.call_termination_reason_handle; + } else if(req.handle == + ccsControlServiceInfo.call_status_flags_desc) { + LOG(INFO) << __func__ << " Status Flags Desc write"; + remoteDevice->status_flags_notify= req_value; + write_desc_value.push_back(StatusFlags.supported_flags); + handle = ccsControlServiceInfo.call_status_flags_handle; + + } else if(req.handle == + ccsControlServiceInfo.incoming_call_target_bearerURI_desc) { + LOG(INFO) << __func__ << " Incoming target bearer desc write"; + int len = 1 + strlen((char*)IncomingCallTargetUri.incoming_target_uri); + write_desc_value.assign((uint8_t*)&IncomingCallTargetUri, (uint8_t*)&IncomingCallTargetUri+len); + remoteDevice->incoming_call_target_URI_notify= req_value; + handle = ccsControlServiceInfo.incoming_call_target_beareruri_handle; + + } else if(req.handle == + ccsControlServiceInfo.incoming_call_desc) { + LOG(INFO) << __func__ << " Incoming Call desc write"; + remoteDevice->incoming_call_state_notify = req_value; + int len = 1 + strlen((char*)IncomingCallInfo.incoming_uri); + write_desc_value.assign((uint8_t*)&IncomingCallInfo, (uint8_t*)&IncomingCallInfo+len); + + handle = ccsControlServiceInfo.incoming_call_handle; + } else { + LOG(INFO) << __func__ << " descriptor write not matched"; + status = 4; //check error code + } + rsp->oper.WriteDescOp.desc_handle = req.handle; + rsp->oper.WriteDescOp.char_handle = handle; + rsp->oper.WriteDescOp.trans_id = p_data->req_data.trans_id; + rsp->oper.WriteDescOp.status = status; + rsp->oper.WriteDescOp.need_rsp = req.need_rsp; + rsp->oper.WriteDescOp.notification = req_value; + rsp->oper.WriteDescOp.value = std::move(write_desc_value); + rsp->event = CCS_DESCRIPTOR_WRITE_RSP; + rsp->remoteDevice = remoteDevice; + break; + } + + case BTA_GATTS_EXEC_WRITE_EVT: { + p_data = (tBTA_GATTS*)param; + break; + } + + case BTA_GATTS_CLOSE_EVT: { + //not required + break; + } + + case BTA_GATTS_PHY_UPDATE_EVT: { + + p_data = (tBTA_GATTS*)param; + CallControllerDeviceList *remoteDevice = + cc_instance->remoteDevices.FindByConnId(p_data->phy_update.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " device not found ignore phy update " + << p_data->phy_update.status; + status = BT_STATUS_FAIL; + break; + } + rsp->event = CCS_PHY_UPDATE; + rsp->oper.PhyOp.rx_phy = p_data->phy_update.rx_phy; + rsp->oper.PhyOp.tx_phy = p_data->phy_update.tx_phy; + rsp->remoteDevice = remoteDevice; + break; + } + + case BTA_GATTS_CONN_UPDATE_EVT: { + p_data = (tBTA_GATTS*)param; + CallControllerDeviceList *remoteDevice = + cc_instance->remoteDevices.FindByConnId(p_data->phy_update.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " connection update device not found"; + break; + } + LOG(INFO) << __func__ << " connection update status " << p_data->phy_update.status; + rsp->event = CCS_CONNECTION_UPDATE; + rsp->oper.ConnectionUpdateOp.remoteDevice = remoteDevice; + rsp->oper.ConnectionUpdateOp.remoteDevice->latency = p_data->conn_update.latency; + rsp->oper.ConnectionUpdateOp.remoteDevice->timeout = p_data->conn_update.timeout; + rsp->oper.ConnectionUpdateOp.remoteDevice->interval = p_data->conn_update.interval; + rsp->oper.ConnectionUpdateOp.status = p_data->conn_update.status; + rsp->remoteDevice = remoteDevice; + break; + } + + case CCS_ACTIVE_DEVICE_UPDATE: + { + tCCS_SET_ACTIVE_DEVICE *data = (tCCS_SET_ACTIVE_DEVICE *)param; + LOG(INFO) << __func__ << " CCS_ACTIVE_DEVICE_UPDATE address " << data->address; + if (cc_instance->remoteDevices.FindByAddress(data->address) != NULL) { + cc_instance->remoteDevices.AddSetActiveDevice(data); + } else { + LOG(ERROR) << __func__ << " CCS_ACTIVE_DEVICE_UPDATE failed as given remote is not registered for notif " << data->address; + } + break; + } + case CCS_CONNECTION_CLOSE_EVENT: + { + break; + } + case CCS_CALL_STATE_UPDATE: + { + std::map::iterator it; + for (it = CallStatelist.begin(); it != CallStatelist.end(); it++){ + tCCS_CALL_STATE obj = it->second; + _data.push_back(obj.index); + _data.push_back(obj.state); + _data.push_back(obj.flags); + } + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.call_state_handle; + isCallControllerOpUsed = true; + break; + } + case CCS_BEARER_NAME_UPDATE: + { + LOG(INFO) << __func__ << " CCS_BEARER_NAME_UPDATE"; + tCCS_BEARER_PROVIDER_INFO *data = (tCCS_BEARER_PROVIDER_INFO*) param; + uint16_t len = strlen((char*)data->name); + ReverseByteOrder(data->name, len); + memcpy(BearerProviderInfo.name, data->name, len); + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.bearer_provider_name_handle; + _data.assign(BearerProviderInfo.name, + BearerProviderInfo.name + len); + isCallControllerOpUsed = true; + break; + } + + case CCS_OPT_OPCODES: + { + LOG(INFO) << __func__ << " CCS_OPT_OPCODES"; + tCCS_SUPP_OPTIONAL_OPCODES *data = (tCCS_SUPP_OPTIONAL_OPCODES*) param; + SupportedOptionalOpcodes.supp_opcode = data->supp_opcode; + + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.call_control_point_opcode_supported_handle; + + _data.push_back(SupportedOptionalOpcodes.supp_opcode); + isCallControllerOpUsed = true; + break; + } + + case CCS_BEARER_CURRENT_CALL_LIST_UPDATE: + { + LOG(INFO) << __func__ << " CCS_BEARER_CURRENT_CALL_LIST_UPDATE"; + std::map::iterator it; + for (it = BlccInfolist.begin(); it != BlccInfolist.end(); it++){ + tCCS_BEARER_LIST_CURRENT_CALLS obj = it->second; + _data.push_back(obj.list_length); + _data.push_back(obj.call_index); + _data.push_back(obj.call_state); + _data.push_back(obj.call_flags); + _data.insert(_data.end(), + obj.call_uri, obj.call_uri+(obj.list_length-3)); + } + + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.bearer_list_currentcalls_handle; + isCallControllerOpUsed = true; + break; + } + + case CCS_BEARER_SIGNAL_STRENGTH_UPDATE: + { + LOG(INFO) << __func__ << " CCS_BEARER_SIGNAL_STRENGTH_UPDATE"; + tCCS_BEARER_PROVIDER_INFO *data = (tCCS_BEARER_PROVIDER_INFO *) param; + BearerProviderInfo.signal = data->signal; + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.bearer_signal_strength_handle; + //control whether notification has to happen based + //on reporting Interval time or not + rsp->force = false; + _data.push_back(BearerProviderInfo.signal); + isCallControllerOpUsed = true; + break; + } + + case CCS_BEARER_TECHNOLOGY_UPDATE: + { + LOG(INFO) << __func__ << " CCS_BEARER_TECHNOLOGY_UPDATE"; + tCCS_BEARER_PROVIDER_INFO *data = (tCCS_BEARER_PROVIDER_INFO *) param; + BearerProviderInfo.technology_type = data->technology_type; + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.bearer_technology_handle; + _data.push_back(BearerProviderInfo.technology_type); + isCallControllerOpUsed = true; + break; + } + + case CCS_BEARER_URI_SCHEMES_SUPPORTED: { + LOG(INFO) << __func__ << " CCS_BEARER_URI_SCHEMES_SUPPORTED"; + tCCS_BEARER_PROVIDER_INFO *data = (tCCS_BEARER_PROVIDER_INFO*) param; + BearerProviderInfo.bearer_list_len = data->bearer_list_len; + LOG(INFO) << __func__ << " CCS_BEARER_URI_SCHEMES_SUPPORTED: len " <bearer_schemes_list, BearerProviderInfo.bearer_list_len); + memcpy(BearerProviderInfo.bearer_schemes_list, data->bearer_schemes_list, BearerProviderInfo.bearer_list_len); + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.bearer_uri_schemes_supported_handle; + isCallControllerOpUsed = true; + _data.assign(BearerProviderInfo.bearer_schemes_list, + BearerProviderInfo.bearer_schemes_list + BearerProviderInfo.bearer_list_len); + isCallControllerOpUsed = true; + break; + } + + case CCS_INCOMING_CALL_UPDATE: + { + LOG(INFO) << __func__ << " CCS_INCOMING_CALL_UPDATE"; + tCCS_INCOMING_CALL* data = (tCCS_INCOMING_CALL *) param; + IncomingCallInfo.index = data->index; + uint16_t len = strlen((char*)data->incoming_uri); + ReverseByteOrder(data->incoming_uri, len); + memcpy(IncomingCallInfo.incoming_uri, data->incoming_uri, len); + + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.incoming_call_handle; + _data.push_back(IncomingCallInfo.index); + _data.insert(_data.end(), IncomingCallInfo.incoming_uri, + IncomingCallInfo.incoming_uri + len); + isCallControllerOpUsed = true; + break; + } + + case CCS_INCOMING_TARGET_URI_UPDATE: + { + LOG(INFO) << __func__ << " CCS_INCOMING_TARGET_URI_UPDATE"; + tCCS_INCOMING_CALL_URI* data = (tCCS_INCOMING_CALL_URI *) param; + IncomingCallTargetUri.index = data->index; + uint16_t len = strlen((char*)data->incoming_target_uri); + LOG(INFO) << __func__ << " CCS_INCOMING_TARGET_URI_UPDATE: urilen " << len; + ReverseByteOrder(data->incoming_target_uri, len); + memcpy(IncomingCallTargetUri.incoming_target_uri, data->incoming_target_uri, len); + + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.incoming_call_target_beareruri_handle; + _data.push_back(IncomingCallTargetUri.index); + _data.insert(_data.end(), IncomingCallTargetUri.incoming_target_uri, + IncomingCallTargetUri.incoming_target_uri + len); + isCallControllerOpUsed = true; + break; + } + + case CCS_TERMINATION_REASON_UPDATE: + { + LOG(INFO) << __func__ << " CCS_TERMINATION_REASON_UPDATE"; + tCCS_TERM_REASON* data = (tCCS_TERM_REASON *) param; + TerminationReason.index = data->index; + TerminationReason.reason = data->reason; + + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.call_termination_reason_handle; + _data.push_back(TerminationReason.index); + _data.push_back(TerminationReason.reason); + + isCallControllerOpUsed = true; + break; + } + + case CCS_STATUS_FLAGS_UPDATE: + { + LOG(INFO) << __func__ << " CCS_STATUS_FLAGS_UPDATE"; + tCCS_STATUS_FLAGS *data = (tCCS_STATUS_FLAGS*) param; + + ReverseByteOrder((unsigned char*)&data->supported_flags, sizeof(data->supported_flags)); + StatusFlags.supported_flags = data->supported_flags; + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.call_status_flags_handle; + _data.push_back(StatusFlags.supported_flags); + isCallControllerOpUsed = true; + break; + } + + case CCS_CCID_UPDATE: + { + LOG(INFO) << __func__ << " CCS_CCID_UPDATE"; + tCCS_CONTENT_CONTROL_ID *data = (tCCS_CONTENT_CONTROL_ID *) param; + ReverseByteOrder((unsigned char*)&data->ccid, sizeof(data->ccid)); + CcidInfo.ccid = (uint32_t)data->ccid; + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.ccid_handle; + _data.push_back(CcidInfo.ccid); + isCallControllerOpUsed = true; + break; + } + + case CCS_CALL_CONTROL_RESPONSE: + { + LOG(INFO) << __func__ << " CCS_CALL_CONTROL_RESPONSE"; + + rsp->event = CCS_NOTIFY_ALL; + rsp->handle = ccsControlServiceInfo.call_control_point_handle; + _data.push_back(CallControllerResp.opcode); + _data.push_back(CallControllerResp.index); + _data.push_back(CallControllerResp.response_status); + isCallControllerOpUsed = true; + break; + } + default: + LOG(INFO) << __func__ << " event not matched !!"; + break; + } + + if(rsp->event != CCS_NONE_EVENT) { + if (isCallControllerOpUsed == true) { + rsp->oper.CallControllerOp.data = std::move(_data); + LOG(INFO) << __func__ << " After Moving size " << rsp->oper.CallControllerOp.data.size(); + } + CCSHandler(rsp->event, rsp); + } + if(rsp) { + LOG(INFO) << __func__ << "free rsp data"; + free(rsp); + } +} + + + +void BTCcCback(tBTA_GATTS_EVT event, tBTA_GATTS* param) { + + HandleCcsEvent((uint32_t)event, param); +} + + + bool DeviceStateConnectionHandler(uint32_t event, void* param) { + LOG(INFO) << __func__ << " device connected handle " << event; + tcc_resp_t *p_data = (tcc_resp_t *) param; + switch (event) { + + case CCS_NOTIFY_ALL: { + LOG(INFO) << __func__ << " device notify all"; + if (p_data->handle == ccsControlServiceInfo.bearer_signal_strength_handle) { + if (p_data->force == false && + p_data->remoteDevice->signal_strength_report_interval != 0) { + LOG(INFO) << __func__ << "Not a timer expired push, dont notify to remote"; + break; + } + } + + GattsOpsQueue::SendNotification(p_data->remoteDevice->conn_id, p_data->handle, + p_data->oper.CallControllerOp.data, false); + break; + } + + case CCS_READ_RSP: + + LOG(INFO) << __func__ << " device CCS_READ_RSP update " << event; + BTA_GATTS_SendRsp(p_data->remoteDevice->conn_id, p_data->oper.ReadOp.trans_id, + BT_STATUS_SUCCESS, &p_data->rsp_value); + break; + + case CCS_DESCRIPTOR_READ_RSP: + + LOG(INFO) << __func__ << " device CCS_DESCRIPTOR_READ_RSP update " << event; + BTA_GATTS_SendRsp(p_data->remoteDevice->conn_id, p_data->oper.ReadDescOp.trans_id, + BT_STATUS_SUCCESS, &p_data->rsp_value); + break; + + case CCS_DESCRIPTOR_WRITE_RSP: + { + LOG(INFO) << __func__ << " device CCS_DESCRIPTOR_WRITE_RSP update rsp: " << p_data->oper.WriteDescOp.need_rsp; + tGATTS_RSP rsp_struct; + rsp_struct.attr_value.handle = p_data->rsp_value.attr_value.handle; + rsp_struct.attr_value.offset = p_data->rsp_value.attr_value.offset; + if (p_data->remoteDevice->congested == false && + p_data->oper.WriteDescOp.need_rsp) { + //send rsp to write + LOG(INFO) << __func__ << " gett send rsp"; + BTA_GATTS_SendRsp(p_data->remoteDevice->conn_id, p_data->oper.WriteDescOp.trans_id, + p_data->oper.WriteDescOp.status, &rsp_struct); + } + if (!p_data->oper.WriteDescOp.status && p_data->oper.WriteDescOp.notification) { + //notify update to register device + GattsOpsQueue::SendNotification(p_data->remoteDevice->conn_id, + p_data->oper.WriteDescOp.char_handle, + p_data->oper.WriteDescOp.value, false); + } else { + LOG(INFO) << __func__ << " notification disable handle : " << p_data->oper.WriteDescOp.char_handle; + } + break; + } + + case CCS_WRITE_RSP: { + LOG(INFO) << __func__ << " device CCS_WRITE_RSP update " << event; + bool need_rsp = p_data->oper.WriteOp.need_rsp; + LOG(INFO) << __func__ << " device CCS_WRITE_RSP update " << event << " need_rsp: " <remoteDevice->conn_id, p_data->oper.WriteOp.trans_id, + BT_STATUS_SUCCESS, &p_data->rsp_value); + } + break; + } + + case CCS_CONNECTION_UPDATE: { + LOG(INFO) << __func__ << " device cconnection update " << event; + break; + } + + case CCS_PHY_UPDATE: { + LOG(INFO) << __func__ << " device CCS_PHY_UPDATE update " << event; + p_data->remoteDevice->rx_phy = p_data->oper.PhyOp.rx_phy; + p_data->remoteDevice->tx_phy = p_data->oper.PhyOp.tx_phy; + break; + } + + case CCS_MTU_UPDATE: { + LOG(INFO) << __func__ << " device CCS_MTU_UPDATE update " << event; + p_data->remoteDevice->mtu = p_data->oper.MtuOp.mtu; + break; + } + + case CCS_CONGESTION_UPDATE: { + LOG(INFO) << __func__ << ": device CCS_CONGESTION_UPDATE update: " << event; + CcpCongestionUpdate(p_data); + break; + } + + case CCS_DISCONNECTION: { + LOG(INFO) << __func__ << " device CCS_DISCONNECTION remove " << event; + cc_instance->remoteDevices.Remove(p_data->remoteDevice->peer_bda); + cc_instance->ConnectionStateCallback(CCS_DISCONNECTED, p_data->remoteDevice->peer_bda); + break; + } + + case CCS_CONNECTION_CLOSE_EVENT: { + LOG(INFO) << __func__ << " device connection closing"; + // Close active connection + if (p_data->remoteDevice->conn_id != 0) + BTA_GATTS_Close(p_data->remoteDevice->conn_id); + else + BTA_GATTS_CancelOpen(ccsControlServiceInfo.server_if, p_data->remoteDevice->peer_bda, true); + // Cancel pending background connections + BTA_GATTS_CancelOpen(ccsControlServiceInfo.server_if, p_data->remoteDevice->peer_bda, false); + break; + } + + case CCS_BOND_STATE_CHANGE_EVENT: + LOG(INFO) << __func__ << " Bond state change"; + cc_instance->remoteDevices.Remove(p_data->remoteDevice->peer_bda); + break; + + default: + LOG(INFO) << __func__ << " event not matched"; + break; + } + + return BT_STATUS_SUCCESS; +} + + +bool DeviceStateDisconnectedHandler(uint32_t event, void* param) { + LOG(INFO) << __func__ << " device disconnected handle " << event; + tcc_resp_t *p_data = (tcc_resp_t *) param; + switch (event) { + case CCS_CONNECTION: + { + LOG(INFO) << __func__ << " connection processing " << event; + p_data->remoteDevice->state = CCS_CONNECTED; + p_data->remoteDevice->call_state_notify = 0x00; + p_data->remoteDevice->bearer_provider_name_notify = 0x00; + p_data->remoteDevice->call_control_point_notify = 0x00; + p_data->remoteDevice->bearer_technology_changed_notify = 0x00; + p_data->remoteDevice->bearer_uci_notify = 0x00; + p_data->remoteDevice->bearer_current_calls_list_notify = 0x00; + p_data->remoteDevice->call_friendly_name_notify = 0x00; + p_data->remoteDevice->call_termination_reason_notify = 0x00; + p_data->remoteDevice->congested = false; + // p_data->remoteDevice->conn_id; + + p_data->remoteDevice->timeout = 0; + p_data->remoteDevice->latency = 0; + p_data->remoteDevice->interval = 0; + p_data->remoteDevice->rx_phy = 0; + p_data->remoteDevice->tx_phy = 0; + cc_instance->ConnectionStateCallback(CCS_CONNECTED, p_data->remoteDevice->peer_bda); + break; + } + + case CCS_CONGESTION_UPDATE: { + LOG(INFO) << __func__ << ": device CCS_CONGESTION_UPDATE update: " << event; + CcpCongestionUpdate(p_data); + break; + } + + case CCS_MTU_UPDATE: + case CCS_PHY_UPDATE: + case CCS_DISCONNECTED: + case CCS_READ_RSP: + case CCS_DESCRIPTOR_READ_RSP: + case CCS_WRITE_RSP: + case CCS_DESCRIPTOR_WRITE_RSP: + case CCS_NOTIFY_ALL: + case CCS_DISCONNECTION: + + default: + //ignore event + LOG(INFO) << __func__ << " Ignore event " << event; + break; + } + return BT_STATUS_SUCCESS; +} + +bool is_digits(const std::string &str) +{ + return str.find_first_not_of("0123456789+") == std::string::npos; +} + +bool IsValidOriginateUri(std::string uri) { + bool ret = false; + std::vector out; + char *token; + char* rest = const_cast(uri.c_str()); + while ((token = strtok_r(rest, ":", &rest))) + { + out.push_back(std::string(token)); + } + if (out.size() == 2) { + if (out[0].compare("tel") == 0 && is_digits(out[1])) { + ret = true; + } + } + LOG(INFO) << __func__ << " ret: " << ret; + return ret; +} + +bool CCSActiveProfile(RawAddress addr) { + bool isCCSActiveProfile = false; + int32_t activeProfile; + activeProfile = osi_property_get_int32("persist.vendor.qcom.bluetooth.default_profiles",0); + if ((activeProfile&0x2000) == 0x2000) { + isCCSActiveProfile = true; + } + LOG(INFO) << __func__ << " activeProfile "<< activeProfile <<" for" << addr; + return isCCSActiveProfile; +} + +bool CCSHandler(uint32_t event, void* param) { + + tcc_resp_t *p_data = (tcc_resp_t *)param; + + CallControllerDeviceList *device = p_data->remoteDevice; + LOG(INFO) << __func__ << " inactive ccs handle event "<< bta_cc_event_str(event); + switch(p_data->event) { + case CCS_NOTIFY_ALL: { + + std::vectornotifyDevices = cc_instance->remoteDevices.FindNotifyDevices(p_data->handle); + std::vector::iterator it; + LOG(INFO) << __func__ << " Notify all handle "<< p_data->handle; + if (notifyDevices.size() <= 0) { + LOG(INFO) << __func__ << " No device register for notification"; + break; + } + for (it = notifyDevices.begin(); it != notifyDevices.end(); it++){ + if (CCSActiveProfile(it->peer_bda)) { + LOG(INFO) << __func__ << " Notify all handle device id " << it->conn_id; + p_data->remoteDevice = cc_instance->remoteDevices.FindByConnId(it->conn_id); + it->DeviceStateHandlerPointer[it->state](p_data->event, p_data); + } + } + break; + } + case CCS_WRITE_RSP: { + + LOG(INFO) << __func__ << " Push Write response first: " << device->state; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data); + //Process the values now + uint8_t* data = p_data->oper.WriteOp.data; + int len = p_data->oper.WriteOp.len; + RawAddress peer_bda = p_data->remoteDevice->peer_bda; + std::map::iterator it; + bool valid_index = false; + uint8_t indices[10]; + char URI[MAX_URI_SIZE]; + int i; + uint32_t handle = p_data->oper.WriteOp.char_handle; + if(handle == ccsControlServiceInfo.call_control_point_handle) { + CallControllerOps.operation = data[0]; + LOG(INFO) << __func__ << " operation: " << std::hex << CallControllerOps.operation; + if (gIsActiveCC == true && !cc_instance->remoteDevices.FindActiveDevice(p_data->remoteDevice) && + (CallControllerOps.operation == CALL_TERMINATE || + CallControllerOps.operation == CALL_LOCAL_HOLD || + CallControllerOps.operation == CALL_LOCAL_RETRIEVE)) { + LOG(ERROR) << __func__ << "TERM|HOLD|RET|JOIN triggered from non-active Device"; + cc_instance->CallControlResponse(CallControllerOps.operation, + CCS_DEFAULT_INDEX_VAL, + CCS_OPCODE_UNSUCCESSFUL, peer_bda); + return true; + } + switch(CallControllerOps.operation) { + case CALL_ACCEPT:{ + if (len != 2) { + LOG(ERROR) << __func__ << " Invalid params"; + valid_index = false; + } else { + indices[0] = data[1]; + valid_index = false; + for (i=0,it = CallStatelist.begin(); it != CallStatelist.end(); it++, i++){ + tCCS_CALL_STATE obj = it->second; + if (obj.index == indices[0] && obj.state == CCS_STATE_INCOMING) { + valid_index = true; + break; + } + } + } + if (valid_index == true) { + cc_instance->CallControlPointChange(CallControllerOps.operation, indices, + DEFAULT_INDICIES_COUNT, NULL, + device->peer_bda); + cc_instance->CallControlResponse(CallControllerOps.operation, + indices[0], + CCS_STATUS_SUCCESS, peer_bda); + } else { + LOG(INFO) << " ACCEPT ignored as there is no valid Index"; + cc_instance->CallControlResponse(CallControllerOps.operation, + CCS_DEFAULT_INDEX_VAL, + CCS_INVALID_INDEX, peer_bda); + } + break; + } + case CALL_TERMINATE: { + if (len != 2) { + LOG(ERROR) << __func__ << " Invalid params"; + valid_index = false; + } else { + indices[0] = data[1]; + valid_index = false; + for (i=0,it = CallStatelist.begin(); it != CallStatelist.end(); it++, i++) { + tCCS_CALL_STATE obj = it->second; + if (obj.index == indices[0]) { + LOG(INFO) << __func__ << " call index match: " << indices[0]; + valid_index = true; + break; + } + } + } + if (valid_index == true) { + cc_instance->CallControlPointChange(CallControllerOps.operation, indices, + DEFAULT_INDICIES_COUNT, NULL, + device->peer_bda); + cc_instance->CallControlResponse(CallControllerOps.operation, + indices[0], + CCS_STATUS_SUCCESS, peer_bda); + gIsTerminatedInitiatedFromClient = true; + gTerminateIntiatedIndex = indices[0]; + } else { + LOG(INFO) << " TERMINATE ignored as there is no valid Index"; + cc_instance->CallControlResponse(CallControllerOps.operation, + CCS_DEFAULT_INDEX_VAL, + CCS_INVALID_INDEX, peer_bda); + } + break; + } + case CALL_LOCAL_HOLD: { + if (len != 2) { + LOG(ERROR) << __func__ << " Invalid params"; + valid_index = false; + } else { + indices[0]= data[1]; + valid_index = false; + for (i=0,it = CallStatelist.begin(); it != CallStatelist.end(); it++, i++) { + tCCS_CALL_STATE obj = it->second; + if (obj.index == indices[0] && + (obj.state == CCS_STATE_INCOMING || obj.state == CCS_STATE_ACTIVE)) { + LOG(INFO) << __func__ << " call index match: " << indices[0]; + valid_index = true; + break; + } + } + } + if (valid_index == true) { + cc_instance->CallControlPointChange(CallControllerOps.operation, indices, + DEFAULT_INDICIES_COUNT, NULL, + device->peer_bda); + + } else { + LOG(INFO) << " CALL_LOCAL_HOLD ignored as there is no valid Index"; + cc_instance->CallControlResponse(CallControllerOps.operation, + CCS_DEFAULT_INDEX_VAL, + CCS_INVALID_INDEX, peer_bda); + } + break; + } + case CALL_LOCAL_RETRIEVE: { + if (len != 2) { + LOG(ERROR) << __func__ << " Invalid params"; + valid_index = false; + } else { + indices[0]= data[1]; + valid_index = false; + for (i=0,it = CallStatelist.begin(); it != CallStatelist.end(); it++, i++) { + tCCS_CALL_STATE obj = it->second; + if (obj.index == indices[0] && (obj.state == CCS_STATE_LOCAL_HELD )) { + LOG(INFO) << __func__ << " call index match: " << indices[0]; + valid_index = true; + break; + } + } + } + if (valid_index == true) { + cc_instance->CallControlPointChange( CallControllerOps.operation, indices, + DEFAULT_INDICIES_COUNT, NULL, + device->peer_bda); + + } else { + LOG(INFO) << " CALL_LOCAL_RETRIEVE ignored as there is no valid Index"; + cc_instance->CallControlResponse(CallControllerOps.operation, + CCS_DEFAULT_INDEX_VAL, + CCS_INVALID_INDEX, peer_bda); + } + break; + } + case CALL_ORIGINATE: { + LOG(INFO) << __func__ << " CALL_ORIGINATE match:"; + memset(URI, '\0', sizeof(char)*MAX_URI_SIZE); + strlcpy(URI, (char*)&data[1], len); + LOG(INFO) << __func__ << " ORIGINATE: " << URI; + if (IsValidOriginateUri(URI)) { + cc_instance->CallControlPointChange(CallControllerOps.operation, /*unused*/0, + /*count*/0, URI, device->peer_bda); + } else { + cc_instance->CallControlResponse(CallControllerOps.operation, + CCS_DEFAULT_INDEX_VAL, + CCS_INVALID_OUTGOING_URI, peer_bda); + } + break; + } + case CALL_JOIN: { + int count = len-1; + uint8_t *start_of_indicies = &(data[1]); + uint8_t indices[MAX_CCS_CONNECTION]; + valid_index = true; + bool atleastOneHeldCall = false; + for (int i=0; i::iterator j = CallStatelist.find(indices[i]); + if (j == CallStatelist.end()) { + valid_index = false; + break; + } else if (j->second.state == CCS_STATE_LOCAL_HELD) { + atleastOneHeldCall = true; + } + } + if (CallStatelist.size() < 2 || atleastOneHeldCall == false) { + LOG(INFO) << __func__ << " Join is not valid: "; + valid_index = false; + } + if (count > 1 && valid_index == true) { + cc_instance->CallControlPointChange(CallControllerOps.operation, + indices, count, + NULL, device->peer_bda); + } else { + LOG(INFO) << __func__ << " no valid indices found for JOIN operation "; + cc_instance->CallControlResponse(CallControllerOps.operation, + CCS_DEFAULT_INDEX_VAL, + CCS_INVALID_INDEX, peer_bda); + } + break; + } + default: + LOG(ERROR) << __func__ << " Unhandled Event: " << CallControllerOps.operation; + cc_instance->CallControlResponse(CallControllerOps.operation, + CCS_DEFAULT_INDEX_VAL, + CCS_OPCODE_NOT_SUPPORTED, peer_bda); + } + } else if (handle == ccsControlServiceInfo.bearer_signal_strength_report_interval_handle) { + if (len != 1) { + LOG(ERROR) << " Invalid input for SSR interval"; + return BT_STATUS_SUCCESS; + } + LOG(INFO) << __func__ << " signal strength repo Interval: " << data[0]; + BearerProviderInfo.signal_report_interval = data[0]; + cc_instance->remoteDevices.UpdateSSReportingInterval(device->peer_bda, BearerProviderInfo.signal_report_interval); + } + break; + } + + case CCS_CONGESTION_UPDATE: { + LOG(INFO) << __func__ << ": device CCS_CONGESTION_UPDATE update: " << event; + CcpCongestionUpdate(p_data); + break; + } + + case CCS_READ_RSP: + case CCS_DESCRIPTOR_READ_RSP: + case CCS_DESCRIPTOR_WRITE_RSP: + case CCS_CONNECTION_UPDATE: + case CCS_PHY_UPDATE: + case CCS_CONNECTION: + LOG(INFO) << __func__ << " calling device state " << device->state; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data); + break; + + default: + LOG(INFO) << __func__ << " event is not in list"; + break; + } + + return BT_STATUS_SUCCESS; + +} + +void CcpCongestionUpdate(tcc_resp_t* p_data) { + p_data->remoteDevice->congested = p_data->oper.CongestionOp.congested; + LOG(INFO) << __func__ << ": conn_id: " << p_data->remoteDevice->conn_id + << ", congested: " << p_data->remoteDevice->congested; + + GattsOpsQueue::CongestionCallback(p_data->remoteDevice->conn_id, + p_data->remoteDevice->congested); +} diff --git a/le_audio/system/bt/bta/csip/bta_csip_act.cc b/le_audio/system/bt/bta/csip/bta_csip_act.cc new file mode 100644 index 00000000000..ff83083b788 --- /dev/null +++ b/le_audio/system/bt/bta/csip/bta_csip_act.cc @@ -0,0 +1,1686 @@ +/****************************************************************************** + +Copyright (c) 2020, The Linux Foundation. All rights reserved. +* +*****************************************************************************/ + +/****************************************************************************** +* + +* Copyright 2009-2013 Broadcom Corporation +* +* 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. +* +******************************************************************************/ + +/****************************************************************************** + * + * This file contains the CSIP Client action functions. + * + ******************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include + +#include "bta_csip_int.h" +#include "bta_csip_api.h" +#include "bta_gatt_api.h" +#include "bta_gatt_queue.h" +#include "btm_api.h" +#include "btm_ble_api.h" +#include "btm_int.h" +#include "osi/include/osi.h" +#include "osi/include/properties.h" +#include "bta_dm_api.h" +#include "bta_dm_adv_audio.h" + +/* CSIS Service UUID */ +Uuid CSIS_SERVICE_UUID = Uuid::FromString("1846"); +/* CSIS Characteristic UUID's */ +Uuid CSIS_SERVICE_SIRK_UUID = Uuid::FromString("2B84"); +Uuid CSIS_SERVICE_SIZE_UUID = Uuid::FromString("2B85"); +Uuid CSIS_SERVICE_LOCK_UUID = Uuid::FromString("2B86"); +Uuid CSIS_SERVICE_RANK_UUID = Uuid::FromString("2B87"); + +/******************************************************************************* + * + * Function bta_csip_api_enable + * + * Description This function completes tasks to be done on BT ON + * + * Parameters: p_cback - callbacks from btif layer + * + ******************************************************************************/ +void bta_csip_api_enable(tBTA_CSIP_CBACK *p_cback) { + APPL_TRACE_DEBUG("%s", __func__); + + bta_csip_cb = tBTA_CSIP_CB(); + bta_csip_cb.p_cback = p_cback; + + // register with GATT CLient interface + bta_csip_gattc_register(); + + bta_csip_load_coordinated_sets_from_storage(); +} + +/******************************************************************************* + * + * Function bta_csip_api_disable + * + * Description This function completes tasks to be done on BT OFF + * + * Parameters: None + * + ******************************************************************************/ +void bta_csip_api_disable() { + std::vector &dev_cb = bta_csip_cb.dev_cb; + + /* close all active GATT Connections */ + for (tBTA_CSIP_DEV_CB& p_cb: dev_cb) { + if (p_cb.state == BTA_CSIP_CONN_ST) { + BTA_GATTC_Close(p_cb.conn_id); + } + } + + /* Deregister GATT Interface */ + BTA_GATTC_AppDeregister(bta_csip_cb.gatt_if); +} + +/******************************************************************************* + * + * Function bta_csip_app_register + * + * Description API used to register App/Module for CSIP callbacks. + * operation + * + * Parameters: app_uuid - Application UUID. + * p_cback - Application callbacks. + * cb - callback after registration. + ******************************************************************************/ +void bta_csip_app_register (const Uuid& app_uuid, tBTA_CSIP_CBACK* p_cback, + BtaCsipAppRegisteredCb cb) { + uint8_t i; + tBTA_CSIP_STATUS status = BTA_CSIP_FAILURE; + + for (i = 0; i < BTA_CSIP_MAX_SUPPORTED_APPS; i++) { + if (!bta_csip_cb.app_rcb[i].in_use) { + bta_csip_cb.app_rcb[i].in_use = true; + bta_csip_cb.app_rcb[i].app_id = i; + bta_csip_cb.app_rcb[i].p_cback = p_cback; + status = BTA_CSIP_SUCCESS; + break; + } + } + + if (status == BTA_CSIP_SUCCESS) { + LOG(INFO) << "CSIP App Registered Succesfully. App ID: " << +i; + } else { + LOG(ERROR) << "CSIP App Registration failed. App Limit reached"; + } + + // Give callback to registering App/Module + if (!cb.is_null()) cb.Run(status, i); +} + +/******************************************************************************* + * + * Function bta_csip_app_unregister + * + * Description API used to unregister App/Module for CSIP callbacks. + * + * Parameters: app_id: ID of the application to be unregistered. + * + ******************************************************************************/ +void bta_csip_app_unregister(uint8_t app_id) { + if (app_id >= BTA_CSIP_MAX_SUPPORTED_APPS) { + LOG(ERROR) << __func__ << " Invalid App ID: " << +app_id; + return; + } + + bta_csip_cb.app_rcb[app_id].in_use = false; + bta_csip_cb.app_rcb[app_id].p_cback = NULL; +} + +/******************************************************************************* + * + * Function bta_csip_gattc_callback + * + * Description This is GATT client callback function used in BTA CSIP. + * + * Parameters: event - received from GATT + * p_data - data associated with the event + * + ******************************************************************************/ +static void bta_csip_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { + tBTA_CSIP_DEV_CB* p_dev_cb; + + APPL_TRACE_DEBUG("bta_csip_gattc_callback event = %d", event); + + if (p_data == NULL) return; + + switch (event) { + case BTA_GATTC_OPEN_EVT: + p_dev_cb = bta_csip_find_dev_cb_by_bda(p_data->open.remote_bda); + if (p_dev_cb) { + bta_csip_sm_execute(p_dev_cb, BTA_CSIP_GATT_OPEN_EVT, + (tBTA_CSIP_REQ_DATA*)&p_data->open); + } + break; + + case BTA_GATTC_CLOSE_EVT: + p_dev_cb = bta_csip_find_dev_cb_by_bda(p_data->close.remote_bda); + if (p_dev_cb) { + APPL_TRACE_DEBUG("BTA_GATTC_CLOSE_EVT state = %d", p_dev_cb->state); + bta_csip_sm_execute(p_dev_cb, BTA_CSIP_GATT_CLOSE_EVT, + (tBTA_CSIP_REQ_DATA*)&p_data->close); + } + break; + + case BTA_GATTC_SEARCH_CMPL_EVT: { + tBTA_GATTC_SEARCH_CMPL* p_srch_data = &p_data->search_cmpl; + tBTA_CSIP_DISC_SET disc_params = {.conn_id = p_srch_data->conn_id, + .status = p_srch_data->status + }; + p_dev_cb = bta_csip_get_dev_cb_by_cid(p_srch_data->conn_id); + if (p_dev_cb) { + disc_params.addr = p_dev_cb->addr; + p_dev_cb->is_disc_external = true; + } + bta_csip_gatt_disc_cmpl_act(&disc_params); + bta_csip_sm_execute(p_dev_cb, BTA_CSIP_OPEN_CMPL_EVT, NULL); + } + break; + + case BTA_GATTC_NOTIF_EVT: { + bta_csip_handle_notification(&p_data->notify); + } + break; + + default: + break; + } +} + +/******************************************************************************* + * + * Function bta_csip_gattc_register + * + * Description API used to register GATT interface for CSIP operations. + * + * Parameters: None + * + ******************************************************************************/ +void bta_csip_gattc_register() { + APPL_TRACE_DEBUG("%s", __func__); + + BTA_GATTC_AppRegister(bta_csip_gattc_callback, + base::Bind([](uint8_t client_id, uint8_t status) { + tBTA_CSIP_STATUS csip_status = BTA_CSIP_FAILURE; + if (status == GATT_SUCCESS) { + bta_csip_cb.gatt_if = client_id; + csip_status = BTA_CSIP_SUCCESS; + } else { + bta_csip_cb.gatt_if = BTA_GATTS_INVALID_IF; + } + + /* BTA_GATTC_AppRegister is done */ + if (bta_csip_cb.p_cback) { + LOG(INFO) << "CSIP GATT IF : " + << +bta_csip_cb.gatt_if; + } + }), true); +} + +/******************************************************************************* + * + * Function bta_csip_process_set_lock_act + * + * Description This function processes lock/unlock request. + * + * Parameters: lock_param: params used in LOCK/UNLOCK request. + * + ******************************************************************************/ +void bta_csip_process_set_lock_act(tBTA_SET_LOCK_PARAMS lock_param) { + LOG(INFO) << __func__ << ": App ID = " << +lock_param.app_id + << ", Set ID = " << +lock_param.set_id + << ", Value = " << +lock_param.lock_value; + + tBTA_CSET_CB* cset_cb = bta_csip_get_cset_cb_by_id (lock_param.set_id); + if (!cset_cb || !bta_csip_is_valid_lock_request(&lock_param)) { + tBTA_LOCK_STATUS_CHANGED res = {.app_id = lock_param.app_id, + .set_id = lock_param.set_id, + .status = INVALID_REQUEST_PARAMS}; + bta_csip_send_lock_req_cmpl_cb(res); + return; + } + + // Add request in the queue if one is already in progress for this set + if (cset_cb->request_in_progress) { + cset_cb->lock_req_queue.push(lock_param); + LOG(INFO) << __func__ << " pending lock requests in queue for Set:" + << +lock_param.set_id + << " Pending Requests = " << +(int)cset_cb->lock_req_queue.size(); + return; + } + + bta_csip_form_lock_request(lock_param, cset_cb); +} + +/******************************************************************************* + * + * Function bta_csip_process_set_lock_act + * + * Description This function forms request (LOCK/UNLOCK and order of the + * set members). + * + * Parameters: lock_param: params used in LOCK/UNLOCK request. + * cset_cb: current set control block. + * + ******************************************************************************/ +void bta_csip_form_lock_request(tBTA_SET_LOCK_PARAMS lock_param, + tBTA_CSET_CB* cset_cb) { + cset_cb->request_in_progress = true; + + std::vector ordered_members; + + if (lock_param.lock_value == LOCK_VALUE) { + ordered_members = bta_csip_arrange_set_members_by_order(cset_cb->set_id, + lock_param.members_addr, true); + } else { + ordered_members = bta_csip_arrange_set_members_by_order(cset_cb->set_id, + lock_param.members_addr, false); + } + + //debug log + for (int i = 0; i < (int)ordered_members.size(); i++) { + APPL_TRACE_DEBUG("%s: Member %d = %s", __func__, (i+1), + ordered_members[i].ToString().c_str()); + } + + // update current request in CB + cset_cb->cur_lock_req = {lock_param.app_id, lock_param.set_id, lock_param.lock_value, + 0, ordered_members}; + + // update current response in CB + cset_cb->cur_lock_res = {}; + cset_cb->cur_lock_res.app_id = lock_param.app_id; + cset_cb->cur_lock_res.set_id = lock_param.set_id; + cset_cb->cur_lock_res.value = UNLOCK_VALUE; + + /* LOCK Request */ + if (cset_cb->cur_lock_req.value == LOCK_VALUE) { + cset_cb->cur_lock_res.status = ALL_LOCKS_ACQUIRED; + /* check if lock request for this set was denied earlier */ + if (bta_csip_validate_req_for_denied_sm(cset_cb)) { + bta_csip_get_next_lock_request(cset_cb); + + /* proceed with request otherwise*/ + } else { + bta_csip_send_lock_req_act(cset_cb); + } + /* UNLOCK Request*/ + } else { + bta_csip_send_unlock_req_act(cset_cb); + } +} + +/******************************************************************************* + * + * Function bta_csip_validate_req_for_denied_sm + * + * Description This function validates request if received for denied set + * member + * + * Parameters: cset_cb: current set control block. + * + ******************************************************************************/ +bool bta_csip_validate_req_for_denied_sm (tBTA_CSET_CB* cset_cb) { + bool is_denied = false; + tBTA_LOCK_REQUEST& lock_req = cset_cb->cur_lock_req; + + for (RawAddress& addr: lock_req.members_addr) { + tBTA_CSIP_DEV_CB* p_cb = bta_csip_find_dev_cb_by_bda(addr); + if (!p_cb) { + APPL_TRACE_ERROR("%s: Device CB not found for %s", __func__, + addr.ToString().c_str()); + continue; + } + + tBTA_CSIS_SRVC_INFO* srvc = bta_csip_get_csis_instance(p_cb, lock_req.set_id); + if (!srvc) { + APPL_TRACE_ERROR("%s: CSIS instance not found for %s", __func__, + addr.ToString().c_str()); + continue; + } + + if (!srvc->denied_applist.empty()) { + is_denied = true; + // add this app_id in the denied_app_list + srvc->denied_applist.push_back(lock_req.app_id); + cset_cb->cur_lock_res.status = LOCK_DENIED; + cset_cb->cur_lock_res.addr.push_back(addr); + } + } + + if (is_denied) { + bta_csip_send_lock_req_cmpl_cb(cset_cb->cur_lock_res); + } + + return is_denied; +} + +/******************************************************************************* + * + * Function bta_csip_lock_release_by_denial_cb + * + * Description This callback function is called when set member is unlocked + * after unlock request is sent post lock denial. + * + * Parameters: GATT operation callback params. + * + ******************************************************************************/ +void bta_csip_lock_release_by_denial_cb(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data) { + tBTA_CSET_CB* cset_cb = (tBTA_CSET_CB *)data; + tBTA_CSIP_DEV_CB* dev_cb = cset_cb->cur_dev_cb; + tBTA_CSIS_SRVC_INFO* srvc = + bta_csip_get_csis_instance(cset_cb->cur_dev_cb, cset_cb->cur_lock_req.set_id); + + LOG(INFO) << __func__ << " Released lock for device: " << dev_cb->addr + << ", Set ID: " << +cset_cb->set_id; + + if (srvc && status == GATT_SUCCESS) { + srvc->lock = UNLOCK_VALUE; + // remove app id from applist + srvc->lock_applist.erase(std::remove(srvc->lock_applist.begin(), srvc->lock_applist.end(), + cset_cb->cur_lock_req.app_id), srvc->lock_applist.end()); + } + + // release next set member with lower rank + bta_csip_handle_lock_denial(cset_cb); +} + +/******************************************************************************* + * + * Function bta_csip_handle_lock_denial + * + * Description This function is called when lock has been denied by one of + * the set members. + * + * Parameters: cset_cb: current set control block. + * + ******************************************************************************/ +void bta_csip_handle_lock_denial(tBTA_CSET_CB* cset_cb) { + // start lock release procedure for acquired locks + int8_t cur_idx = cset_cb->cur_lock_req.cur_idx - 1; + cset_cb->cur_lock_req.cur_idx--; + + if (cur_idx >= 0) { + RawAddress bd_addr = cset_cb->cur_lock_req.members_addr[cur_idx]; + cset_cb->cur_dev_cb = bta_csip_find_dev_cb_by_bda(bd_addr); + tBTA_CSIS_SRVC_INFO* srvc = + bta_csip_get_csis_instance(cset_cb->cur_dev_cb, cset_cb->cur_lock_req.set_id); + + // check if locked by other app + if (!cset_cb->cur_dev_cb || !srvc || + bta_csip_is_locked_by_other_apps(srvc, cset_cb->cur_lock_req.app_id)) { + LOG(INFO) << "Invalid device or service CB or" + << " other apps have locked this set member(" << bd_addr << "). Skip."; + bta_csip_handle_lock_denial(cset_cb); + return; + } + + // Lock value in vector format (one uint8_t size element with ) + std::vector unlock_value(1, UNLOCK_VALUE); + BtaGattQueue::WriteCharacteristic(cset_cb->cur_dev_cb->conn_id, + srvc->lock_handle, unlock_value, GATT_WRITE, bta_csip_lock_release_by_denial_cb, cset_cb); + + } else { + bta_csip_get_next_lock_request(cset_cb); + } +} + +/******************************************************************************* + * + * Function bta_csip_lock_req_cb + * + * Description This callback function is called when set member is locked + * by remote device after LOCK Request + * + * Parameters: GATT operation callback params. + * + ******************************************************************************/ +void bta_csip_lock_req_cb(uint16_t conn_id, tGATT_STATUS status, uint16_t handle, + void* data) { + LOG(INFO) << __func__ << " status = " << +status; + tBTA_CSET_CB* cset_cb = (tBTA_CSET_CB *)data; + tBTA_CSIP_DEV_CB* dev_cb = bta_csip_get_dev_cb_by_cid(conn_id); + tBTA_CSIS_SRVC_INFO* srvc = + bta_csip_get_csis_instance(dev_cb, cset_cb->set_id); + + /* Device control block or corresponding CSIS service instance not found */ + if (!dev_cb || !srvc) { + APPL_TRACE_ERROR("%s: Device CB not found for conn_id = %d", __func__, conn_id); + cset_cb->cur_lock_req.cur_idx++; + alarm_cancel(cset_cb->unresp_timer); + bta_csip_send_lock_req_act(cset_cb); + return; + } + + /*check if this response is received from unresponsive set member */ + if (dev_cb->unresponsive) { + LOG(INFO) << __func__ << " unresponsive remote: " << dev_cb->addr; + bta_csip_handle_unresponsive_sm_res(srvc, status); + dev_cb->unresponsive = false; + return; + } + // cancel alarm (used for unresponsive set member) + alarm_cancel(cset_cb->unresp_timer); + + if (status == CSIP_LOCK_DENIED) { + LOG(INFO) << __func__ << " Locked Denied by " << dev_cb->addr; + srvc->lock = UNLOCK_VALUE; + cset_cb->cur_lock_res.value = UNLOCK_VALUE; + cset_cb->cur_lock_res.status = LOCK_DENIED; + + // add member to the response list for which lock is denied and clear others + cset_cb->cur_lock_res.addr.clear(); + cset_cb->cur_lock_res.addr.push_back(dev_cb->addr); + + // add app_id in the denied applist + srvc->denied_applist.push_back(cset_cb->cur_lock_req.app_id); + // Give callback to upper layer that lock is denied + bta_csip_send_lock_req_cmpl_cb(cset_cb->cur_lock_res); + // release all the acquired locks till now + bta_csip_handle_lock_denial(cset_cb); + + /* PTS: Remote responding with invalid value */ + } else if (status == CSIP_INVALID_LOCK_VALUE) { + LOG(ERROR) << __func__ << " remote " << dev_cb->addr + << " responded with INVALID Value"; + /* for PTS to ensure set coordinator is working fine */ + BtaGattQueue::ReadCharacteristic(cset_cb->cur_dev_cb->conn_id, + srvc->lock_handle, NULL, NULL); + + /* Stop locking remaining set members and inform requesting app */ + bta_csip_send_lock_req_cmpl_cb(cset_cb->cur_lock_res); + /* Process next lock request from pending queue */ + bta_csip_get_next_lock_request(cset_cb); + } else { + if (status == GATT_SUCCESS || status == CSIP_LOCK_ALREADY_GRANTED) { + LOG(INFO) << __func__ << " successfully locked " << dev_cb->addr; + cset_cb->cur_lock_res.addr.push_back(dev_cb->addr); + cset_cb->cur_lock_res.value = LOCK_VALUE; + srvc->lock = LOCK_VALUE; + // add app_id against this device entry + srvc->lock_applist.push_back(cset_cb->cur_lock_req.app_id); + } + + //proceed with next set member + cset_cb->cur_lock_req.cur_idx++; + bta_csip_send_lock_req_act(cset_cb); + } +} + +/******************************************************************************* + * + * Function bta_csip_send_lock_req_act + * + * Description This function is is used to send LOCK request to set member. + * It validates if it is required to send lock request based on + * connection state and current lock value. + * + * Parameters: cset_cb: current set control block. + * + ******************************************************************************/ +void bta_csip_send_lock_req_act(tBTA_CSET_CB* cset_cb) { + RawAddress bd_addr; + uint8_t cur_index = cset_cb->cur_lock_req.cur_idx; + + if (cur_index == (uint8_t)cset_cb->cur_lock_req.members_addr.size()) { + LOG(INFO) << __func__ << " lock operation completed for all set members"; + bta_csip_send_lock_req_cmpl_cb(cset_cb->cur_lock_res); + bta_csip_get_next_lock_request(cset_cb); + return; + } + + bd_addr = cset_cb->cur_lock_req.members_addr[cur_index]; + + // get device control block and corresponding csis service details + cset_cb->cur_dev_cb = bta_csip_find_dev_cb_by_bda(bd_addr); + tBTA_CSIS_SRVC_INFO* srvc = + bta_csip_get_csis_instance(cset_cb->cur_dev_cb, cset_cb->cur_lock_req.set_id); + + // Skip device if it is not in connected state + if (!cset_cb->cur_dev_cb || !srvc || + cset_cb->cur_dev_cb->state != BTA_CSIP_CONN_ST) { + LOG(INFO) << __func__ << ": Set Member (" << bd_addr.ToString() + << ") is not connected. Skip this Set member"; + + cset_cb->cur_lock_req.cur_idx++; + cset_cb->cur_lock_res.status = SOME_LOCKS_ACQUIRED_REASON_DISC; + bta_csip_send_lock_req_act(cset_cb); + + // check if already locked (skip sending write request) + } else if (srvc->lock == LOCK_VALUE) { + LOG(INFO) << __func__ << ": Set Member (" << cset_cb->cur_dev_cb->addr + << ") is already locked. Skip this Set member"; + cset_cb->cur_lock_res.value = LOCK_VALUE; + // add element in the list + cset_cb->cur_lock_res.addr.push_back(bd_addr); + // add appid in the list if locked by different app + if (!bta_csip_is_member_locked_by_app(cset_cb->cur_lock_req.app_id, srvc)) { + srvc->lock_applist.push_back(cset_cb->cur_lock_req.app_id); + } + cset_cb->cur_lock_req.cur_idx++; + // process next set member + bta_csip_send_lock_req_act(cset_cb); + + // send the lock request + } else { + // Lock value in vector format (one uint8_t size element with ) + LOG(INFO) << __func__ << " Sending Lock Request to "<< cset_cb->cur_dev_cb->addr + << " Conn Id: " << +cset_cb->cur_dev_cb->conn_id; + std::vector lock_value = {2}; + BtaGattQueue::WriteCharacteristic(cset_cb->cur_dev_cb->conn_id, + srvc->lock_handle, lock_value, GATT_WRITE, bta_csip_lock_req_cb, cset_cb); + + // Start set member request timeout alarm + cset_cb->unresp_timer = alarm_new("csip_unresp_sm_timer"); + alarm_set_on_mloop(cset_cb->unresp_timer, cset_cb->set_member_tout, + bta_csip_set_member_lock_timeout, cset_cb); + } +} + +/******************************************************************************* + * + * Function bta_csip_unlock_req_cb + * + * Description This callback function is called when set member is unlocked + * by remote device after UNLOCK Request + * + * Parameters: GATT operation callback params. + * + ******************************************************************************/ +void bta_csip_unlock_req_cb(uint16_t conn_id, tGATT_STATUS status, uint16_t handle, + void* data) { + LOG(INFO) << __func__ << " status = " << +status; + tBTA_CSET_CB* cset_cb = (tBTA_CSET_CB *)data; + tBTA_CSIP_DEV_CB* dev_cb = bta_csip_get_dev_cb_by_cid(conn_id); + tBTA_CSIS_SRVC_INFO* srvc = + bta_csip_get_csis_instance(dev_cb, cset_cb->cur_lock_req.set_id); + + /* Device control block or corresponding CSIS service instance not found */ + if (!dev_cb || !srvc) { + APPL_TRACE_ERROR("%s: Device CB not found for conn_id = %d", __func__, conn_id); + cset_cb->cur_lock_req.cur_idx++; + alarm_cancel(cset_cb->unresp_timer); + bta_csip_send_unlock_req_act(cset_cb); + return; + } + + /*check if this response is received from unresponsive set member */ + if (dev_cb->unresponsive) { + LOG(INFO) << __func__ << " unresponsive remote: " << dev_cb->addr; + srvc->lock = UNLOCK_VALUE; + srvc->unrsp_applist.clear(); + dev_cb->unresponsive = false; + return; + } + + // cancel alarm (used for unresponsive set member) + alarm_cancel(cset_cb->unresp_timer); + + /* PTS Test Case: read any characteristic */ + if (status == CSIP_LOCK_RELEASE_NOT_ALLOWED || + status == CSIP_INVALID_LOCK_VALUE) { + /* for PTS to ensure set coordinator is working fine */ + BtaGattQueue::ReadCharacteristic(cset_cb->cur_dev_cb->conn_id, + srvc->lock_handle, NULL, NULL); + + /* Stop unlocking remaining set members and inform requesting app */ + cset_cb->cur_lock_res.status = status; + bta_csip_send_lock_req_cmpl_cb(cset_cb->cur_lock_res); + /* Process next lock request from pending queue */ + bta_csip_get_next_lock_request(cset_cb); + } else { + if (status == GATT_SUCCESS) { + srvc->lock = UNLOCK_VALUE; + // remove app id from applist + srvc->lock_applist.erase(std::remove(srvc->lock_applist.begin(), + srvc->lock_applist.end(), cset_cb->cur_lock_req.app_id), + srvc->lock_applist.end()); + cset_cb->cur_lock_res.addr.push_back(dev_cb->addr); + } + //proceed with next set member + cset_cb->cur_lock_req.cur_idx++; + bta_csip_send_unlock_req_act(cset_cb); + } +} + +/******************************************************************************* + * + * Function bta_csip_send_unlock_req_act + * + * Description This function is is used to send UNLOCK request to set member. + * It validates if it is required to send unlock request based on + * connection state and current lock value. + * + * Parameters: cset_cb: current set control block. + * + ******************************************************************************/ +void bta_csip_send_unlock_req_act(tBTA_CSET_CB* cset_cb) { + RawAddress bd_addr; + uint8_t cur_index = cset_cb->cur_lock_req.cur_idx; + + if (cur_index == (uint8_t)cset_cb->cur_lock_req.members_addr.size()) { + cset_cb->request_in_progress = false; + bta_csip_send_lock_req_cmpl_cb(cset_cb->cur_lock_res); + bta_csip_get_next_lock_request(cset_cb); + LOG(INFO) << __func__ << " Request completed for all set members"; + return; + } + + bd_addr = cset_cb->cur_lock_req.members_addr[cur_index]; + + LOG(INFO) << __func__ << ": Set Member address: " << bd_addr.ToString(); + + // get device control block and corresponding csis service details + cset_cb->cur_dev_cb = bta_csip_find_dev_cb_by_bda(bd_addr); + tBTA_CSIS_SRVC_INFO* srvc = + bta_csip_get_csis_instance(cset_cb->cur_dev_cb, cset_cb->cur_lock_req.set_id); + + /* Device control block or corresponding CSIS service instance not found */ + if (!cset_cb->cur_dev_cb || !srvc) { + APPL_TRACE_ERROR("%s: Device CB not found for %s", __func__, + bd_addr.ToString().c_str()); + cset_cb->cur_lock_req.cur_idx++; + bta_csip_send_unlock_req_act(cset_cb); + return; + } + + /* Set member is not locked by requesting app */ + if (!bta_csip_is_member_locked_by_app(cset_cb->cur_lock_req.app_id, srvc)) { + LOG(INFO) << __func__ << " App "<< +cset_cb->cur_lock_req.app_id + << "has not locked this set member (" << srvc->bd_addr + << "). Skip this set member"; + cset_cb->cur_lock_req.cur_idx++; + bta_csip_send_unlock_req_act(cset_cb); + return; + } + + // Skip device if it is not in connected state + if (cset_cb->cur_dev_cb->state != BTA_CSIP_CONN_ST) { + LOG(INFO) << __func__ << ": Set Member (" << bd_addr.ToString() + << ") is not connected. Skip this Set member"; + + cset_cb->cur_lock_req.cur_idx++; + // remove app id from applist + srvc->lock_applist.erase(std::remove(srvc->lock_applist.begin(), srvc->lock_applist.end(), + cset_cb->cur_lock_req.app_id), srvc->lock_applist.end()); + bta_csip_send_unlock_req_act(cset_cb); + + // check if already unlocked or locked by multiple apps (skip sending write request) + } else if (srvc->lock == UNLOCK_VALUE || + bta_csip_is_locked_by_other_apps(srvc, cset_cb->cur_lock_req.app_id)) { + LOG(INFO) << __func__ << ": Set Member (" << bd_addr.ToString() + << ") is already unlocked or locked by other app. Skip this Set member"; + // remove app id from applist + srvc->lock_applist.erase(std::remove(srvc->lock_applist.begin(), srvc->lock_applist.end(), + cset_cb->cur_lock_req.app_id), srvc->lock_applist.end()); + cset_cb->cur_lock_req.cur_idx++; + cset_cb->cur_lock_res.addr.push_back(cset_cb->cur_dev_cb->addr); + // process next set member + bta_csip_send_unlock_req_act(cset_cb); + + // send the unlock request + } else { + // Unlock value in vector format (one uint8_t size element with unlock value) + std::vector lock_value(1, UNLOCK_VALUE); + BtaGattQueue::WriteCharacteristic(cset_cb->cur_dev_cb->conn_id, + srvc->lock_handle, lock_value, GATT_WRITE, bta_csip_unlock_req_cb, cset_cb); + + // Start set member request timeout alarm + cset_cb->unresp_timer = alarm_new("csip_unresp_sm_timer"); + alarm_set_on_mloop(cset_cb->unresp_timer, cset_cb->set_member_tout, + bta_csip_set_member_lock_timeout, cset_cb); + } +} + +/******************************************************************************* + * + * Function bta_csip_set_member_lock_timeout + * + * Description This API is called when Set Member has not responded within + * required set member lock timeout. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_set_member_lock_timeout(void* p_data) { + tBTA_CSET_CB* cset_cb = (tBTA_CSET_CB *)p_data; + tBTA_CSIP_DEV_CB* dev_cb = cset_cb->cur_dev_cb; + + APPL_TRACE_DEBUG("%s", __func__); + // Device not found or disconnected + if (!dev_cb || dev_cb->state != BTA_CSIP_CONN_ST) { + LOG(ERROR) << __func__ << " device disconnected."; + cset_cb->cur_lock_res.status = SOME_LOCKS_ACQUIRED_REASON_DISC; + cset_cb->cur_lock_req.cur_idx++; + bta_csip_send_lock_req_act(cset_cb); + return; + } + + tBTA_CSIS_SRVC_INFO* srvc = + bta_csip_get_csis_instance(cset_cb->cur_dev_cb, cset_cb->cur_lock_req.set_id); + + if (!srvc) { + APPL_TRACE_ERROR("%s: CSIS instance not found.", __func__); + return; + } + + dev_cb->unresponsive = true; + // add app_id in unresponsive set members app list + srvc->unrsp_applist.push_back(cset_cb->cur_lock_res.app_id); + + if (cset_cb->cur_lock_req.value == LOCK_VALUE) { + cset_cb->cur_lock_res.status = SOME_LOCKS_ACQUIRED_REASON_TIMEOUT; + APPL_TRACE_DEBUG("%s: Process next device in the lock request", __func__); + cset_cb->cur_lock_req.cur_idx++; + bta_csip_send_lock_req_act(cset_cb); + } else if (cset_cb->cur_lock_req.value == UNLOCK_VALUE) { + cset_cb->cur_lock_res.addr.push_back( + cset_cb->cur_lock_req.members_addr[cset_cb->cur_lock_req.cur_idx]); + cset_cb->cur_lock_req.cur_idx++; + bta_csip_send_unlock_req_act(cset_cb); + } +} + +/******************************************************************************* + * + * Function bta_csip_le_encrypt_cback + * + * Description link encryption complete callback. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_le_encrypt_cback(const RawAddress* bd_addr, + UNUSED_ATTR tGATT_TRANSPORT transport, + UNUSED_ATTR void* p_ref_data, tBTM_STATUS result) { + APPL_TRACE_ERROR("%s: status = %d", __func__, result); + tBTA_CSIP_DEV_CB* p_cb = bta_csip_find_dev_cb_by_bda(*bd_addr); + + if (!p_cb) { + APPL_TRACE_ERROR("unexpected encryption callback, ignore"); + return; + } + + /* If encryption fails, disconnect the connection */ + if (result != BTM_SUCCESS) { + bta_csip_close_csip_conn(p_cb); + return; + } + + if (p_cb->state == BTA_CSIP_W4_SEC) { + bta_csip_sm_execute(p_cb, BTA_CSIP_ENC_CMPL_EVT, NULL); + } +} + +/******************************************************************************* + * + * Function bta_csip_open_act + * + * Description API Call to open CSIP Gatt Connection + * + * Returns None + * + ******************************************************************************/ +void bta_csip_api_open_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data) { + APPL_TRACE_DEBUG("%s: Open GATT connection for CSIP", __func__); + + tBTA_CSIP_API_CONN* p_conn_req = (tBTA_CSIP_API_CONN *)&p_data->conn_param; + + if (!bta_csip_is_app_reg(p_conn_req->app_id)) { + LOG(ERROR) << __func__ << ": Request from Invalid/Unregistered App: " + << +p_conn_req->app_id; + if (p_cb && (uint8_t)p_cb->conn_applist.size() == 0) { + p_cb->state = BTA_CSIP_IDLE_ST; + } + // No need to send callback to invalid/unregistered app + return; + } + + if (btm_sec_is_a_bonded_dev(p_cb->addr) && !bta_csip_is_csis_supported(p_cb)) { + APPL_TRACE_DEBUG("%s: Remote (%s) doesnt contain any coordinated set", __func__, + p_cb->addr.ToString().c_str()); + bta_csip_send_conn_state_changed_cb(p_cb, p_conn_req->app_id, + BTA_CSIP_DISCONNECTED, BTA_CSIP_COORDINATED_SET_NOT_SUPPORTED); + return; + } + + if (!p_cb) { + LOG(ERROR) << __func__ << ": Insufficient resources. Max" + " supported Set members have reached "; + tBTA_CSIP_DEV_CB invalid_cb = { + .addr = p_data->conn_param.bd_addr + }; + bta_csip_send_conn_state_changed_cb(&invalid_cb, p_conn_req->app_id, + BTA_CSIP_DISCONNECTED, BTA_CSIP_CONN_ESTABLISHMENT_FAILED); + return; + } + + // check if connection state is already connected + if (p_cb->state == BTA_CSIP_CONN_ST) { + if (!bta_csip_is_app_from_applist(p_cb, p_conn_req->app_id)) { + bta_csip_add_app_to_applist(p_cb, p_conn_req->app_id); + } + bta_csip_send_conn_state_changed_cb(p_cb, p_conn_req->app_id, + BTA_CSIP_CONNECTED, BTA_CSIP_CONN_ESTABLISHED); + return; + + // other app has already started connection procedure + } else if (!bta_csip_is_app_from_applist(p_cb, p_conn_req->app_id) + && (uint8_t)p_cb->conn_applist.size() > 0 + && p_cb->state != BTA_CSIP_IDLE_ST) { + LOG(INFO) << __func__ << ": Other app is establishing CSIP Connection." + << " Current connection state = " << +p_cb->state; + bta_csip_add_app_to_applist(p_cb, p_conn_req->app_id); + /* Note: Callback will given to all apps once connection procedure is completed */ + return; + } + + p_cb->addr = p_data->conn_param.bd_addr; + bta_csip_add_app_to_applist(p_cb, p_conn_req->app_id); + + BTA_GATTC_Open(bta_csip_cb.gatt_if, p_cb->addr, true, GATT_TRANSPORT_LE, + false); +} + +/******************************************************************************* + * + * Function bta_csip_api_close_act + * + * Description API Call to close CSIP Gatt Connection + * + * Returns None + * + ******************************************************************************/ +void bta_csip_api_close_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data) { + if (!p_cb) { + LOG(ERROR) << __func__ << " Already Closed"; + return; + } + + tBTA_CSIP_API_CONN* p_req = (tBTA_CSIP_API_CONN *)&p_data->conn_param; + + LOG(INFO) << __func__ << " Disconnect Request from App: " << +p_req->app_id; + + if (!bta_csip_is_app_reg(p_req->app_id)) { + LOG(ERROR) << __func__ << ": Request from Invalid/Unregistered App: " + << +p_req->app_id; + // No need to send callback to invalid/unregistered app + return; + } else if (!bta_csip_is_app_from_applist(p_cb, p_req->app_id)) { + LOG(ERROR) << __func__ << " App (ID:"<< +p_req->app_id <<") has not connected"; + bta_csip_send_conn_state_changed_cb(p_cb, p_req->app_id, + BTA_CSIP_DISCONNECTED, BTA_CSIP_DISCONNECT_WITHOUT_CONNECT); + return; + } + + // Check if its last disconnecting app + if ((uint8_t)p_cb->conn_applist.size() > 1) { + bta_csip_remove_app_from_conn_list(p_cb, p_req->app_id); + bta_csip_send_conn_state_changed_cb(p_cb, p_req->app_id, + BTA_CSIP_DISCONNECTED, BTA_CSIP_APP_DISCONNECTED); + return; + } + + bta_csip_close_csip_conn(p_cb); +} + +/******************************************************************************* + * + * Function bta_csip_gatt_open_act + * + * Description Callback function when GATT Connection is created. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_gatt_open_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data) { + tBTA_GATTC_OPEN* open_param = &p_data->gatt_open_param; + LOG(INFO) << __func__ << " Remote = " << open_param->remote_bda + << " Status = " << open_param->status + << " conn_id = " << open_param->conn_id; + + if (open_param->status == GATT_SUCCESS) { + p_cb->in_use = true; + p_cb->conn_id = open_param->conn_id; + BtaGattQueue::Clean(p_cb->conn_id); + bta_csip_sm_execute(p_cb, BTA_CSIP_START_ENC_EVT, NULL); + } else { + /* open failure */ + bta_csip_sm_execute(p_cb, BTA_CSIP_OPEN_FAIL_EVT, p_data); + } +} + +/******************************************************************************* + * + * Function bta_csip_gatt_close_act + * + * Description Callback function when GATT Connection is closed. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_gatt_close_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data) { + tBTA_GATTC_OPEN* open_param = &p_data->gatt_open_param; + + // Give callback to all apps from connection applist + bta_csip_send_conn_state_changed_cb(p_cb, BTA_CSIP_DISCONNECTED, open_param->status); + + // Clear applist + p_cb->conn_applist.clear(); + + for (int i = 0; i < MAX_SUPPORTED_SETS_PER_DEVICE; i++) { + tBTA_CSIS_SRVC_INFO* srvc = &p_cb->csis_srvc[i]; + if (srvc->in_use) { + srvc->lock = UNLOCK_VALUE; + } + } + + p_cb->conn_id = 0; + p_cb->in_use = false; +} + +/******************************************************************************* + * + * Function bta_csip_gatt_open_fail_act + * + * Description Callback function when GATT Connection fails to be created. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_gatt_open_fail_act (tBTA_CSIP_DEV_CB* p_cb, + tBTA_CSIP_REQ_DATA* p_data) { + LOG(ERROR) << __func__ << " Failed to open GATT Connection"; + + tBTA_GATTC_OPEN* open_param = &p_data->gatt_open_param; + + // Give callback to all apps from connection applist waiting for connection + bta_csip_send_conn_state_changed_cb(p_cb, BTA_CSIP_DISCONNECTED, open_param->status); + + // Clear applist + p_cb->conn_applist.clear(); + p_cb->in_use = false; +} + +/******************************************************************************* + * + * Function bta_csip_open_cmpl_act + * + * Description Tasks needed to be done when connection is established. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_open_cmpl_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data) { + APPL_TRACE_DEBUG("%s", __func__); + + if (!p_cb) { + LOG(ERROR) << __func__ << " Invalid device contrl block"; + return; + } + + // Give callback to all apps from connection applist waiting for connection + bta_csip_send_conn_state_changed_cb(p_cb, BTA_CSIP_CONNECTED, + BTA_CSIP_CONN_ESTABLISHED); + + /* Register for notification of required CSIS characteristic*/ + int i = 0; + for (i = 0; i < MAX_SUPPORTED_SETS_PER_DEVICE; i++) { + tBTA_CSIS_SRVC_INFO* srvc = &p_cb->csis_srvc[i]; + if (srvc->in_use) { + bta_csip_write_cccd(p_cb, srvc->lock_handle, srvc->lock_ccd_handle); + bta_csip_write_cccd(p_cb, srvc->size_handle, srvc->size_ccd_handle); + bta_csip_write_cccd(p_cb, srvc->sirk_handle, srvc->sirk_ccd_handle); + } + } + +} + +/******************************************************************************* + * + * Function bta_csip_start_sec_act + * + * Description Tasks needed to be done to check or establish CSIP required + * security. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_start_sec_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data) { + APPL_TRACE_DEBUG("%s", __func__); + + uint8_t sec_flag = 0; + + // Get security flags for the device + BTM_GetSecurityFlagsByTransport(p_cb->addr, &sec_flag, BT_TRANSPORT_LE); + + // link is already encrypted, send encryption complete callback to csip + if (sec_flag & BTM_SEC_FLAG_ENCRYPTED) { + LOG(INFO) << __func__ << " Already Encrypted"; + bta_csip_sm_execute(p_cb, BTA_CSIP_ENC_CMPL_EVT, NULL); + } + // device is bonded but link is not encrypted. Start encryption + else if (sec_flag & BTM_SEC_FLAG_LKEY_KNOWN) { + sec_flag = BTM_BLE_SEC_ENCRYPT; + BTM_SetEncryption(p_cb->addr, BTA_TRANSPORT_LE, bta_csip_le_encrypt_cback, + NULL, sec_flag); + } + // unbonded device. Set MITM Encryption + else if (p_cb->sec_mask != BTA_SEC_NONE) { + sec_flag = BTM_BLE_SEC_ENCRYPT_MITM; + BTM_SetEncryption(p_cb->addr, BTA_TRANSPORT_LE, bta_csip_le_encrypt_cback, + NULL, sec_flag); + } + // link is already encrypted + else { + bta_csip_sm_execute(p_cb, BTA_CSIP_ENC_CMPL_EVT, NULL); + } +} + +/******************************************************************************* + * + * Function bta_csip_start_sec_act + * + * Description Tasks needed to be done to check or establish CSIP required + * security. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_sec_cmpl_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data) { + APPL_TRACE_DEBUG("%s p_cb->csis_srvc[0].in_use = %d, p_cb->csis_srvc[0].sirk_handle = %d", + __func__, p_cb->csis_srvc[0].in_use, p_cb->csis_srvc[0].sirk_handle); + + if (!p_cb->csis_srvc[0].in_use || !p_cb->csis_srvc[0].sirk_handle) { + /* Service discovery is triggered from this path when csip connection is opened + * from 3rd party application */ + LOG(INFO) << __func__ << "Service discovery is pending"; + Uuid pri_srvc = Uuid::From16Bit(UUID_SERVCLASS_CSIS); + BTA_GATTC_ServiceSearchRequest(p_cb->conn_id, &pri_srvc); + } else { + LOG(INFO) << __func__ << "Service discovery is already completed"; + bta_csip_sm_execute(p_cb, BTA_CSIP_OPEN_CMPL_EVT, NULL); + } +} + +/******************************************************************************* + * + * Function bta_csip_close_csip_conn + * + * Description API to close CSIP Connection and remove device from background + * list. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_close_csip_conn (tBTA_CSIP_DEV_CB* p_cb) { + LOG(INFO) << __func__; + if (p_cb->conn_id != GATT_INVALID_CONN_ID) { + // clear pending GATT Requests + BtaGattQueue::Clean(p_cb->conn_id); + p_cb->state = BTA_CSIP_DISCONNECTING_ST; + // Send Close to GATT Layer + if (p_cb->state == BTA_CSIP_CONN_ST || p_cb->conn_id) { + BTA_GATTC_Close(p_cb->conn_id); + } else { + BTA_GATTC_CancelOpen(bta_csip_cb.gatt_if, p_cb->addr, true); + tBTA_GATTC_OPEN open = {.status = GATT_SUCCESS}; + bta_csip_gatt_close_act(p_cb,(tBTA_CSIP_REQ_DATA *)&open); + } + } +} + +/******************************************************************************* + * + * Function bta_csip_handle_notification + * + * Description This function is called when notification is received on one + * of the characteristic registered for notification. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_handle_notification(tBTA_GATTC_NOTIFY* ntf) { + if (!ntf->is_notify) return; + LOG(INFO) << __func__<< " Set Member: " << ntf->bda << ", handle: " << ntf->handle; + + tBTA_CSIP_DEV_CB* p_cb = bta_csip_find_dev_cb_by_bda(ntf->bda); + if (!p_cb) { + LOG(ERROR) << __func__ << " No CSIP GATT Connection for this device"; + return; + } + + const gatt::Characteristic* p_char = + BTA_GATTC_GetCharacteristic(p_cb->conn_id, ntf->handle); + if (p_char == NULL) { + APPL_TRACE_ERROR( + "%s: notification received for Unknown Characteristic, conn_id: " + "0x%04x, handle: 0x%04x", + __func__, p_cb->conn_id, ntf->handle); + return; + } + + if (p_char->uuid == CSIS_SERVICE_LOCK_UUID) { + bta_csip_handle_lock_value_notif(p_cb, ntf->handle, ntf->value[0]); + } else if (p_char->uuid == CSIS_SERVICE_SIRK_UUID) { + //bta_csip_handle_sirk_change(); + } else if (p_char->uuid == CSIS_SERVICE_SIZE_UUID) { + //bta_csip_handle_size_change(); + } +} + +/******************************************************************************* + * + * Function bta_csip_handle_lock_value_notif + * + * Description This function is called when notification is received for + * change in lock value on set member. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_handle_lock_value_notif(tBTA_CSIP_DEV_CB* p_cb, + uint16_t handle, uint8_t value) { + tBTA_CSIS_SRVC_INFO* srvc = bta_csip_find_csis_srvc_by_lock_handle(p_cb, handle); + if (!srvc) { + LOG(ERROR) << __func__ << " CSIS Service instance not found for this handle"; + return; + } + + /* LOCK has been released by Set member (by lock timeout) */ + if (value == UNLOCK_VALUE && srvc->lock == LOCK_VALUE) { + srvc->lock = UNLOCK_VALUE; + /* Give lock status changed notification to all apps holding + * lock for this set member */ + LOG(INFO) << __func__ << " Lock released by timeout"; + for (auto i: srvc->lock_applist) { + tBTA_CSIP_RCB* rcb = bta_csip_get_rcb(i); + if (rcb && rcb->p_cback) { + std::vector sm(1, p_cb->addr); + tBTA_LOCK_STATUS_CHANGED p_data = {i, srvc->set_id, value, + LOCK_RELEASED_TIMEOUT, sm}; + (*rcb->p_cback) (BTA_CSIP_LOCK_STATUS_CHANGED_EVT, (tBTA_CSIP_DATA *)&p_data); + } + } + srvc->lock_applist.clear(); + } + /* LOCK held by other set coordinator is released */ + else if (value == UNLOCK_VALUE && srvc->lock == UNLOCK_VALUE) { + // check if lock was denied for any previous request + for (auto i: srvc->denied_applist) { + tBTA_CSIP_RCB* rcb = bta_csip_get_rcb(i); + if (rcb && rcb->p_cback) { + tBTA_LOCK_AVAILABLE p_data = {i, srvc->set_id, p_cb->addr}; + (*rcb->p_cback) (BTA_CSIP_LOCK_AVAILABLE_EVT, (tBTA_CSIP_DATA *)&p_data); + } + } + srvc->denied_applist.clear(); + } + /* Other Set Coordinator acquired the lock */ + else if (value == LOCK_VALUE && srvc->lock == UNLOCK_VALUE) { + // No action is required to be taken + } +} + +/******************************************************************************* + * + * Function bta_csip_csis_disc_complete_ind + * + * Description This function informas CSIS service discovery has been + * completed to DM layer. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_csis_disc_complete_ind (RawAddress& addr) { + tBTA_CSIP_DEV_CB* p_cb = bta_csip_find_dev_cb_by_bda(addr); + if (p_cb) { + p_cb->total_instance_disc++; + LOG(INFO) << __func__ << " discovered = " << +p_cb->total_instance_disc + << " Total = " << +p_cb->csis_instance_count; + if (p_cb->total_instance_disc == p_cb->csis_instance_count + && !p_cb->is_disc_external) { + bta_dm_csis_disc_complete(addr, true); + bta_dm_lea_disc_complete(addr); + } + } +} + +/******************************************************************************* + * + * Function bta_csip_give_new_set_found_cb + * + * Description Give new coordinate set found callback to upper layer. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_give_new_set_found_cb (tBTA_CSIS_SRVC_INFO *srvc) { + /* Check if this remote csis instance is included in another service */ + const std::vector* services = + BTA_GATTC_GetServices(srvc->conn_id); + + if (services) { + for (const gatt::Service& service : *services) { + if (service.is_primary) { + for (const gatt::IncludedService &included_srvc : service.included_services) { + if (included_srvc.uuid == CSIS_SERVICE_UUID + && service.handle == srvc->service_handle) { + APPL_TRACE_DEBUG("%s: service Uuid of service including CSIS service : %s", + __func__, service.uuid.ToString().c_str()); + srvc->including_srvc_uuid = service.uuid; + } + } + } + } + } + + // Given New Set found callback to upper layer + tBTA_CSIP_NEW_SET_FOUND new_set_params; + new_set_params.set_id = srvc->set_id; + memcpy(new_set_params.sirk, srvc->sirk, SIRK_SIZE); + new_set_params.size = srvc->size; + new_set_params.including_srvc_uuid = srvc->including_srvc_uuid; + new_set_params.addr = srvc->bd_addr; + new_set_params.lock_support = (srvc->lock_handle != 0)? true : false; + + (*bta_csip_cb.p_cback)(BTA_CSIP_NEW_SET_FOUND_EVT, (tBTA_CSIP_DATA *)&new_set_params); +} + +bool bta_csip_decrypt_sirk(tBTA_CSIS_SRVC_INFO *srvc, uint8_t *enc_sirk) { + // Get K from LTK or Link Key based on transport + Octet16 K = {}; + uint8_t gatt_if, transport = BT_TRANSPORT_LE; + RawAddress bdaddr; + GATT_GetConnectionInfor(srvc->conn_id, &gatt_if, bdaddr, &transport); + + char sample_data_prop[6]; + osi_property_get("vendor.bt.pts.sample_csis_data", sample_data_prop, "false"); + + if (!strncmp("true", sample_data_prop, 4)) { // comparing prop with "true" + K = {0x67, 0x6e, 0x1b, 0x9b, 0xd4, 0x48, 0x69, 0x6f, + 0x06, 0x1e, 0xc6, 0x22, 0x3c, 0xe5, 0xce, 0xd9}; + } else if (transport == BT_TRANSPORT_BR_EDR) { + K = BTM_SecGetDeviceLinkKey(srvc->bd_addr); + } else if (transport == BT_TRANSPORT_LE) { + RawAddress pseudo_addr; + pseudo_addr = bta_get_pseudo_addr_with_id_addr(srvc->bd_addr); + Octet16 rev_K = BTM_BleGetLTK(pseudo_addr); + std::reverse_copy(rev_K.begin(), rev_K.end(), K.begin()); + } + + if(is_key_empty(K)) { + APPL_TRACE_DEBUG("%s Invalid Key received", __func__); + srvc->discovery_status = BTA_CSIP_INVALID_KEY; + return false; + } + + /* compute SALT */ + Octet16 salt = bta_csip_get_salt(); + + // Compute T + Octet16 T = bta_csip_compute_T(salt, K); + + // Compute final result k1 + Octet16 k1 = bta_csip_compute_k1(T); + + // Get decrypted SIRK + Octet16 r_k1; + std::reverse_copy(k1.begin(), k1.end(), r_k1.begin()); + bta_csip_get_decrypted_sirk(r_k1, enc_sirk, srvc->sirk); + return true; +} + +/******************************************************************************* + * + * Function bta_sirk_read_cb + * + * Description Callback received when remote device Coordinated Sets SIRK + * is read. + * + * Returns None + * + ******************************************************************************/ +void bta_sirk_read_cb(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + APPL_TRACE_DEBUG("%s ", __func__); + + if (status != GATT_SUCCESS) { + APPL_TRACE_ERROR("%s: SIRK Read failed. conn_id = %d status = %04x", + __func__, conn_id, status); + return; + } + + tBTA_CSIS_SRVC_INFO *srvc = (tBTA_CSIS_SRVC_INFO *)data; + + uint8_t type = 0xFF; + LOG(INFO) << __func__ << " SIRK len = " << +len; + + if (len != (SIRK_SIZE + 1)) { + APPL_TRACE_ERROR("%s : Invalid SIRK length", __func__); + srvc->discovery_status = BTA_CSIP_INVALID_SIRK_FORMAT; + bta_csip_csis_disc_complete_ind(srvc->bd_addr); + return; + } + + STREAM_TO_UINT8(type, value); + APPL_TRACE_DEBUG("%s Type Field with SIRK = %d", __func__, type); + if (type != ENCRYPTED_SIRK && type != PLAINTEXT_SIRK) { + APPL_TRACE_ERROR("%s : Invalid SIRK Type", __func__); + srvc->discovery_status = BTA_CSIP_INVALID_KEY_TYPE; + bta_csip_csis_disc_complete_ind(srvc->bd_addr); + return; + } + + if (type == ENCRYPTED_SIRK) { + uint8_t enc_sirk[SIRK_SIZE] = {}; + STREAM_TO_ARRAY(enc_sirk, value, SIRK_SIZE); + if (!bta_csip_decrypt_sirk(srvc, enc_sirk)) { + APPL_TRACE_ERROR("%s : Invalid Empty Key", __func__); + srvc->discovery_status = BTA_CSIP_INVALID_KEY; + bta_csip_csis_disc_complete_ind(srvc->bd_addr); + return; + } + } else { + STREAM_TO_ARRAY(srvc->sirk, value, SIRK_SIZE); + } + // check if this set was found earlier + uint8_t set_id = bta_csip_find_set_id_by_sirk (srvc->sirk); + tBTA_CSET_CB *cset_cb = NULL; + + /* New Coordinated Set */ + if (set_id == INVALID_SET_ID) { + cset_cb = bta_csip_get_cset_cb(); + if (!cset_cb) { + LOG(ERROR) << __func__ << " Insufficient set control blocks available."; + srvc->discovery_status = BTA_CSIP_RSRC_EXHAUSTED; + bta_csip_csis_disc_complete_ind(srvc->bd_addr); + return; + } + memcpy(cset_cb->sirk, srvc->sirk, SIRK_SIZE); + + // Create new coordinated set and update in database + tBTA_CSIP_CSET cset = {}; + cset.set_id = cset_cb->set_id; + cset.set_members.push_back(srvc->bd_addr); + cset.total_discovered++; + cset.lock_support = (srvc->lock_handle != 0 ? true : false); + LOG(INFO) << __func__ << "New Set. Adding device " << srvc->bd_addr.ToString() + << " Set ID: " << +cset.set_id; + bta_csip_cb.csets.push_back(cset); + + // assign set id in respective control blocks + srvc->set_id = cset_cb->set_id; + + /* Existing coordinated Set */ + } else { + LOG(INFO) << __func__ << " Device from existing set (set_id: " << +set_id << " )"; + //bta_csip_csis_disc_complete_ind(srvc->bd_addr); + srvc->set_id = set_id; + if (!bta_csip_update_set_member(set_id, srvc->bd_addr)) { + srvc->discovery_status = BTA_CSIP_ALL_MEMBERS_DISCOVERED; + bta_csip_csis_disc_complete_ind(srvc->bd_addr); + return; + } + + // Give set member found callback + tBTA_SET_MEMBER_FOUND set_member_params = + { .set_id = set_id, + .addr = srvc->bd_addr, + }; + + bta_csip_cb.p_cback (BTA_CSIP_SET_MEMBER_FOUND_EVT, (tBTA_CSIP_DATA *)&set_member_params); + return; + } + + /* If size is optional, give callback to upper layer */ + if (!srvc->size_handle) { + bta_csip_give_new_set_found_cb(srvc); + } + + if (!srvc->size_handle && !srvc->rank_handle) { + bta_csip_preserve_cset(srvc); + } +} + +/******************************************************************************* + * + * Function bta_size_read_cb + * + * Description Callback received when remote device Coordinated Sets SIZE + * characteristic is read. + * + * Returns None + * + ******************************************************************************/ +void bta_size_read_cb(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + + if (status != GATT_SUCCESS) { + APPL_TRACE_ERROR("%s: SIZE Read failed. conn_id = %d status = %04x", + __func__, conn_id, status); + return; + } + + tBTA_CSIS_SRVC_INFO *srvc = (tBTA_CSIS_SRVC_INFO *)data; + + if (srvc->discovery_status != BTA_CSIP_DISC_SUCCESS) { + APPL_TRACE_ERROR("%s: Ignore response (Reason: %d)", __func__, srvc->discovery_status); + return; + } + + srvc->size = *value; + APPL_TRACE_DEBUG("%s size = %d", __func__, srvc->size); + + tBTA_CSIP_CSET* cset = bta_csip_get_or_create_cset(srvc->set_id, true); + if (cset) cset->size = srvc->size; + // Give callback only when its a first set member + uint8_t totalDiscovered = bta_csip_get_coordinated_set(srvc->set_id).set_members.size(); + if (totalDiscovered == 1) { + bta_csip_give_new_set_found_cb(srvc); + } + + if (!srvc->rank_handle) { + bta_csip_preserve_cset(srvc); + } +} + +/******************************************************************************* + * + * Function bta_lock_read_cb + * + * Description Callback received when remote device Coordinated Sets LOCK + * characteristic is read. + * + * Returns None + * + ******************************************************************************/ +void bta_lock_read_cb(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + if (status != GATT_SUCCESS) { + APPL_TRACE_ERROR("%s: LOCK Read failed. conn_id = %d status = %04x", + __func__, conn_id, status); + return; + } + + APPL_TRACE_DEBUG("%s lock value = %d", __func__, *value); +} + +/******************************************************************************* + * + * Function bta_rank_read_cb + * + * Description Callback received when remote device Coordinated Sets RANK + * characteristic is read. + * + * Returns None + * + ******************************************************************************/ +void bta_rank_read_cb(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + if (status != GATT_SUCCESS) { + APPL_TRACE_ERROR("%s: Rank Read failed. conn_id = %d status = %04x", + __func__, conn_id, status); + return; + } + + tBTA_CSIS_SRVC_INFO *srvc = (tBTA_CSIS_SRVC_INFO *)data; + if (srvc->discovery_status != BTA_CSIP_DISC_SUCCESS) { + APPL_TRACE_ERROR("%s: Ignore response (Reason: %d)", __func__, srvc->discovery_status); + return; + } + + srvc->rank = *value; + APPL_TRACE_DEBUG("%s device: %s Rank = %d set_id: %d", __func__, + srvc->bd_addr.ToString().c_str(), srvc->rank, srvc->set_id); + + // get coordinated set control block from set_id + tBTA_CSET_CB *cset_cb = bta_csip_get_cset_cb_by_id(srvc->set_id); + if (cset_cb) { + cset_cb->ordered_members.insert({srvc->rank, srvc->bd_addr}); + } + + bta_csip_preserve_cset(srvc); + bta_csip_csis_disc_complete_ind(srvc->bd_addr); +} + +/******************************************************************************* + * + * Function bta_csip_gatt_disc_cmpl_act + * + * Description This APIS is used to serach presence of csis service on + * remote device and initialize CSIS handles in csis service + * control block. + * + * Returns None + * + ******************************************************************************/ +void bta_csip_gatt_disc_cmpl_act(tBTA_CSIP_DISC_SET *disc_params) { + uint16_t conn_id = disc_params->conn_id; + uint8_t status = disc_params->status; + RawAddress addr = disc_params->addr; + + APPL_TRACE_DEBUG("%s conn_id = %d, status = %d addr: %s", __func__, conn_id, + status, addr.ToString().c_str()); + + if (status) return; + + // Fetch remote device gatt services from database + const std::vector* services = + BTA_GATTC_GetServices(conn_id); + + if (!services) { + LOG(ERROR) << __func__ << " No Services discovered."; + bta_csip_csis_disc_complete_ind(addr); + return; + } + + tBTA_CSIP_DEV_CB* dev_cb = bta_csip_find_dev_cb_by_bda(addr); + + if (!dev_cb) { + dev_cb = bta_csip_create_dev_cb_for_bda(addr); + } + + dev_cb->csis_instance_count = 0; + + // Search for CSIS service in the database + for (const gatt::Service& service : *services) { + if (service.uuid == CSIS_SERVICE_UUID) { + dev_cb->csis_instance_count++; + // Get service control block from service handle (subsequent connection) + tBTA_CSIS_SRVC_INFO *srvc = bta_csip_get_csis_service_by_handle(dev_cb, service.handle); + if (!srvc) { + // create new service cb (if its a first time connection) + srvc = bta_csip_get_csis_service_cb(dev_cb); + if (!srvc) { + APPL_TRACE_ERROR("%s Resources not available for storing CSIS Service.", __func__); + return; + } + } + srvc->bd_addr = addr; + srvc->service_handle = service.handle; + srvc->conn_id = conn_id; + APPL_TRACE_DEBUG("%s: CSIS service found Uuid: %s service_handle = %d", __func__, + service.uuid.ToString().c_str(), srvc->service_handle); + + // Get Characteristic and CCCD handle + for (const gatt::Characteristic& charac : service.characteristics) { + Uuid uuid1 = charac.uuid; + if (uuid1 == CSIS_SERVICE_SIRK_UUID) { + srvc->sirk_handle = charac.value_handle; + srvc->sirk_ccd_handle = bta_csip_get_cccd_handle(conn_id, charac.value_handle); + } else if (uuid1 == CSIS_SERVICE_SIZE_UUID) { + srvc->size_handle = charac.value_handle; + srvc->size_ccd_handle = bta_csip_get_cccd_handle(conn_id, charac.value_handle); + } else if (uuid1 == CSIS_SERVICE_LOCK_UUID) { + srvc->lock_handle = charac.value_handle; + srvc->lock_ccd_handle = bta_csip_get_cccd_handle(conn_id, charac.value_handle); + } else if (uuid1 == CSIS_SERVICE_RANK_UUID) { + srvc->rank_handle = charac.value_handle; + } + } + + /* Skip reading characteristics and Set Discovery procedure if it was done earlier */ + if (srvc->set_id >= 0 && srvc->set_id < BTA_MAX_SUPPORTED_SETS) { + LOG(INFO) << __func__ << " Coordinated set discovery procedure already completed."; + continue; + } + + if (srvc->sirk_handle) { + BtaGattQueue::ReadCharacteristic( + conn_id, srvc->sirk_handle, bta_sirk_read_cb, srvc); + } + + if (srvc->size_handle) { + BtaGattQueue::ReadCharacteristic( + conn_id, srvc->size_handle, bta_size_read_cb, srvc); + } + + if (srvc->lock_handle) { + BtaGattQueue::ReadCharacteristic( + conn_id, srvc->lock_handle, bta_lock_read_cb, srvc); + } + + if (srvc->rank_handle) { + BtaGattQueue::ReadCharacteristic( + conn_id, srvc->rank_handle, bta_rank_read_cb, srvc); + } + } + + } +} diff --git a/le_audio/system/bt/bta/csip/bta_csip_api.cc b/le_audio/system/bt/bta/csip/bta_csip_api.cc new file mode 100644 index 00000000000..13f6ffdb6bd --- /dev/null +++ b/le_audio/system/bt/bta/csip/bta_csip_api.cc @@ -0,0 +1,273 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +/****************************************************************************** + * + * This file contains the CSIP API in the subsystem of BTA. + * + ******************************************************************************/ + +#define LOG_TAG "bt_bta_csip" + +#include +#include +#include +#include +#include +#include + +#include "bta_csip_api.h" +#include "bta_csip_int.h" +#include "bta_gatt_queue.h" +#include "osi/include/log.h" +#include "osi/include/osi.h" + +/***************************************************************************** + * Constants + ****************************************************************************/ +static const tBTA_SYS_REG bta_csip_reg = {bta_csip_hdl_event, BTA_CsipDisable}; + +/********************************************************************************* + * + * Function BTA_RegisterCsipApp + * + * Description This function is called to register application or module to + * to register with CSIP for using CSIP functionalities. + * + * Parameters p_csip_cb: callback to be received in registering app when + * required CSIP operation is completed. + * reg_cb : callback when app/module is registered with CSIP. + * + * Returns None + * + *********************************************************************************/ +void BTA_RegisterCsipApp(tBTA_CSIP_CBACK* p_csip_cb, + BtaCsipAppRegisteredCb reg_cb) { + do_in_bta_thread(FROM_HERE, base::Bind(&bta_csip_app_register, Uuid::GetRandom(), + p_csip_cb, std::move(reg_cb))); +} + +/********************************************************************************* + * + * Function BTA_UnregisterCsipApp + * + * Description This function is called to unregister application or module. + * + * Parameters app_id: id of the app/module that needs to be unregistered. + * + * Returns None + * + *********************************************************************************/ + +void BTA_UnregisterCsipApp(uint8_t app_id) { + do_in_bta_thread(FROM_HERE, base::Bind(&bta_csip_app_unregister, app_id)); +} + +/********************************************************************************* + * + * Function BTA_CsipSetLockValue + * + * Description This function is called to request or release lock for the + * coordinated set. + * + * Parameters lock_param: parameters to acquire or release lock. + * (tBTA_SET_LOCK_PARAMS). + * + * Returns None + * + *********************************************************************************/ +void BTA_CsipSetLockValue(tBTA_SET_LOCK_PARAMS lock_params) { + tBTA_CSIP_LOCK_PARAMS* p_buf = + (tBTA_CSIP_LOCK_PARAMS*)osi_calloc(sizeof(tBTA_CSIP_LOCK_PARAMS)); + + p_buf->hdr.event = BTA_CSIP_SET_LOCK_VALUE_EVT; + p_buf->lock_req = lock_params; + + bta_sys_sendmsg(p_buf); +} + +/********************************************************************************* + * + * Function BTA_CsipGetCoordinatedSet + * + * Description This function is called to fetch details of the coordinated set. + * + * Parameters set_id: identifier of the coordinated set whose details are + * required to be fetched. + * + * Returns tBTA_CSIP_CSET (containing details of coordinated set). + * + *********************************************************************************/ +tBTA_CSIP_CSET BTA_CsipGetCoordinatedSet(uint8_t set_id) { + APPL_TRACE_DEBUG("%s: set_id = %d", __func__, set_id); + return bta_csip_get_coordinated_set(set_id); +} + +/********************************************************************************* + * + * Function BTA_CsipSetLockValue + * + * Description This function is called to request or release lock for the + * coordinated set. + * + * Parameters None. + * + * Returns vector: (all discovered coordinated set) + * + *********************************************************************************/ +std::vector BTA_CsipGetDiscoveredSets() { + return bta_csip_cb.csets; +} + +/********************************************************************************* + * + * Function BTA_CsipConnect + * + * Description This function is called to establish GATT Connection. + * + * Parameters bd_addr : Address of the remote device. + * + * Returns None. + * + *********************************************************************************/ +void BTA_CsipConnect (uint8_t app_id, const RawAddress& bd_addr) { + tBTA_CSIP_API_CONN* p_buf = + (tBTA_CSIP_API_CONN*)osi_calloc(sizeof(tBTA_CSIP_API_CONN)); + p_buf->hdr.event = BTA_CSIP_API_OPEN_EVT; + p_buf->bd_addr = bd_addr; + p_buf->app_id = app_id; + + bta_sys_sendmsg(p_buf); +} + +/********************************************************************************* + * + * Function BTA_CsipConnect + * + * Description This function is called to establish GATT Connection. + * + * Parameters bd_addr : Address of the remote device. + * + * Returns None. + * + *********************************************************************************/ +void BTA_CsipDisconnect (uint8_t app_id, const RawAddress& bd_addr) { + tBTA_CSIP_API_CONN* p_buf = + (tBTA_CSIP_API_CONN*)osi_calloc(sizeof(tBTA_CSIP_API_CONN)); + p_buf->hdr.event = BTA_CSIP_API_CLOSE_EVT; + p_buf->bd_addr = bd_addr; + p_buf->app_id = app_id; + + bta_sys_sendmsg(p_buf); +} + +/********************************************************************************* + * + * Function BTA_CsipFindCsisInstance + * + * Description This function is called to find presence of CSIS service on + * remote device. + * + * Parameters coon_id : Connection ID of the GATT Connection at DM Layer. + * + * Returns None. + * + *********************************************************************************/ +void BTA_CsipFindCsisInstance(uint16_t conn_id, tGATT_STATUS status, + RawAddress& bd_addr) { + APPL_TRACE_DEBUG("%s ", __func__); + + tBTA_CSIP_DISC_SET* p_buf = + (tBTA_CSIP_DISC_SET*)osi_calloc(sizeof(tBTA_CSIP_DISC_SET)); + p_buf->hdr.event = BTA_CSIP_DISC_CMPL_EVT; + p_buf->conn_id = conn_id; + p_buf->status = status; + p_buf->addr = bd_addr; + + bta_sys_sendmsg(p_buf); +} + +/********************************************************************************* + * + * Function BTA_CsipInit + * + * Description This function is invoked to initialize CSIP in BTA layer. + * + * Parameters None. + * + * Returns None. + * + *********************************************************************************/ +void BTA_CsipEnable(tBTA_CSIP_CBACK *p_cback) { + tBTA_CSIP_ENABLE* p_buf = + (tBTA_CSIP_ENABLE*)osi_calloc(sizeof(tBTA_CSIP_ENABLE)); + + /* register with BTA system manager */ + bta_sys_register(BTA_ID_GROUP, &bta_csip_reg); + + p_buf->hdr.event = BTA_CSIP_API_ENABLE_EVT; + p_buf->p_cback = p_cback; + + bta_sys_sendmsg(p_buf); +} + +/********************************************************************************* + * + * Function BTA_CsipDisable + * + * Description This function is called for deinitialization. + * + * Parameters None. + * + * Returns None. + * + *********************************************************************************/ +void BTA_CsipDisable() { + tBTA_CSIP_ENABLE* p_buf = + (tBTA_CSIP_ENABLE*)osi_calloc(sizeof(tBTA_CSIP_ENABLE)); + + p_buf->hdr.event = BTA_CSIP_API_DISABLE_EVT; + + bta_sys_sendmsg(p_buf); +} + +/********************************************************************************* + * + * Function BTA_CsipRemoveUnpairedSetMember + * + * Description This function is called when a given set member is unpaired. + * + * Parameters addr: BD Address of the set member. + * + * Returns None. + * + *********************************************************************************/ +void BTA_CsipRemoveUnpairedSetMember(RawAddress addr) { + do_in_bta_thread(FROM_HERE, base::Bind(&bta_csip_remove_set_member, addr)); +} + +/********************************************************************************* + * + * Function BTA_CsipGetDeviceSetId + * + * Description This API is used to get set id of the remote device. + * + * Parameters addr: BD Address of the set member. + * uuid: UUID of the service which includes CSIS service. + * + * Returns None. + * + *********************************************************************************/ +uint8_t BTA_CsipGetDeviceSetId(RawAddress addr, bluetooth::Uuid uuid) { + for (tBTA_CSIP_CSET cset: bta_csip_cb.csets) { + for (RawAddress bd_addr: cset.set_members) { + if (bd_addr == addr && (cset.p_srvc_uuid == uuid)) { + return cset.set_id; + } + } + } + + return BTA_MAX_SUPPORTED_SETS; +} diff --git a/le_audio/system/bt/bta/csip/bta_csip_int.h b/le_audio/system/bt/bta/csip/bta_csip_int.h new file mode 100644 index 00000000000..2d7b911ad23 --- /dev/null +++ b/le_audio/system/bt/bta/csip/bta_csip_int.h @@ -0,0 +1,307 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +/****************************************************************************** + * + * This file contains BTA CSIP Client internal definitions + * + ******************************************************************************/ + +#ifndef BTA_CSIP_INT_H +#define BTA_CSIP_INT_H + +#include "bta_csip_api.h" +#include "bta_gatt_api.h" +#include "bta_sys.h" +#include "btm_ble_api_types.h" + +/* Max CSIP supported devices (control blocks) */ +#define BTA_CSIP_MAX_DEVICE 32 + +/* Max CSIP supported coordinated sets */ +#define BTA_MAX_SUPPORTED_SETS 16 + +/* Maximum number of apps that can be registered with CSIP*/ +#define BTA_CSIP_MAX_SUPPORTED_APPS 16 + +/* Max Supported coordinated sets per device*/ +#define MAX_SUPPORTED_SETS_PER_DEVICE 5 + +/* Status of the CSIS Discovery*/ +#define BTA_CSIP_DISC_SUCCESS 0 +#define BTA_CSIP_INVALID_SIRK_FORMAT 1 +#define BTA_CSIP_INVALID_KEY 2 +#define BTA_CSIP_INVALID_KEY_TYPE 3 +#define BTA_CSIP_ALL_MEMBERS_DISCOVERED 4 +#define BTA_CSIP_RSRC_EXHAUSTED 5 + +using bluetooth::Uuid; + +/* state machine events, these events are handled by the state machine */ +enum { + BTA_CSIP_API_OPEN_EVT = BTA_SYS_EVT_START(BTA_ID_GROUP), + BTA_CSIP_API_CLOSE_EVT, + BTA_CSIP_GATT_OPEN_EVT, + BTA_CSIP_GATT_CLOSE_EVT, + BTA_CSIP_OPEN_FAIL_EVT, + BTA_CSIP_OPEN_CMPL_EVT, + BTA_CSIP_START_ENC_EVT, + BTA_CSIP_ENC_CMPL_EVT, + BTA_CSIP_GATT_ENC_CMPL_EVT, + + /* common events: not handled by execute state machine */ + BTA_CSIP_API_ENABLE_EVT, + BTA_CSIP_API_DISABLE_EVT, + BTA_CSIP_DISC_CMPL_EVT, + BTA_CSIP_SET_LOCK_VALUE_EVT, +}; + +/* CSIP device state machine states */ +enum { + BTA_CSIP_IDLE_ST, + BTA_CSIP_W4_CONN_ST, + BTA_CSIP_W4_SEC, + BTA_CSIP_CONN_ST, + BTA_CSIP_DISCONNECTING_ST, +}; + +typedef uint8_t tBTA_CSIP_STATE; + + +/* CSIP Command request parameters in BTA */ + +/* Find Coordinated set parameters*/ +typedef struct { + BT_HDR hdr; + uint16_t conn_id; + tGATT_STATUS status; + RawAddress addr; +} tBTA_CSIP_DISC_SET; + +/* Connection Request parameters */ +typedef struct { + BT_HDR hdr; + RawAddress bd_addr; + uint8_t app_id; +} tBTA_CSIP_API_CONN; + +/* Lock request parameters */ +typedef struct { + BT_HDR hdr; + tBTA_SET_LOCK_PARAMS lock_req; +} tBTA_CSIP_LOCK_PARAMS; + + +typedef struct { + BT_HDR hdr; + tBTA_CSIP_CBACK* p_csip_cb; + tBTA_CSIP_CLT_REG_CB* reg_cb; +} tBTA_CSIP_APP_REG_PARAMS; + +typedef struct { + BT_HDR hdr; + tBTA_CSIP_CBACK *p_cback; +} tBTA_CSIP_ENABLE; + +typedef struct { + BT_HDR hdr; +} tBTA_CSIP_CMD; + +typedef union { + BT_HDR hdr; + tBTA_CSIP_API_CONN conn_param; + tBTA_CSIP_LOCK_PARAMS lock_req; + tBTA_CSIP_APP_REG_PARAMS reg_param; + tBTA_CSIP_ENABLE enable_param; + tBTA_GATTC_OPEN gatt_open_param; + tBTA_GATTC_CLOSE gatt_close_param; + tBTA_CSIP_CMD cmd; +} tBTA_CSIP_REQ_DATA; + +typedef struct { + bool in_use; + uint8_t set_id = BTA_MAX_SUPPORTED_SETS; + uint16_t conn_id; /* GATT conn_id used for service discovery */ + RawAddress bd_addr; + + uint16_t service_handle; /* Handle of this CSIS service */ + uint16_t sirk_handle; /* SIRK characteristic value handle */ + uint16_t size_handle; /* size characteristic value handle */ + uint16_t lock_handle; /* lock characteristic value handle */ + uint16_t rank_handle; /* rank characteristic value handle */ + + uint16_t sirk_ccd_handle; /* SIRK CCCD handle*/ + uint16_t size_ccd_handle; /* size CCCD handle */ + uint16_t lock_ccd_handle; /* lock CCCD handle */ + + uint8_t sirk[SIRK_SIZE]; /* Coordinated set SIRK */ + uint8_t size; /* size of the coordinated set */ + uint8_t lock; /* lock status of the set member */ + uint8_t rank; /* rank of the set member*/ + uint8_t discovery_status = BTA_CSIP_DISC_SUCCESS; /* status of the CSIS discovery*/ + bluetooth::Uuid including_srvc_uuid; /* uuid of the service which includes CSIS*/ + + /* Lock mamangement details*/ + std::vector lock_applist; /* Apps those have locked this set */ + std::vector unrsp_applist; /* Apps to which unresponsive res is sent */ + std::vector denied_applist; /* Apps to which lock was denied */ +} tBTA_CSIS_SRVC_INFO; + +typedef struct { + bool in_use; + RawAddress addr; /* Remote device address */ + uint16_t conn_id; /* GATT Connection ID */ + uint8_t sec_mask = (BTM_SEC_IN_ENCRYPT | BTM_SEC_OUT_ENCRYPT); /* Security Mask for CSIP*/ + bool security_pending; + bool is_disc_external = false; /* if discovery is started by external App*/ + uint8_t state; /* connection state */ + uint8_t csis_instance_count; /* number of CSIS instances on remote device */ + uint8_t total_instance_disc; /* total number of instances discovered */ + + /* CSIS services found on remote device*/ + tBTA_CSIS_SRVC_INFO csis_srvc[MAX_SUPPORTED_SETS_PER_DEVICE]; + + // list of applications which initiated CSIP connect for this device + std::vector conn_applist; /* List of Apps that sent connection request*/ + std::string set_info = ""; + bool unresponsive; /* if remote is unresponsive to GATT request */ +} tBTA_CSIP_DEV_CB; + +typedef struct { + uint8_t app_id; + uint8_t set_id; + uint8_t value; + int8_t cur_idx; + std::vector members_addr; +} tBTA_LOCK_REQUEST; + +typedef struct { + bool in_use; + uint8_t set_id = BTA_MAX_SUPPORTED_SETS; + uint8_t sirk[SIRK_SIZE]; + uint16_t set_member_tout = 500; + bool request_in_progress; + tBTA_CSIP_DEV_CB* cur_dev_cb; + alarm_t* unresp_timer; + tBTA_LOCK_REQUEST cur_lock_req; + tBTA_LOCK_STATUS_CHANGED cur_lock_res; + std::map ordered_members; + std::queue lock_req_queue; +} tBTA_CSET_CB; + +typedef struct { + uint8_t app_id; + bool in_use; + tBTA_CSIP_CBACK* p_cback; +} tBTA_CSIP_RCB; + +typedef struct { + tGATT_IF gatt_if; + tBTA_CSIP_CBACK* p_cback; /* callbacks for btif layer */ + std::vector dev_cb; /* device control block */ + tBTA_CSIP_RCB app_rcb[BTA_CSIP_MAX_SUPPORTED_APPS]; + std::vector csets; + tBTA_CSET_CB csets_cb[BTA_MAX_SUPPORTED_SETS]; +} tBTA_CSIP_CB; + +/***************************************************************************** + * Global data + ****************************************************************************/ + +/* CSIP control block */ +extern tBTA_CSIP_CB bta_csip_cb; + +/***************************************************************************** + * Function prototypes + ****************************************************************************/ +void bta_csip_sm_execute(tBTA_CSIP_DEV_CB* p_cb, uint16_t event, + tBTA_CSIP_REQ_DATA* p_data); + + +//action api's +extern bool bta_csip_hdl_event(BT_HDR* p_msg); +extern void bta_csip_api_enable(tBTA_CSIP_CBACK *p_cback); +extern void bta_csip_api_disable(); +extern void bta_csip_app_register (const Uuid& app_uuid, tBTA_CSIP_CBACK* p_cback, + BtaCsipAppRegisteredCb cb); +extern void bta_csip_gattc_register(); +extern void bta_csip_app_unregister(uint8_t app_id); +extern void bta_csip_gatt_disc_cmpl_act(tBTA_CSIP_DISC_SET *disc_params); +extern void bta_csip_api_open_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data); +extern void bta_csip_api_close_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data); +extern void bta_csip_gatt_open_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data); +extern void bta_csip_gatt_close_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data); +extern void bta_csip_gatt_open_fail_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data); +extern void bta_csip_open_cmpl_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data); +extern void bta_csip_start_sec_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data); +extern void bta_csip_sec_cmpl_act (tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data); +extern void bta_csip_close_csip_conn (tBTA_CSIP_DEV_CB* p_cb); +extern void bta_csip_process_set_lock_act(tBTA_SET_LOCK_PARAMS lock_req); +extern void bta_csip_send_lock_req_act(tBTA_CSET_CB* cset_cb); +extern void bta_csip_handle_lock_denial(tBTA_CSET_CB* cset_cb); +extern bool bta_csip_validate_req_for_denied_sm (tBTA_CSET_CB* cset_cb); +extern void bta_csip_form_lock_request(tBTA_SET_LOCK_PARAMS lock_param, + tBTA_CSET_CB* cset_cb); +extern void bta_csip_send_unlock_req_act(tBTA_CSET_CB* cset_cb); +extern void bta_csip_set_member_lock_timeout(void* p_data); +extern void bta_csip_load_coordinated_sets_from_storage(); + +// bta_csip_utils +extern tBTA_CSIP_DEV_CB* bta_csip_find_dev_cb_by_bda(const RawAddress& bda); +extern tBTA_CSIP_DEV_CB* bta_csip_get_dev_cb_by_cid(uint16_t conn_id); +extern tBTA_CSIP_DEV_CB* bta_csip_create_dev_cb_for_bda(const RawAddress& bda); +extern tBTA_CSIS_SRVC_INFO* bta_csip_get_csis_service_cb(tBTA_CSIP_DEV_CB* dev_cb); +extern bool bta_csip_is_csis_supported(tBTA_CSIP_DEV_CB* dev_cb); +extern tBTA_CSIS_SRVC_INFO* bta_csip_get_csis_service_by_handle(tBTA_CSIP_DEV_CB* dev_cb, + uint16_t service_handle); +extern tBTA_CSIS_SRVC_INFO* bta_csip_find_csis_srvc_by_lock_handle(tBTA_CSIP_DEV_CB* dev_cb, + uint16_t lock_handle); +extern tBTA_CSIP_CSET* bta_csip_get_or_create_cset (uint8_t set_id, bool existing); +extern bool bta_csip_validate_set_params(tBTA_SET_LOCK_PARAMS* lock_req); +extern bool bta_csip_is_valid_lock_request(tBTA_SET_LOCK_PARAMS* lock_req); +extern std::vector bta_csip_arrange_set_members_by_order( + uint8_t set_id, std::vector& req_sm, bool ascending); +extern tBTA_CSIP_CSET bta_csip_get_coordinated_set (uint8_t set_id); +extern bool bta_csip_update_set_member (uint8_t set_id, RawAddress addr); +extern tBTA_CSET_CB* bta_csip_get_cset_cb (); +extern tBTA_CSET_CB* bta_csip_get_cset_cb_by_id (uint8_t set_id); +extern uint8_t bta_csip_find_set_id_by_sirk (uint8_t* sirk); +extern tBTA_CSIS_SRVC_INFO* bta_csip_get_csis_instance(tBTA_CSIP_DEV_CB* dev_cb, uint8_t set_id); +extern bool bta_csip_is_locked_by_other_apps(tBTA_CSIS_SRVC_INFO* srvc, uint8_t app_id); +extern std::vector bta_csip_get_set_member_by_order(uint8_t set_id, + bool ascending); +extern bool bta_csip_is_member_locked_by_app (uint8_t app_id, tBTA_CSIS_SRVC_INFO* srvc); +extern void bta_csip_get_next_lock_request(tBTA_CSET_CB* cset_cb); +extern uint16_t bta_csip_get_cccd_handle (uint16_t conn_id, uint16_t char_handle); +extern bool bta_csip_is_app_reg(uint8_t app_id); +extern tBTA_CSIP_RCB* bta_csip_get_rcb (uint8_t app_id); +extern void bta_csip_remove_set_member (RawAddress addr); +extern void bta_csip_handle_notification(tBTA_GATTC_NOTIFY* ntf); +extern void bta_csip_handle_lock_value_notif(tBTA_CSIP_DEV_CB* p_cb, + uint16_t handle, uint8_t value); +extern void bta_csip_add_app_to_applist(tBTA_CSIP_DEV_CB* p_cb, uint8_t app_id); +extern void bta_csip_handle_unresponsive_sm_res(tBTA_CSIS_SRVC_INFO* srvc, + tGATT_STATUS status); +extern void bta_csip_remove_app_from_conn_list(tBTA_CSIP_DEV_CB* p_cb, uint8_t app_id); +extern bool bta_csip_is_app_from_applist(tBTA_CSIP_DEV_CB* p_cb, uint8_t app_id); +extern void bta_csip_send_conn_state_changed_cb(tBTA_CSIP_DEV_CB* p_cb, + uint8_t state, uint8_t status); +extern void bta_csip_send_conn_state_changed_cb (tBTA_CSIP_DEV_CB* p_cb, uint8_t app_id, + uint8_t state, uint8_t status); +extern void bta_csip_send_lock_req_cmpl_cb (tBTA_LOCK_STATUS_CHANGED cset_cb); +extern void bta_csip_write_cccd (tBTA_CSIP_DEV_CB* p_cb, uint16_t char_handle, + uint16_t cccd_handle); +extern void bta_csip_preserve_cset (tBTA_CSIS_SRVC_INFO* srvc); +extern Octet16 bta_csip_get_salt(); +extern Octet16 bta_csip_compute_T(Octet16 salt, Octet16 K); +extern Octet16 bta_csip_compute_k1(Octet16 T); +extern void bta_csip_get_decrypted_sirk(Octet16 k1, uint8_t *enc_sirk, uint8_t *sirk); +extern Octet16 bta_csip_get_aes_cmac_result(const Octet16& key, const Octet16& message); +extern Octet16 bta_csip_get_aes_cmac_result(const Octet16& key, const uint8_t* input, + uint16_t length); +extern void hex_string_to_byte_arr(char *str, uint8_t* byte_arr, uint8_t len); +extern void byte_arr_to_hex_string(uint8_t* byte_arr, char* str, uint8_t len); +extern bool is_key_empty(Octet16& key); +#endif diff --git a/le_audio/system/bt/bta/csip/bta_csip_main.cc b/le_audio/system/bt/bta/csip/bta_csip_main.cc new file mode 100644 index 00000000000..21073f5ed6f --- /dev/null +++ b/le_audio/system/bt/bta/csip/bta_csip_main.cc @@ -0,0 +1,282 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +#include "bt_target.h" +#include "bta_csip_int.h" + +#include + +#include "bt_common.h" + +#define LOG_TAG "bt_bta_csip" + +/***************************************************************************** + * Static methods + ****************************************************************************/ +static const char* bta_csip_evt_code(uint16_t evt_code); +static const char* bta_csip_state_code(tBTA_CSIP_STATE evt_code); + +/***************************************************************************** + * Global data + ****************************************************************************/ +tBTA_CSIP_CB bta_csip_cb; + +enum { + BTA_CSIP_OPEN_ACT, + BTA_CSIP_CLOSE_ACT, + BTA_CSIP_GATT_OPEN_ACT, + BTA_CSIP_GATT_CLOSE_ACT, + BTA_CSIP_GATT_OPEN_FAIL_ACT, + BTA_CSIP_OPEN_CMPL_ACT, + BTA_CSIP_START_SEC_ACT, + BTA_CSIP_SEC_CMPL_ACT, + BTA_CSIP_IGNORE, +}; + +/* type for action functions */ +typedef void (*tBTA_CSIP_ACTION)(tBTA_CSIP_DEV_CB* p_cb, tBTA_CSIP_REQ_DATA* p_data); + +/* action functions */ +const tBTA_CSIP_ACTION bta_csip_action[] = { + bta_csip_api_open_act, + bta_csip_api_close_act, + bta_csip_gatt_open_act, + bta_csip_gatt_close_act, + bta_csip_gatt_open_fail_act, + bta_csip_open_cmpl_act, + bta_csip_start_sec_act, + bta_csip_sec_cmpl_act, +}; + +/* state table information */ +#define BTA_CSIP_ACTION 0 /* position of action */ +#define BTA_CSIP_NEXT_STATE 1 /* position of next state */ +#define BTA_CSIP_NUM_COLS 2 /* number of columns */ + +/* state table in idle state */ +const uint8_t bta_csip_st_idle[][BTA_CSIP_NUM_COLS] = { + /* Event Action Next state */ + /* BTA_CSIP_API_OPEN_EVT */ {BTA_CSIP_OPEN_ACT, BTA_CSIP_W4_CONN_ST}, + /* BTA_CSIP_API_CLOSE_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_GATT_OPEN_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_GATT_CLOSE_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_GATT_OPEN_FAIL_ACT */ {BTA_CSIP_IGNORE, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_OPEN_CMPL_EVT */ {BTA_CSIP_OPEN_CMPL_ACT, BTA_CSIP_CONN_ST}, + /* BTA_CSIP_START_ENC_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_ENC_CMPL_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_IDLE_ST}, +}; + +/* state table in wait for security state */ +const uint8_t bta_csip_st_w4_conn[][BTA_CSIP_NUM_COLS] = { + /* Event Action Next state */ + /* BTA_CSIP_API_OPEN_EVT */ {BTA_CSIP_OPEN_ACT, BTA_CSIP_W4_CONN_ST}, + /* BTA_CSIP_API_CLOSE_EVT */ {BTA_CSIP_CLOSE_ACT, BTA_CSIP_W4_CONN_ST}, + /* BTA_CSIP_GATT_OPEN_EVT */ {BTA_CSIP_GATT_OPEN_ACT, BTA_CSIP_W4_CONN_ST}, + /* BTA_CSIP_GATT_CLOSE_EVT */ {BTA_CSIP_GATT_CLOSE_ACT, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_GATT_OPEN_FAIL_ACT */ {BTA_CSIP_GATT_OPEN_FAIL_ACT, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_OPEN_CMPL_EVT */ {BTA_CSIP_OPEN_CMPL_ACT, BTA_CSIP_CONN_ST}, + /* BTA_CSIP_START_ENC_EVT */ {BTA_CSIP_START_SEC_ACT, BTA_CSIP_W4_SEC}, + /* BTA_CSIP_ENC_CMPL_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_W4_CONN_ST}, +}; + +/* state table in wait for connection state */ +const uint8_t bta_csip_st_w4_sec[][BTA_CSIP_NUM_COLS] = { + /* Event Action Next state */ + /* BTA_CSIP_API_OPEN_EVT */ {BTA_CSIP_OPEN_ACT, BTA_CSIP_W4_SEC}, + /* BTA_CSIP_API_CLOSE_EVT */ {BTA_CSIP_CLOSE_ACT, BTA_CSIP_W4_SEC}, + /* BTA_CSIP_GATT_OPEN_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_W4_SEC}, + /* BTA_CSIP_GATT_CLOSE_EVT */ {BTA_CSIP_GATT_CLOSE_ACT, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_GATT_OPEN_FAIL_ACT */ {BTA_CSIP_GATT_OPEN_FAIL_ACT, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_OPEN_CMPL_EVT */ {BTA_CSIP_OPEN_CMPL_ACT, BTA_CSIP_CONN_ST}, + /* BTA_CSIP_START_ENC_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_W4_SEC}, + /* BTA_CSIP_ENC_CMPL_EVT */ {BTA_CSIP_SEC_CMPL_ACT, BTA_CSIP_W4_CONN_ST}, +}; + +/* state table in connection state */ +const uint8_t bta_csip_st_connected[][BTA_CSIP_NUM_COLS] = { + /* Event Action Next state */ + /* BTA_CSIP_API_OPEN_EVT */ {BTA_CSIP_OPEN_ACT, BTA_CSIP_CONN_ST}, + /* BTA_CSIP_API_CLOSE_EVT */ {BTA_CSIP_CLOSE_ACT, BTA_CSIP_CONN_ST}, + /* BTA_CSIP_GATT_OPEN_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_CONN_ST}, + /* BTA_CSIP_GATT_CLOSE_EVT */ {BTA_CSIP_GATT_CLOSE_ACT, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_GATT_OPEN_FAIL_ACT */ {BTA_CSIP_GATT_OPEN_FAIL_ACT, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_OPEN_CMPL_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_CONN_ST}, + /* BTA_CSIP_START_ENC_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_CONN_ST}, + /* BTA_CSIP_ENC_CMPL_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_CONN_ST}, +}; + +/* state table in disconnecting state */ +const uint8_t bta_csip_st_disconnecting[][BTA_CSIP_NUM_COLS] = { + /* Event Action Next state */ + /* BTA_CSIP_API_OPEN_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_DISCONNECTING_ST}, + /* BTA_CSIP_API_CLOSE_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_DISCONNECTING_ST}, + /* BTA_CSIP_GATT_OPEN_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_DISCONNECTING_ST}, + /* BTA_CSIP_GATT_CLOSE_EVT */ {BTA_CSIP_GATT_CLOSE_ACT, BTA_CSIP_IDLE_ST}, + /* BTA_CSIP_GATT_OPEN_FAIL_ACT */ {BTA_CSIP_IGNORE, BTA_CSIP_DISCONNECTING_ST}, + /* BTA_CSIP_OPEN_CMPL_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_DISCONNECTING_ST}, + /* BTA_CSIP_START_ENC_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_DISCONNECTING_ST}, + /* BTA_CSIP_ENC_CMPL_EVT */ {BTA_CSIP_IGNORE, BTA_CSIP_DISCONNECTING_ST}, +}; + +/* type for state table */ +typedef const uint8_t (*tBTA_CSIP_ST_TBL)[BTA_CSIP_NUM_COLS]; + +/* state table */ +tBTA_CSIP_ST_TBL bta_csip_st_tbl[] = {bta_csip_st_idle, bta_csip_st_w4_conn, + bta_csip_st_w4_sec, bta_csip_st_connected, + bta_csip_st_disconnecting}; + +/******************************************************************************* + * + * Function bta_csip_sm_execute + * + * Description API to execute state operation. + * + * Returns void + * + ******************************************************************************/ +void bta_csip_sm_execute(tBTA_CSIP_DEV_CB* p_cb, uint16_t event, + tBTA_CSIP_REQ_DATA* p_data) { + tBTA_CSIP_ST_TBL state_table; + uint8_t action; + + if (!p_cb) { + APPL_TRACE_ERROR("%s: Device not found. Return.", __func__); + return; + } + + state_table = bta_csip_st_tbl[p_cb->state]; + + event &= 0xff; + + p_cb->state = state_table[event][BTA_CSIP_NEXT_STATE]; + APPL_TRACE_DEBUG("%s: Next State = %d(%s) event = %04x(%s)", __func__, + p_cb->state, bta_csip_state_code(p_cb->state), event, + bta_csip_evt_code(event)); + + action = state_table[event][BTA_CSIP_ACTION]; + APPL_TRACE_DEBUG("%s: action = %d", __func__, action); + if (action != BTA_CSIP_IGNORE) { + (*bta_csip_action[action])(p_cb, p_data); + } + +} + +/******************************************************************************* + * + * Function bta_csip_hdl_event + * + * Description CSIP client main event handling function. + * + * Returns void + * + ******************************************************************************/ +bool bta_csip_hdl_event(BT_HDR* p_msg) { + tBTA_CSIP_DEV_CB* dev_cb = NULL; + + APPL_TRACE_DEBUG("%s: Event: %04x", __func__, p_msg->event); + + switch (p_msg->event) { + case BTA_CSIP_API_ENABLE_EVT: + bta_csip_api_enable(((tBTA_CSIP_ENABLE *)p_msg)->p_cback); + break; + + case BTA_CSIP_API_DISABLE_EVT: + bta_csip_api_disable(); + break; + + case BTA_CSIP_DISC_CMPL_EVT: + bta_csip_gatt_disc_cmpl_act((tBTA_CSIP_DISC_SET *)p_msg); + break; + + case BTA_CSIP_SET_LOCK_VALUE_EVT: + bta_csip_process_set_lock_act(((tBTA_CSIP_LOCK_PARAMS*)p_msg)->lock_req); + break; + + default: + if (p_msg->event == BTA_CSIP_API_OPEN_EVT) { + RawAddress bd_addr = ((tBTA_CSIP_API_CONN *)p_msg)->bd_addr; + dev_cb = bta_csip_find_dev_cb_by_bda(bd_addr); + if (!dev_cb) { + dev_cb = bta_csip_create_dev_cb_for_bda(bd_addr); + APPL_TRACE_DEBUG("%s: Created Device CB for device: %s", + __func__, bd_addr.ToString().c_str()); + } + } else if (p_msg->event == BTA_CSIP_API_CLOSE_EVT) { + dev_cb = bta_csip_find_dev_cb_by_bda(((tBTA_CSIP_API_CONN *)p_msg)->bd_addr); + } + + bta_csip_sm_execute(dev_cb, p_msg->event, (tBTA_CSIP_REQ_DATA*)p_msg); + } + + return (true); + +} + +/******************************************************************************* + * + * Function bta_csip_evt_code + * + * Description returns event name in string format + * + * Returns string representation of event code + * + ******************************************************************************/ +static const char* bta_csip_evt_code(uint16_t evt_code) { + evt_code = (BTA_ID_GROUP << 8) | evt_code; + switch (evt_code) { + case BTA_CSIP_API_OPEN_EVT: + return "BTA_CSIP_API_OPEN_EVT"; + case BTA_CSIP_API_CLOSE_EVT: + return "BTA_CSIP_API_CLOSE_EVT"; + case BTA_CSIP_GATT_OPEN_EVT: + return "BTA_CSIP_GATT_OPEN_EVT"; + case BTA_CSIP_GATT_CLOSE_EVT: + return "BTA_CSIP_GATT_CLOSE_EVT"; + case BTA_CSIP_OPEN_FAIL_EVT: + return "BTA_CSIP_OPEN_FAIL_EVT"; + case BTA_CSIP_OPEN_CMPL_EVT: + return "BTA_CSIP_OPEN_CMPL_EVT"; + case BTA_CSIP_START_ENC_EVT: + return "BTA_CSIP_START_ENC_EVT"; + case BTA_CSIP_ENC_CMPL_EVT: + return "BTA_CSIP_ENC_CMPL_EVT"; + case BTA_CSIP_API_ENABLE_EVT: + return "BTA_CSIP_API_ENABLE_EVT"; + case BTA_CSIP_API_DISABLE_EVT: + return "BTA_CSIP_API_DISABLE_EVT"; + case BTA_CSIP_SET_LOCK_VALUE_EVT: + return "BTA_CSIP_SET_LOCK_VALUE_EVT"; + default: + return "Unknown CSIP event code"; + } +} + +/******************************************************************************* + * + * Function bta_csip_state_code + * + * Description returns state name in string format + * + * Returns string representation of connection state + * + ******************************************************************************/ +static const char* bta_csip_state_code(tBTA_CSIP_STATE state) { + switch (state) { + case BTA_CSIP_IDLE_ST: + return "BTA_CSIP_IDLE_ST"; + case BTA_CSIP_W4_CONN_ST: + return "BTA_CSIP_W4_CONN_ST"; + case BTA_CSIP_W4_SEC: + return "BTA_CSIP_W4_SEC"; + case BTA_CSIP_CONN_ST: + return "BTA_CSIP_CONN_ST"; + case BTA_CSIP_DISCONNECTING_ST: + return "BTA_CSIP_DISCONNECTING_ST"; + default: + return "Incorrect State"; + } +} + diff --git a/le_audio/system/bt/bta/csip/bta_csip_utils.cc b/le_audio/system/bt/bta/csip/bta_csip_utils.cc new file mode 100644 index 00000000000..667b20c5082 --- /dev/null +++ b/le_audio/system/bt/bta/csip/bta_csip_utils.cc @@ -0,0 +1,1304 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +/****************************************************************************** + * + * This file contains the CSIP Client supporting functions + * + ******************************************************************************/ + +#include +#include +#include + +#include + +#include "bta_csip_api.h" +#include "bta_csip_int.h" +#include "bta_gatt_queue.h" + +#include "osi/include/config.h" +#include "btif/include/btif_config.h" +#include "stack/crypto_toolbox/crypto_toolbox.h" + +/* CSIS Characteristic descriptors handles */ +#define CSIP_CCCD_UUID_VAL 0x2902 +Uuid CSIP_CCCD_UUID = Uuid::From16Bit(CSIP_CCCD_UUID_VAL); + +/******************************************************************************* + * + * Function bta_csip_validate_set_params + * + * Description Validates if set id and its members are valid + * + * Returns bool. true - if details are valid otherwise false. + * + ******************************************************************************/ +bool bta_csip_validate_set_params(tBTA_SET_LOCK_PARAMS* lock_req) { + std::vector *csets = &bta_csip_cb.csets; + tBTA_CSIP_CSET cset; + bool is_valid_set = false; + + std::vector::iterator itr; + for (itr = csets->begin(); itr != csets->end(); ++itr) { + if (lock_req->set_id == itr->set_id) { + cset = *itr; + is_valid_set = true; + break; + } + } + + if (!is_valid_set) { + LOG(ERROR) << __func__ << ": Invalid Set ID = " << +lock_req->set_id; + //TODO: Give Invalid parameters callback + return (false); + } + + std::vector req_members = lock_req->members_addr; + // TODO: if requested set members size = 0, return true + if ((int)lock_req->members_addr.size() == 0) { + LOG(INFO) << __func__<< " Lock of All Set Memebers is requested"; + return (true); + } + + std::vector set_members = cset.set_members; + int members_matched = 0; + for (int i = 0; i < (int)req_members.size(); i++) { + for (int j = 0; j < (int)set_members.size(); j++) { + if (req_members[i] == set_members[j]) { + members_matched++; + break; + } + } + } + LOG(INFO) << "set members matched count = " << +members_matched; //debug + if (members_matched != (int)req_members.size()) { + LOG(ERROR) << __func__ << " Incorrect Set members provided"; + return (false); + } + + return (true); +} + +/******************************************************************************* + * + * Function bta_csip_is_valid_lock_request + * + * Description Validates lock request parameters received + * + * Returns bool. true - if details are valid otherwise false. + * + ******************************************************************************/ +bool bta_csip_is_valid_lock_request(tBTA_SET_LOCK_PARAMS* lock_req) { + // validate if correct lock value is provided + if (lock_req->lock_value != UNLOCK_VALUE && lock_req->lock_value != LOCK_VALUE) { + LOG(ERROR) << __func__ << ": Invalid Lock Value."; + return (false); + } + + // validate set id + if (!bta_csip_validate_set_params(lock_req)) { + LOG(INFO) << __func__ << " Invalid params"; + return (false); + } + + return (true); +} + +/******************************************************************************* + * + * Function bta_csip_get_cset_cb + * + * Description Finds coordinated set control block by set_id + * + * Returns tBTA_CSET_CB. NULL - if set is not found. + * + ******************************************************************************/ +tBTA_CSET_CB* bta_csip_get_cset_cb_by_id (uint8_t set_id) { + int i; + tBTA_CSET_CB* cset_cb = &bta_csip_cb.csets_cb[0]; + + for (i = 0; i < BTA_MAX_SUPPORTED_SETS; i++, cset_cb++) { + if ((cset_cb->in_use) && (cset_cb->set_id == set_id)) { + return (cset_cb); + } + } + + /* no match found */ + return (NULL); +} + +/******************************************************************************* + * + * Function bta_csip_get_cset_cb + * + * Description Creates new coordinated set control block with next available + * set id. + * + * Returns tBTA_CSET_CB. NULL - if no resources are available for set. + * + ******************************************************************************/ +tBTA_CSET_CB* bta_csip_get_cset_cb () { + int i; + tBTA_CSET_CB* cset_cb = &bta_csip_cb.csets_cb[0]; + + for (i = 0; i < BTA_MAX_SUPPORTED_SETS; i++, cset_cb++) { + if (!cset_cb->in_use) { + cset_cb->set_id = i; + cset_cb->in_use = true; + return (cset_cb); + } + } + + LOG(ERROR) << __func__ << " No resource available for Coordinated set"; + return (NULL); +} + +/******************************************************************************** + * + * Function bta_csip_is_app_reg + * + * Description Utility function to check if app_id is valid and registered. + * + * Returns true - if reistered. + * false - if invalid app id or its not registered. + * + *******************************************************************************/ +bool bta_csip_is_app_reg(uint8_t app_id) { + if (app_id >= BTA_CSIP_MAX_SUPPORTED_APPS) { + return (false); + } + + if (bta_csip_cb.app_rcb[app_id].in_use) { + return (true); + } + + return (false); +} + +/******************************************************************************** + * + * Function bta_csip_get_rcb + * + * Description Utility function to check if app_id is valid and registered. + * + * Returns registration control block. NULL if not in use. + * + *******************************************************************************/ +tBTA_CSIP_RCB* bta_csip_get_rcb (uint8_t app_id) { + if (app_id >= BTA_CSIP_MAX_SUPPORTED_APPS) { + return (NULL); + } + + if (bta_csip_cb.app_rcb[app_id].in_use) { + return (&bta_csip_cb.app_rcb[app_id]); + } + + return (NULL); +} + +/******************************************************************************* + * + * Function bta_csip_get_coordinated_set + * + * Description Creates new coordinated set control block + * + * Returns tBTA_CSIP_CSET for valid set_id. + * Empty set with INVALID_SET_ID if not found. + * + ******************************************************************************/ +tBTA_CSIP_CSET bta_csip_get_coordinated_set (uint8_t set_id) { + for (tBTA_CSIP_CSET cset: bta_csip_cb.csets) { + if (cset.set_id == set_id) { + return cset; + } + } + + LOG(ERROR) << __func__ << "Coordinated set not found for set_id: " << +set_id; + tBTA_CSIP_CSET cset = {.set_id = INVALID_SET_ID, + .size = 0 + }; + return cset; + } + +/****************************************************************************** + * + * Function bta_csip_update_set_member + * + * Description Updates set member in the given set. + * + * Returns bool (true, if added successfully. Otherwise, false.) + * + ******************************************************************************/ +bool bta_csip_update_set_member (uint8_t set_id, RawAddress addr) { + for (tBTA_CSIP_CSET& cset: bta_csip_cb.csets) { + if (cset.set_id == set_id) { + if (cset.set_members.size() == cset.size) { + LOG(ERROR) << __func__ << " All Set members already discovered."; + return false; + } + cset.set_members.push_back(addr); + return true; + } + } + + LOG(ERROR) << __func__ << "Coordinated set not found for set_id: " << +set_id; + return false; +} + +/******************************************************************************* + * + * Function bta_csip_remove_set_member + * + * Description Removes set member from given coordinated set after unpairing. + * If its the last set member in set, coordinated set is deleted. + * + * Returns void + * + ******************************************************************************/ +void bta_csip_remove_set_member (RawAddress addr) { + LOG(INFO) << __func__ << " Device = " << addr.ToString(); + bool is_device_found = false; + + btif_config_remove(addr.ToString().c_str(), "DGroup"); + tBTA_CSIP_DEV_CB* p_cb = bta_csip_find_dev_cb_by_bda(addr); + if (!p_cb) { + APPL_TRACE_DEBUG("%s: Set Member not found", __func__); + return; + } + + tBTA_CSIS_SRVC_INFO* srvc = &p_cb->csis_srvc[0]; + for (int i = 0; i < MAX_SUPPORTED_SETS_PER_DEVICE && !is_device_found; i++, srvc++) { + if (!srvc->in_use) continue; + for (tBTA_CSIP_CSET& cset: bta_csip_cb.csets) { + if (cset.set_id == srvc->set_id) { + //std::remove(cset.set_members.begin(), cset.set_members.end(), addr); + cset.set_members.erase( + std::remove_if(cset.set_members.begin(), cset.set_members.end(), + [&](RawAddress const & bdaddr) { + return bdaddr == addr; + }), + cset.set_members.end()); + is_device_found = true; + LOG(INFO) << __func__ << " Size = " << +(int)cset.set_members.size(); + if (cset.set_members.empty()) { + tBTA_CSET_CB* cset_cb = bta_csip_get_cset_cb_by_id(cset.set_id); + if (cset_cb) { + LOG(INFO) << __func__ << " Invalidating set. Last member unpaired."; + cset_cb->in_use = false; + cset_cb->set_id = INVALID_SET_ID; + cset.set_members.clear(); + bta_csip_cb.csets.erase( + std::remove_if(bta_csip_cb.csets.begin(), + bta_csip_cb.csets.end(), [&](tBTA_CSIP_CSET& cs) { + return cs.set_id == srvc->set_id; + }), + bta_csip_cb.csets.end()); + } + } + break; + } + } + } + + bta_csip_cb.dev_cb.erase( + std::remove_if(bta_csip_cb.dev_cb.begin(), bta_csip_cb.dev_cb.end(), + [&](tBTA_CSIP_DEV_CB const &dev_cb) { + return dev_cb.addr == addr; + }), + bta_csip_cb.dev_cb.end()); +} + +/******************************************************************************* + * + * Function bta_csip_get_or_create_cset + * + * Description API used to create Coordinated Set Control block. + * + * Returns void + * + ******************************************************************************/ +tBTA_CSIP_CSET* bta_csip_get_or_create_cset (uint8_t set_id, bool existing) { + /*std::find_if(bta_csip_cb.csets.begin(), bta_csip_cb.csets.end(), + [&set_id](const tBTA_CSIP_CSET& set) { + return set.set_id == set_id; + });*/ + + for (tBTA_CSIP_CSET& cset: bta_csip_cb.csets) { + if (cset.set_id == set_id) { + return &cset; + } + } + + if (existing) return NULL; + + /* Create a new set with invalid set_id*/ + tBTA_CSIP_CSET cset = {.set_id = INVALID_SET_ID, + .size = 0 + }; + bta_csip_cb.csets.push_back(cset); + return &bta_csip_cb.csets.back(); +} + +/******************************************************************************* + * + * Function bta_csip_find_set_id_by_sirk + * + * Description Finds Coordinated set control block by sirk + * + * Returns set_id if SIRK is found + * otherwise, INVALID_SET_ID + * + ******************************************************************************/ +uint8_t bta_csip_find_set_id_by_sirk (uint8_t* sirk) { + int i = 0; + tBTA_CSET_CB* csets = &bta_csip_cb.csets_cb[0]; + + for (i = 0; i < BTA_MAX_SUPPORTED_SETS; i++, csets++) { + if (csets->in_use) { + // compare SIRK's + if (!memcmp(sirk, csets->sirk, SIRK_SIZE)) { + return csets->set_id; + } + } + } + + return INVALID_SET_ID; +} + + +/******************************************************************************* + * + * Function bta_csip_get_cset_cb + * + * Description Finds coordinated set control block by set_id + * + * Returns tBTA_CSET_CB. NULL - if set is not found. + * + ******************************************************************************/ +tBTA_CSIS_SRVC_INFO* bta_csip_get_csis_instance(tBTA_CSIP_DEV_CB* dev_cb, + uint8_t set_id) { + int i = 0; + + if (!dev_cb) return NULL; + + tBTA_CSIS_SRVC_INFO* srvc = &dev_cb->csis_srvc[0]; + + for (i = 0; i < MAX_SUPPORTED_SETS_PER_DEVICE; i++, srvc++) { + srvc = &dev_cb->csis_srvc[i]; + if ((srvc->in_use) && (srvc->set_id == set_id)) { + return (srvc); + } + } + + /* no match found */ + return (NULL); +} + +/******************************************************************************* + * + * Function bta_csip_get_csis_service_cb + * + * Description Creates new coordinated set control block for a given device + * + * Returns tBTA_CSET_CB. NULL if no resources available. + * + ******************************************************************************/ +tBTA_CSIS_SRVC_INFO* bta_csip_get_csis_service_cb(tBTA_CSIP_DEV_CB* dev_cb) { + int i = 0; + tBTA_CSIS_SRVC_INFO* srvc = &dev_cb->csis_srvc[0]; + + for (i = 0; i < MAX_SUPPORTED_SETS_PER_DEVICE; i++, srvc++) { + if (!srvc->in_use) { + srvc->in_use = true; + return srvc; + } + } + + /* no match found */ + return (NULL); +} + +/******************************************************************************* + * + * Function bta_csip_is_csis_supported + * + * Description checks if remote device supports coordinated set + * + * Returns true if supported, otherwise false. + * + ******************************************************************************/ +bool bta_csip_is_csis_supported(tBTA_CSIP_DEV_CB* dev_cb) { + int i = 0; + tBTA_CSIS_SRVC_INFO* srvc = &dev_cb->csis_srvc[0]; + + for (i = 0; i < MAX_SUPPORTED_SETS_PER_DEVICE; i++, srvc++) { + if (srvc->in_use) { + return true; + } + } + + /* no csis instance found */ + return (false); +} + +/******************************************************************************* + * + * Function bta_csip_get_csis_service_by_handle + * + * Description Gives CSIS Service Control block by service handle. + * + * Returns CSIS Service control block. Null if not found. + * + ******************************************************************************/ +tBTA_CSIS_SRVC_INFO* bta_csip_get_csis_service_by_handle( + tBTA_CSIP_DEV_CB* dev_cb, uint16_t service_handle) { + int i = 0; + tBTA_CSIS_SRVC_INFO* srvc = &dev_cb->csis_srvc[0]; + + for (i = 0; i < MAX_SUPPORTED_SETS_PER_DEVICE; i++, srvc++) { + if (srvc->in_use && srvc->service_handle == service_handle) { + return srvc; + } + } + + /* no match found */ + return (NULL); +} + +/******************************************************************************* + * + * Function bta_csip_find_csis_srvc_by_lock_handle + * + * Description Gives CSIS Service Control block by lock handle. + * + * Returns CSIS Service control block. Null if not found. + * + ******************************************************************************/ +tBTA_CSIS_SRVC_INFO* bta_csip_find_csis_srvc_by_lock_handle( + tBTA_CSIP_DEV_CB* dev_cb, uint16_t lock_handle) { + int i = 0; + tBTA_CSIS_SRVC_INFO* srvc = &dev_cb->csis_srvc[0]; + + for (i = 0; i < MAX_SUPPORTED_SETS_PER_DEVICE; i++, srvc++) { + if (srvc->in_use && srvc->lock_handle == lock_handle) { + return srvc; + } + } + + /* no match found */ + return (NULL); + +} + +/******************************************************************************* + * + * Function bta_csip_is_locked_by_other_apps + * + * Description Checks if set is locked by app other than mentioned one. + * + * Returns true, if locked by other app otherwise false. + * + ******************************************************************************/ +bool bta_csip_is_locked_by_other_apps(tBTA_CSIS_SRVC_INFO* srvc, uint8_t app_id) { + std::vector &lock_applist = srvc->lock_applist; + + for (auto& it : lock_applist) { + if (it != app_id) { + return (true); + } + } + + return (false); +} + +/******************************************************************************* + * + * Function bta_csip_form_set_lock_order + * + * Description Forms order of set members as per rank. + * + * Returns Ordered Set members. + * + ******************************************************************************/ +std::vector bta_csip_form_set_lock_order(tBTA_CSET_CB* cset_cb) { + std::vector ordered_members; + std::vector req_members = cset_cb->cur_lock_req.members_addr; + std::map lock_order_map; + + for (int i = 0; i < (int)req_members.size(); i++) { + // get device control block and corresponding csis service details + tBTA_CSIP_DEV_CB* dev_cb = bta_csip_find_dev_cb_by_bda(req_members[i]); + // null checks required + tBTA_CSIS_SRVC_INFO* srvc = + bta_csip_get_csis_instance(dev_cb, cset_cb->cur_lock_req.set_id); + if (srvc) { + lock_order_map.insert({srvc->rank, req_members[i]}); + } + } + + for (auto itr: lock_order_map) { + ordered_members.push_back(itr.second); + } + + return ordered_members; +} + +/******************************************************************************* + * + * Function bta_csip_arrange_set_members_by_order + * + * Description Forms order of set members for LOCK/UNLOCK request. + * + * Returns Ordered set members in vector. + * + ******************************************************************************/ +std::vector bta_csip_arrange_set_members_by_order( + uint8_t set_id, std::vector& req_sm, bool ascending) { + LOG(INFO) << __func__; + + std::vector ordered_req_sm; + std::vector set_members = + bta_csip_get_set_member_by_order(set_id, ascending); + + // Check if all set members are requested + if ((uint8_t)req_sm.size() == 0) { + LOG(INFO) << __func__ << " original size = " << +set_members.size(); + return set_members; + } + + /* LOCK Request Order*/ + for (int i = 0; i < (int)set_members.size(); i++) { + for (int j = 0; j < (int)req_sm.size(); j++) { + if (set_members[i] == req_sm[j]) { + ordered_req_sm.push_back(set_members[i]); + if (ordered_req_sm.size() == req_sm.size()) { + return ordered_req_sm; + } + } + } + } + + return {}; +} + +/******************************************************************************* + * + * Function bta_csip_arrange_set_members_by_order + * + * Description Forms order of set members for LOCK/UNLOCK request. + * + * Returns Ordered set members in vector. + * + ******************************************************************************/ +std::vector bta_csip_get_set_member_by_order(uint8_t set_id, + bool ascending) { + std::vector ordered_members; + + tBTA_CSET_CB* cset_cb = &bta_csip_cb.csets_cb[set_id]; + if (!cset_cb->in_use) { + LOG(ERROR) << __func__ << " Invalid Set for for Set ID: " << +set_id; + return {}; + } + + if (ascending) { + for (auto itr: cset_cb->ordered_members) { + ordered_members.push_back(itr.second); + } + } else { + for (auto i = cset_cb->ordered_members.rbegin(); + i != cset_cb->ordered_members.rend(); ++i) { + ordered_members.push_back(i->second); + } + } + + return ordered_members; +} + + +/******************************************************************************* + * + * Function bta_csip_is_member_locked_by_app + * + * Description checks if application (app_id) has locked given set. + * + * Returns void. + ******************************************************************************/ +bool bta_csip_is_member_locked_by_app (uint8_t app_id, tBTA_CSIS_SRVC_INFO* srvc) { + std::vector& lock_applist = srvc->lock_applist; + + auto it = std::find(lock_applist.begin(), lock_applist.end(), app_id); + if (it != lock_applist.end()) { + LOG(INFO) << __func__ << " App Id found in app list"; + return true; + } + + LOG(INFO) << __func__ << " App Id not found in app list"; + return false; +} + +/******************************************************************************* + * + * Function bta_csip_handle_unresponsive_sm_res + * + * Description sends lock response to earlier requesting app. + * + * Returns void. + ******************************************************************************/ +void bta_csip_handle_unresponsive_sm_res(tBTA_CSIS_SRVC_INFO* srvc, + tGATT_STATUS status) { + LOG(INFO) << __func__ << " Response from unresponsive remote " << srvc->bd_addr.ToString() + << " status: " << +status; + std::vector& unres_applist = srvc->unrsp_applist; + + if (status == GATT_SUCCESS) { + srvc->lock = LOCK_VALUE; + for (auto& it : unres_applist) { + LOG(INFO) << __func__ << " Sending GATT_SUCCESS callback to app: " << +it; + std::vector sm = {srvc->bd_addr}; + tBTA_LOCK_STATUS_CHANGED res = {.app_id = it, .set_id = srvc->set_id, + .value = 0x02, .addr = sm}; + bta_csip_send_lock_req_cmpl_cb(res); + // Add app id to the lock applist + srvc->lock_applist.push_back(it); + } + } + + unres_applist.clear(); +} + +/******************************************************************************* + * + * Function bta_csip_get_next_lock_request + * + * Description Schedules next pending lock request for the given set. + * + * Returns void. + ******************************************************************************/ +void bta_csip_get_next_lock_request(tBTA_CSET_CB* cset_cb) { + tBTA_SET_LOCK_PARAMS lock_req_params; + std::queue& lock_req_queue = cset_cb->lock_req_queue; + + if (lock_req_queue.empty()) { + LOG(INFO) << " No pending Lock Request for Set: " << +cset_cb->set_id; + cset_cb->request_in_progress = false; + return; + } + + lock_req_params = lock_req_queue.front(); + lock_req_queue.pop(); + + bta_csip_form_lock_request(lock_req_params, cset_cb); +} + +/******************************************************************************* + * + * Function bta_csip_find_dev_cb_by_bda + * + * Description Utility function find a device control block by BD address. + * + * Returns tBTA_CSIP_DEV_CB - device control block for a given remote. + * nullptr, if device control block is not present. + ******************************************************************************/ +tBTA_CSIP_DEV_CB* bta_csip_find_dev_cb_by_bda(const RawAddress& bda) { + /*auto iter = std::find_if(bta_csip_cb.dev_cb.begin(), bta_csip_cb.dev_cb.end(), + [&bda](const tBTA_CSIP_DEV_CB& device) { + return device.addr == bda; + }); + + return (iter == bta_csip_cb.dev_cb.end()) ? nullptr : &(*iter);*/ + for (tBTA_CSIP_DEV_CB& p_cb: bta_csip_cb.dev_cb) { + if (p_cb.addr == bda) { + return &p_cb; + } + } + + return NULL; +} + +/******************************************************************************* + * + * Function bta_csip_get_dev_cb_by_cid + * + * Description Utility function find a device control block by gatt conn id. + * + * Returns tBTA_CSIP_DEV_CB (device control block for a given remote.) + ******************************************************************************/ +tBTA_CSIP_DEV_CB* bta_csip_get_dev_cb_by_cid(uint16_t conn_id) { + auto iter = std::find_if(bta_csip_cb.dev_cb.begin(), bta_csip_cb.dev_cb.end(), + [&conn_id](const tBTA_CSIP_DEV_CB& device) { + return device.conn_id == conn_id; + }); + + return (iter == bta_csip_cb.dev_cb.end()) ? nullptr : &(*iter); +} + +/******************************************************************************* + * + * Function bta_csip_create_dev_cb_for_bda + * + * Description Utility function find a device control block by BD address. + * + * Returns tBTA_CSIP_DEV_CB (device control block for a given remote. + ******************************************************************************/ +tBTA_CSIP_DEV_CB* bta_csip_create_dev_cb_for_bda(const RawAddress& bda) { + tBTA_CSIP_DEV_CB p_dev_cb = {}; + p_dev_cb.addr = bda; + p_dev_cb.in_use = true; + + bta_csip_cb.dev_cb.push_back(p_dev_cb); + + return &bta_csip_cb.dev_cb.back(); +} + +/************************************************************************************ + * + * Function bta_csip_get_cccd_handle + * + * Description Utility function to fetch cccd handle of a given characteristic. + * + * Returns handle of cccd. 0 if not found. + ************************************************************************************/ +uint16_t bta_csip_get_cccd_handle(uint16_t conn_id, uint16_t char_handle) { + const gatt::Characteristic* p_char = + BTA_GATTC_GetCharacteristic(conn_id, char_handle); + if (!p_char) { + LOG(WARNING) << __func__ << ": Characteristic not found: " << char_handle; + return 0; + } + + for (const gatt::Descriptor& desc : p_char->descriptors) { + if (desc.uuid == CSIP_CCCD_UUID) { + LOG(INFO) << __func__ << " desc handle = " << +desc.handle; + return desc.handle; + } + } + + return 0; +} + +/************************************************************************************ + * + * Function bta_csip_add_app_to_applist + * + * Description Utility function adds app to connection applist of a + * given device control block. + * + * Returns void + ************************************************************************************/ +void bta_csip_add_app_to_applist(tBTA_CSIP_DEV_CB* p_cb, uint8_t app_id) { + if (p_cb && !bta_csip_is_app_from_applist(p_cb, app_id)) { + LOG(INFO) << __func__ << ": adding app(" << +app_id + << ") to connection applist of " << p_cb->addr; + p_cb->conn_applist.push_back(app_id); + } +} + +/************************************************************************************ + * + * Function bta_csip_is_app_from_applist + * + * Description Utility function checks if app is from connection applist of a + * given device control block. + * + * Returns true if app has already sent connect request for CSIP. + ************************************************************************************/ +bool bta_csip_is_app_from_applist(tBTA_CSIP_DEV_CB* p_cb, uint8_t app_id) { + for (auto i: p_cb->conn_applist) { + if (i == app_id) { + return (true); + } + } + + return (false); +} + +/************************************************************************************ + * + * Function bta_csip_remove_app_from_conn_list + * + * Description Utility function to remove application from connection applist of + * given device control block + * + * Returns void + ************************************************************************************/ +void bta_csip_remove_app_from_conn_list(tBTA_CSIP_DEV_CB* p_cb, uint8_t app_id) { + p_cb->conn_applist.erase( + std::remove(p_cb->conn_applist.begin(), p_cb->conn_applist.end(), app_id), + p_cb->conn_applist.end()); +} + +/************************************************************************************ + * + * Function bta_csip_send_conn_state_changed_cb + * + * Description Utility function to send connection state changed to all + * registered application in connection app list. + * + * Returns void + ************************************************************************************/ +void bta_csip_send_conn_state_changed_cb(tBTA_CSIP_DEV_CB* p_cb, + uint8_t state, uint8_t status) { + if (!p_cb) { + LOG(ERROR) << __func__ << ": Device CB for " << p_cb->addr << " not found"; + return; + } + + // send connection state change to all apps in conn_applist + for (auto i: p_cb->conn_applist) { + tBTA_CSIP_CONN_STATE_CHANGED conn_cb_params = { + .app_id = i, + .addr = p_cb->addr, + .state = state, + .status =status + }; + if (bta_csip_cb.app_rcb[i].p_cback) { + (*bta_csip_cb.app_rcb[i].p_cback) + (BTA_CSIP_CONN_STATE_CHG_EVT, (tBTA_CSIP_DATA *)&conn_cb_params); + } + } +} + +/************************************************************************************ + * + * Function bta_csip_send_conn_state_changed_cb + * + * Description Utility function to send connection state changed to requesting + * registered application from connection applist. + * + * Returns void + ************************************************************************************/ +void bta_csip_send_conn_state_changed_cb (tBTA_CSIP_DEV_CB* p_cb, uint8_t app_id, + uint8_t state, uint8_t status) { + + tBTA_CSIP_CONN_STATE_CHANGED conn_cb_params = { + .app_id = app_id, + .addr = p_cb->addr, + .state = state, + .status =status + }; + + // send connection state change to the requested App + if (bta_csip_cb.app_rcb[app_id].p_cback) { + (*bta_csip_cb.app_rcb[app_id].p_cback) + (BTA_CSIP_CONN_STATE_CHG_EVT, (tBTA_CSIP_DATA *)&conn_cb_params); + } + +} + +/************************************************************************************ + * + * Function bta_csip_process_completed_lock_req + * + * Description Utility function to send lock state changed to requesting + * registered application. + * + * Returns void + ************************************************************************************/ +void bta_csip_send_lock_req_cmpl_cb (tBTA_LOCK_STATUS_CHANGED response) { + if (response.app_id >= BTA_CSIP_MAX_SUPPORTED_APPS || + !bta_csip_cb.app_rcb[response.app_id].in_use) { + LOG(ERROR) << __func__ << "Invalid or unregistered application: " << +response.app_id; + return; + } + + if (bta_csip_cb.app_rcb[response.app_id].p_cback) { + (*bta_csip_cb.app_rcb[response.app_id].p_cback) + (BTA_CSIP_LOCK_STATUS_CHANGED_EVT, (tBTA_CSIP_DATA *)&response); + } +} + +/************************************************************************************ + * + * Function bta_csip_write_cccd + * + * Description API used to write required characteristic descriptor. + * + * Returns void + ************************************************************************************/ +void bta_csip_write_cccd (tBTA_CSIP_DEV_CB* p_cb, uint16_t char_handle, + uint16_t cccd_handle) { + LOG(INFO) << __func__; + // Register for LOCK + if (BTA_GATTC_RegisterForNotifications( + bta_csip_cb.gatt_if, p_cb->addr, char_handle)) { + LOG(ERROR) << __func__ + << " Notification Registration failed for char handle: " << +char_handle; + return; + } + + LOG(INFO) << __func__ << " notification registration successful. handle: " << +char_handle; + std::vector value(2); + uint8_t* ptr = value.data(); + UINT16_TO_STREAM(ptr, GATT_CHAR_CLIENT_CONFIG_NOTIFICATION); + BtaGattQueue::WriteDescriptor(p_cb->conn_id, cccd_handle, value, + GATT_WRITE, nullptr, nullptr); +} + +/************************************************************************************ + * + * Function bta_csip_load_coordinated_sets_from_storage + * + * Description API used to load coordinated sets from storage on BT ON. + * + * Returns void + ************************************************************************************/ +void bta_csip_load_coordinated_sets_from_storage () { + LOG(INFO) << __func__; + + static const char* CONFIG_FILE_PATH = "/data/misc/bluedroid/bt_config.conf"; + config_t* config = config_new(CONFIG_FILE_PATH); + if (!config) { + LOG(INFO) << __func__ << " file "<< CONFIG_FILE_PATH << " not found"; + return; + } + + const config_section_node_t* snode = config_section_begin(config); + while (snode != config_section_end(config)) { + + const char* name = config_section_name(snode); + if (!RawAddress::IsValidAddress(name)) { + snode = config_section_next(snode); + continue; + } + + const char* key = "DGroup"; + const char* coordinated_sets = config_get_string(config, name, key, ""); + if (!strcmp(coordinated_sets, "")) { + LOG(INFO) << __func__ << " doesnt support cooedinated set."; + snode = config_section_next(snode); + continue; + } + + RawAddress bdaddr; + RawAddress::FromString(name, bdaddr); + tBTA_CSIP_DEV_CB* dev_cb = bta_csip_find_dev_cb_by_bda(bdaddr); + if (!dev_cb) { + dev_cb = bta_csip_create_dev_cb_for_bda(bdaddr); + } + + char *next = NULL; + char *csets = strdup(coordinated_sets); + /* Set Level parsing*/ + char *set_details = strtok_r(csets, " ", &next); + + do { + tBTA_CSIP_CSET *cset = NULL; + uint8_t set_id = INVALID_SET_ID, size = 0, rank = 0; + uint16_t srvc_handle = 0; + bluetooth::Uuid uuid; + uint8_t sirk[SIRK_SIZE] = {}; + bool lock_support = false; + + char *part; + char *posn; + /* separating properties of set*/ + part = strtok_r(set_details, "~", &posn); + + while (part != NULL) + { + char *ptr = NULL; + /* Decode property type and its value*/ + char *prop_details = strtok_r(part, ":", &ptr); + + if (prop_details != NULL) { + char* prop_val = strtok_r(NULL, ":", &ptr); + if (prop_val) { + if (!strcmp(prop_details, "SET_ID")) { + set_id = (uint8_t)atoi(prop_val); + cset = bta_csip_get_or_create_cset(set_id, true); + if (!cset) LOG(INFO) << __func__ << " got cset empty"; + else LOG(INFO) << "valid " << +cset->set_id; + } else if (!strcmp(prop_details, "SIZE")) { + size = (uint8_t)atoi(prop_val); + } else if (!strcmp(prop_details, "SIRK")) { + hex_string_to_byte_arr(prop_val, sirk, SIRK_SIZE * 2); + } else if (!strcmp(prop_details, "INCLUDING_SRVC")) { + uuid = Uuid::FromString(prop_val); + } else if (!strcmp(prop_details, "LOCK_SUPPORT")) { + if (!strcmp(prop_val, "true")) lock_support = true; + } else if (!strcmp(prop_details, "RANK")) { + rank = (uint8_t)atoi(prop_val); + } else if (!strcmp(prop_details, "SRVC_HANDLE")) { + srvc_handle = (uint16_t)atoi(prop_val); + } + } + } + + part = strtok_r(NULL, "~", &posn); + } + + if (set_id < BTA_MAX_SUPPORTED_SETS) { + if (!cset) { + // Create new coordinated set insatnce and device to it + cset = bta_csip_get_or_create_cset(set_id, false); + cset->set_id = set_id; + cset->size = size; + cset->p_srvc_uuid = uuid; + cset->total_discovered = 1; + cset->set_members.push_back(bdaddr); + + // create coordinated set control block + tBTA_CSET_CB* cset_cb = &bta_csip_cb.csets_cb[set_id]; + cset_cb->set_id = set_id; + cset_cb->in_use = true; + memcpy(cset_cb->sirk, sirk, SIRK_SIZE); + if (rank != 0) { + cset_cb->ordered_members.insert({rank, bdaddr}); + } + + } else { + //LOG(INFO) << "Existing set = " << +cset->set_id; + cset->total_discovered++; + cset->set_members.push_back(bdaddr); + + tBTA_CSET_CB* cset_cb = &bta_csip_cb.csets_cb[set_id]; + if (rank != 0) { + cset_cb->ordered_members.insert({rank, bdaddr}); + } + } + + // assign service properties - set_id and bd_addr + tBTA_CSIS_SRVC_INFO *srvc = bta_csip_get_csis_service_cb(dev_cb); + if (srvc) { + srvc->set_id = set_id; + srvc->bd_addr = bdaddr; + srvc->size = size; + srvc->rank = rank; + srvc->service_handle = srvc_handle; + memcpy(srvc->sirk, sirk, SIRK_SIZE); + } + } + } while ((set_details = strtok_r(NULL, " ", &next)) != NULL); + + snode = config_section_next(snode); + } + + /*LOG(INFO) << "------------------DEBUG----------------------------"; + LOG(INFO) << "printing all loaded coordinated sets"; + for (int i = 0; i < (int)bta_csip_cb.csets.size(); i++) { + tBTA_CSIP_CSET set = bta_csip_cb.csets[i]; + LOG(INFO) << " Set ID = " << +set.set_id + << " Size = " << +set.size + << " total discovered = " << +set.total_discovered; + for (int j = 0; j < (int)set.set_members.size(); j++) { + LOG(INFO) << " Member (" << +(j+1) <<") = " << set.set_members[j].ToString(); + } + }*/ +} + +/************************************************************************************ + * + * Function bta_csip_preserve_cset + * + * Description function used to preserve coordinated set details to storage. + * + * Returns void + ************************************************************************************/ +void bta_csip_preserve_cset (tBTA_CSIS_SRVC_INFO* srvc) { + tBTA_CSIP_DEV_CB* p_cb = bta_csip_find_dev_cb_by_bda(srvc->bd_addr); + + if (!p_cb) { + LOG(ERROR) << " Device cb record not found for " << srvc->bd_addr; + return; + } + + std::string& set_info = p_cb->set_info; + if (set_info.size() > 0) { + set_info += " "; + } + + set_info += "SET_ID:" + std::to_string(srvc->set_id); + + if (srvc->size_handle) { + set_info += "~SIZE:" + std::to_string(srvc->size); + } + + char sirk[SIRK_SIZE * 2 + 1] = {0}; + byte_arr_to_hex_string(srvc->sirk, sirk, SIRK_SIZE); + set_info += "~SIRK:" + std::string(sirk); + + if (!srvc->including_srvc_uuid.IsEmpty()) { + set_info += "~INCLUDING_SRVC:" + srvc->including_srvc_uuid.ToString(); + } + + if (srvc->lock_handle) { + set_info += "~LOCK_SUPPORT:" + std::string((srvc->lock_handle ? "true" : "false")); + } + + if (srvc->rank_handle) { + set_info += "~RANK:" + std::to_string(srvc->rank); + } + + set_info += "~SRVC_HANDLE:" + std::to_string(srvc->service_handle); + + LOG(INFO) << __func__ << " " << set_info; + + btif_config_set_str(p_cb->addr.ToString().c_str(), + "DGroup", set_info.c_str()); +} + +/************************************************************************************ + * + * Function bta_csip_get_salt + * + * Description function s1: Used to compute SALT. + * + * Returns Octet16 (cipher - SALT) + ************************************************************************************/ +Octet16 bta_csip_get_salt() { + Octet16 salt = {}; + Octet16 zero = {}; + uint8_t SIRKenc[] = {0x53, 0x49, 0x52, 0x4B, 0x65, 0x6E, 0x63}; + + salt = bta_csip_get_aes_cmac_result(zero, SIRKenc, 7); + + return salt; +} + +/************************************************************************************ + * + * Function bta_csip_compute_T + * + * Description First step of k1 function. Used to compute T from SALT and K. + * + * Returns Octet16 (cipher - T) + ************************************************************************************/ +Octet16 bta_csip_compute_T(Octet16 salt, Octet16 K) { + Octet16 T = {}; + + T = bta_csip_get_aes_cmac_result(salt, K); + + return T; +} + +/************************************************************************************ + * + * Function bta_csip_get_aes_cmac_result + * + * Description Second step of k1 function. Used to compute k1 from T and "csis". + * + * Returns Octet16 (cipher - k1) + ************************************************************************************/ +Octet16 bta_csip_compute_k1(Octet16 T) { + Octet16 k1 = {}; + uint8_t csis[] = {0x63,0x73,0x69,0x73}; + + k1 = bta_csip_get_aes_cmac_result(T, csis, 4); + + return k1; +} + +/************************************************************************************ + * + * Function bta_csip_get_aes_cmac_result + * + * Description sdf function. Used to compute SIRK from encrypted SIRK and k1. + * + * Returns Octet16 (SIRK) + ************************************************************************************/ +void bta_csip_get_decrypted_sirk(Octet16 k1, uint8_t *enc_sirk, uint8_t *sirk) { + for (int i = 0; i < 16; i++) { + sirk[i] = k1[i] ^ enc_sirk[i]; + } +} + +/************************************************************************************ + * + * Function bta_csip_get_aes_cmac_result + * + * Description Used to get aes-cmac result (for 16 byte input and output + * in LSB->MSB order) + * + * Returns Octet16 (cipher) + ************************************************************************************/ +Octet16 bta_csip_get_aes_cmac_result(const Octet16& key, const Octet16& message) { + Octet16 r_key, r_message, r_result; + + // reverse inputs as required by crypto_toolbox::aes_cmac + std::reverse_copy(key.begin(), key.end(), r_key.begin()); + std::reverse_copy(message.begin(), message.end(), r_message.begin()); + + Octet16 result = crypto_toolbox::aes_cmac(r_key, r_message.data(), r_message.size()); + + // reverse the result to get LSB->MSB order + std::reverse_copy(result.begin(), result.end(), r_result.begin()); + + return r_result; +} + +/************************************************************************************ + * + * Function bta_csip_get_aes_cmac_result + * + * Description Used to get aes-cmac result (for variable input and output + * in LSB->MSB order) + * + * Returns Octet16 (cipher) + ************************************************************************************/ +Octet16 bta_csip_get_aes_cmac_result(const Octet16& key, const uint8_t* input, + uint16_t length) { + Octet16 r_key, r_result; + + // reverse inputs as required by crypto_toolbox::aes_cmac + std::reverse_copy(key.begin(), key.end(), r_key.begin()); + uint8_t *input_buf = (uint8_t *)osi_malloc(length); + for (int i = 0; i < length; i++) { + input_buf[i] = input[length - 1 - i]; + } + + Octet16 result = crypto_toolbox::aes_cmac(r_key, input_buf, length); + + // reverse the result to get LSB->MSB order + std::reverse_copy(result.begin(), result.end(), r_result.begin()); + + return r_result; +} + +/************************************************************************************ + * + * Function byte_arr_to_hex_string + * + * Description function used to get hex representation in string format for byte[]. + * + * Returns void + ************************************************************************************/ +void byte_arr_to_hex_string(uint8_t* byte_arr, char* str, uint8_t len) { + int i; + LOG(INFO) << __func__ << " Convert byte array to hex format string"; + + for (i = 0; i < len; i++) + { + snprintf(str + (i * 2), (len * 2 + 1), "%02X", byte_arr[i]); + } +} + +/************************************************************************************ + * + * Function hex_string_to_byte_arr + * + * Description function used to get byte array from hex format string. + * + * Returns void + ************************************************************************************/ +void hex_string_to_byte_arr(char *str, uint8_t* byte_arr, uint8_t len) { + for (int length = 0; *str; str += 2, length++) + sscanf(str, "%02hhx", &byte_arr[length]); +} + +/************************************************************************************ + * + * Function is_key_empty + * + * Description function used to check if key is 0 intialized. + * + * Returns true, if all elements are 0. Otherwise, false. + ************************************************************************************/ +bool is_key_empty(Octet16& key) { + for (unsigned int i = 0; i < key.size(); i++) { + if (key[i] != 0) return false; + } + return true; +} diff --git a/le_audio/system/bt/bta/dm/bta_dm_adv_audio.cc b/le_audio/system/bt/bta/dm/bta_dm_adv_audio.cc new file mode 100644 index 00000000000..c9eb3fa6959 --- /dev/null +++ b/le_audio/system/bt/bta/dm/bta_dm_adv_audio.cc @@ -0,0 +1,1172 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +#define LOG_TAG "bt_bta_dm_adv_audio" + +#include +#include +#include +#include + +#include "bt_common.h" +#include "bt_target.h" +#include "bt_types.h" +#include "bta_api.h" +#include "bta_dm_api.h" +#include "bta_dm_co.h" +#include "bta/dm/bta_dm_int.h" +#include "bta_csip_api.h" +#include "bta_sys.h" +#include "btif/include/btif_storage.h" +#include "btm_api.h" +#include "btm_int.h" +#include "btu.h" +#include "gap_api.h" /* For GAP_BleReadPeerPrefConnParams */ +#include "l2c_api.h" +#include "osi/include/log.h" +#include "osi/include/osi.h" +#include "sdp_api.h" +#include "bta_sdp_api.h" +#include "stack/gatt/connection_manager.h" +#include "stack/include/gatt_api.h" +#include "utl.h" +#include "device/include/interop_config.h" +#include "device/include/profile_config.h" +#include "device/include/interop.h" +#include "stack/sdp/sdpint.h" +#include +#include "btif/include/btif_config.h" +#include "device/include/device_iot_config.h" +#include +#include +#include "bta_gatt_queue.h" +#include "bta_dm_adv_audio.h" +#include "btif/include/btif_dm_adv_audio.h" + +#if (GAP_INCLUDED == TRUE) +#include "gap_api.h" +#endif + +using bluetooth::Uuid; + +#define ADV_AUDIO_VOICE_ROLE_BIT 2 +#define ADV_AUDIO_MEDIA_ROLE_BIT 8 +#define CONN_LESS_MEDIA_SINK_ROLE_BIT 32 +#define CONN_LESS_ASSIST_ROLE_BIT 64 +#define CONN_LESS_DELEGATE_ROLE_BIT 128 +#define PACS_CT_SUPPORT_VALUE 2 +#define PACS_UMR_SUPPORT_VALUE 4 +#define PACS_CONVERSATIONAL_ROLE_VALUE 2 +#define PACS_MEDIA_ROLE_VALUE 4 + +#define BTA_DM_ADV_AUDIO_GATT_CLOSE_DELAY_TOUT 5000 + +Uuid UUID_SERVCLASS_WMCP = Uuid::FromString + ("2587db3c-ce70-4fc9-935f-777ab4188fd7"); + +std::vector uuid_srv_disc_search; +tBTA_LE_AUDIO_DEV_CB bta_le_audio_dev_cb; +tBTA_LEA_PAIRING_DB bta_lea_pairing_cb; +extern void bta_dm_proc_open_evt(tBTA_GATTC_OPEN* p_data); +bool is_adv_audio_unicast_supported(RawAddress rem_bda, int conn_id); + + +/*************************************************************************** + * + * Function bta_get_lea_ctrl_cb + * + * Description Gets the control block of LE audio device + * + * Parameters: tBTA_LE_AUDIO_DEV_INFO* + * + ****************************************************************************/ + +tBTA_LE_AUDIO_DEV_INFO* bta_get_lea_ctrl_cb(RawAddress peer_addr) { + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = NULL; + p_lea_cb = &bta_le_audio_dev_cb.bta_lea_dev_info[0]; + + for (int i = 0; i < MAX_LEA_DEVICES ; i++) { + if (p_lea_cb[i].in_use && + (p_lea_cb[i].peer_address == peer_addr)) { + APPL_TRACE_DEBUG(" %s Control block Found for addr %s", + __func__, peer_addr.ToString().c_str()); + return &p_lea_cb[i]; + } + } + APPL_TRACE_DEBUG(" %s Control block Not Found for addr %s", + __func__, peer_addr.ToString().c_str()); + return NULL; +} + +/* Callback received when remote device Coordinated Sets SIRK is read */ +void bta_gap_gatt_read_cb(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = + bta_get_lea_ctrl_cb(bta_le_audio_dev_cb.gatt_op_addr); + uint32_t role = 0; + uint8_t *p_val = value; + + STREAM_TO_ARRAY(&role, p_val, len); + + if (p_lea_cb) { + APPL_TRACE_DEBUG("%s Addr %s ", __func__, + p_lea_cb->peer_address.ToString().c_str()); + if (status == GATT_SUCCESS) { + if (p_lea_cb->t_role_handle == handle) { + LOG(INFO) << __func__ << " Role derived by T_ADV_AUDIO " + << +role; + if (role != 0) { + if (role & ADV_AUDIO_VOICE_ROLE_BIT) + p_lea_cb->uuids.push_back(Uuid::From16Bit + (UUID_SERVCLASS_T_ADV_AUDIO_VOICE)); + if (role & ADV_AUDIO_MEDIA_ROLE_BIT) + p_lea_cb->uuids.push_back(Uuid::From16Bit + (UUID_SERVCLASS_T_ADV_AUDIO_MEDIA_SINK)); + if (role & CONN_LESS_MEDIA_SINK_ROLE_BIT) + p_lea_cb->uuids.push_back(Uuid::From16Bit + (UUID_SERVCLASS_T_ADV_AUDIO_CONN_LESS_MEDIA_SINK)); + if (role & CONN_LESS_ASSIST_ROLE_BIT) + p_lea_cb->uuids.push_back(Uuid::From16Bit + (UUID_SERVCLASS_T_ADV_AUDIO_ASSIST)); + if (role & CONN_LESS_DELEGATE_ROLE_BIT) + p_lea_cb->uuids.push_back(Uuid::From16Bit + (UUID_SERVCLASS_T_ADV_AUDIO_DELEGATE)); + } + p_lea_cb->disc_progress--; + } else if(handle == p_lea_cb->pacs_char_handle) { + LOG(INFO) << __func__ << " derived by PACS " << +role; + if (role == 0) { + LOG(INFO) << __func__ << " Invalid Information "; + } else { + if (is_adv_audio_unicast_supported(bta_le_audio_dev_cb.gatt_op_addr, conn_id)) { + LOG(INFO) << __func__ << " ASCS Supported by the remote "; + if ((role & PACS_CONVERSATIONAL_ROLE_VALUE) == PACS_CT_SUPPORT_VALUE) + p_lea_cb->uuids.push_back(Uuid::From16Bit(UUID_SERVCLASS_PACS_CT_SUPPORT)); + if ((role & PACS_MEDIA_ROLE_VALUE) == PACS_UMR_SUPPORT_VALUE) + p_lea_cb->uuids.push_back(Uuid::From16Bit(UUID_SERVCLASS_PACS_UMR_SUPPORT)); + } + //TODO LEA_DBG Call API which will be provided by BAP + } + p_lea_cb->disc_progress--; + } else { + LOG(INFO) << __func__ << " Invalid Handle for LE AUDIO"; + } + } else { + p_lea_cb->disc_progress--; + LOG(INFO) << __func__ << " GATT READ FAILED" ; + } + + if (p_lea_cb->disc_progress <= 0) { + bta_dm_lea_disc_complete(p_lea_cb->peer_address); + } + } else { + LOG(INFO) << __func__ << " INVALID CONTROL BLOCK" ; + } + +} + + +/******************************************************************************* + * + * Function bta_get_adv_audio_role + * + * Description This API gets role for LE Audio Device after all services + * discovered + * + * Parameters: none + * + ******************************************************************************/ +void bta_get_adv_audio_role(RawAddress peer_address, uint16_t conn_id, + tGATT_STATUS status) { + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = bta_get_lea_ctrl_cb(peer_address); + + bta_le_audio_dev_cb.gatt_op_addr = peer_address; + + if (p_lea_cb == NULL) { + APPL_TRACE_ERROR(" %s Control block didnt find for peer address %s", __func__, + peer_address.ToString().c_str()); + return; + } + + // Fetch remote device gatt services from database + const std::vector* services = BTA_GATTC_GetServices(conn_id); + + if (services) { + APPL_TRACE_DEBUG(" bta_get_adv_audio_role SIZE %d addr %s conn_id %d", + (*services).size(),bta_le_audio_dev_cb.gatt_op_addr.ToString().c_str(), + conn_id); + + // Search for CSIS service in the database + for (const gatt::Service& service : *services) { + APPL_TRACE_DEBUG("%s: SERVICES IN REMOTE DEVICE %s ", __func__, + service.uuid.ToString().c_str()) + if (is_le_audio_service(service.uuid)) { + size_t len = service.uuid.GetShortestRepresentationSize(); + uint16_t uuid_val = 0; + if (len == Uuid::kNumBytes16) { + uuid_val = service.uuid.As16Bit(); + } else if(len == Uuid::kNumBytes128) { + if (service.uuid == UUID_SERVCLASS_WMCP) { + APPL_TRACE_DEBUG("%s: WMCP Service UUId found", __func__); + std::vector::iterator itr; + itr = std::find(p_lea_cb->uuids.begin(), p_lea_cb->uuids.end(), + UUID_SERVCLASS_WMCP); + if (itr == p_lea_cb->uuids.end()) { + p_lea_cb->uuids.push_back(UUID_SERVCLASS_WMCP); + } + } + } + + switch (uuid_val) { + case UUID_SERVCLASS_CSIS: + { + APPL_TRACE_DEBUG("%s:CSIS service found Uuid: %s ", __func__, + service.uuid.ToString().c_str()); + + p_lea_cb->is_csip_support = true; + bta_dm_csis_disc_complete(bta_dm_search_cb.peer_bdaddr, false); + // Get Characteristic and CCCD handle + for (const gatt::Characteristic& charac : service.characteristics) { + Uuid lock_uuid = charac.uuid; + if (lock_uuid.As16Bit() == UUID_SERVCLASS_CSIS_LOCK) { + APPL_TRACE_DEBUG("%s: CSIS rank found Uuid: %s ", __func__, + lock_uuid.ToString().c_str()); + if (p_lea_cb != NULL) { + Uuid csip_lock_uuid = Uuid::FromString("6AD8"); + std::vector::iterator itr; + itr = std::find(p_lea_cb->uuids.begin(), p_lea_cb->uuids.end(), + csip_lock_uuid); + if (itr == p_lea_cb->uuids.end()) { + p_lea_cb->uuids.push_back(csip_lock_uuid); + } + } else { + APPL_TRACE_DEBUG(" %s No Control Block", __func__); + } + } + } + } + break; + case UUID_SERVCLASS_T_ADV_AUDIO: + { + if (!p_lea_cb->is_has_found) { + APPL_TRACE_DEBUG("%s: T_ADV_AUDIO service found Uuid: %s ", __func__, + service.uuid.ToString().c_str()); + std::vector::iterator itr; + itr = std::find(p_lea_cb->uuids.begin(), p_lea_cb->uuids.end(), + service.uuid); + if (itr == p_lea_cb->uuids.end()) { + p_lea_cb->uuids.push_back(service.uuid); + } + // Get Characteristic and CCCD handle + for (const gatt::Characteristic& charac : service.characteristics) { + Uuid role_uuid = charac.uuid; + if (role_uuid.As16Bit() == UUID_SERVCLASS_T_ADV_AUDIO_ROLE_CHAR) { + APPL_TRACE_DEBUG("%s:T_ADV_AUDIO ROLE CHAR found Uuid: %s ", __func__, + role_uuid.ToString().c_str()); + if (p_lea_cb != NULL) { + p_lea_cb->is_t_audio_srvc_found = true; + p_lea_cb->disc_progress++; + p_lea_cb->t_role_handle = charac.value_handle; + } else { + APPL_TRACE_DEBUG(" %s No Control Block", __func__); + } + } + } + if (p_lea_cb->t_role_handle) { + APPL_TRACE_DEBUG("%s t_role_handle %d", __func__, + p_lea_cb->t_role_handle); + BtaGattQueue::ReadCharacteristic(conn_id, p_lea_cb->t_role_handle, + bta_gap_gatt_read_cb, NULL); + } + } + } + break; + case UUID_SERVCLASS_HAS: + if (!p_lea_cb->is_t_audio_srvc_found) { + p_lea_cb->is_has_found = true; + APPL_TRACE_DEBUG("%s: HAS service found Uuid: %s ", __func__, + service.uuid.ToString().c_str()); + std::vector::iterator itr; + itr = std::find(p_lea_cb->uuids.begin(), p_lea_cb->uuids.end(), + service.uuid); + if (itr == p_lea_cb->uuids.end()) { + p_lea_cb->uuids.push_back(service.uuid); + } + } + FALLTHROUGH_INTENDED; /* FALLTHROUGH */ + case UUID_SERVCLASS_PACS: + { + if ((!p_lea_cb->pacs_char_handle) && + ((!p_lea_cb->is_t_audio_srvc_found))) { + APPL_TRACE_DEBUG("%s:PACS service found Uuid: %s ", __func__, + service.uuid.ToString().c_str()); + + std::vector::iterator itr; + itr = std::find(p_lea_cb->uuids.begin(), p_lea_cb->uuids.end(), + service.uuid); + if (itr == p_lea_cb->uuids.end()) { + p_lea_cb->uuids.push_back(service.uuid); + } + // Get Characteristic and CCCD handle + for (const gatt::Characteristic& charac : service.characteristics) { + Uuid role_uuid = charac.uuid; + if (role_uuid.As16Bit() == UUID_SERVCLASS_SOURCE_CONTEXT) { + APPL_TRACE_DEBUG("%s: PACS Source context CHAR found Uuid: %s ", + __func__, role_uuid.ToString().c_str()); + if (p_lea_cb != NULL) { + p_lea_cb->disc_progress++; + p_lea_cb->pacs_char_handle = charac.value_handle; + } else { + APPL_TRACE_DEBUG(" %s No Control Block", __func__); + } + } + } + if (p_lea_cb->pacs_char_handle) { + BtaGattQueue::ReadCharacteristic(conn_id, p_lea_cb->pacs_char_handle, + bta_gap_gatt_read_cb, NULL); + } + } + } + break; + default: + APPL_TRACE_DEBUG(" Not a LE AUDIO SERVICE-- IGNORE %s ", + service.uuid.ToString().c_str()); + } + } + } + } + + if (p_lea_cb->disc_progress == 0) { + bta_dm_lea_disc_complete(peer_address); + } +} + +/***************************************************************************** + * + * Function bta_dm_csis_disc_complete + * + * Description This API updates csis discovery complete status + * + * Parameters: none + *****************************************************************************/ +void bta_dm_csis_disc_complete(RawAddress p_bd_addr, bool status) { + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = bta_get_lea_ctrl_cb(p_bd_addr); + APPL_TRACE_DEBUG("%s %s %d", __func__, p_bd_addr.ToString().c_str(), + status); + + if (p_lea_cb) { + p_lea_cb->csip_disc_progress = status; + } else { + RawAddress pseudo_addr = bta_get_pseudo_addr_with_id_addr(p_bd_addr); + if (pseudo_addr != RawAddress::kEmpty) { + p_lea_cb = bta_get_lea_ctrl_cb(pseudo_addr); + if (p_lea_cb) { + p_lea_cb->csip_disc_progress = status; + APPL_TRACE_DEBUG(" %s Pseudo addr disc_progress resetted", __func__); + } else { + APPL_TRACE_DEBUG(" %s No Control Block for pseudo addr", __func__); + } + } else { + APPL_TRACE_DEBUG(" %s No Control Block", __func__); + } + } +} + +/***************************************************************************** + * + * Function bta_dm_lea_disc_complete + * + * Description This API sends the event to upper layer that LE audio + * gatt operations are complete. + * + * Parameters: none + * + ****************************************************************************/ +void bta_dm_lea_disc_complete(RawAddress p_bd_addr) { + tBTA_DM_SEARCH result; + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = bta_get_lea_ctrl_cb(p_bd_addr); + APPL_TRACE_DEBUG("%s %s", __func__, p_bd_addr.ToString().c_str()); + + if (p_lea_cb == NULL) { + RawAddress pseudo_addr = bta_get_pseudo_addr_with_id_addr(p_bd_addr); + p_lea_cb = bta_get_lea_ctrl_cb(pseudo_addr); + p_bd_addr = pseudo_addr; + } + + if (p_lea_cb) { + APPL_TRACE_DEBUG("csip_disc_progress %d", p_lea_cb->csip_disc_progress); + if ((p_lea_cb->disc_progress == 0) && + (p_lea_cb->csip_disc_progress)) { //Add CSIS check also + result.adv_audio_disc_cmpl.num_uuids = 0; + for (uint16_t i = 0; i < p_lea_cb->uuids.size(); i++) { + result.adv_audio_disc_cmpl.adv_audio_uuids[i] = p_lea_cb->uuids[i]; + result.adv_audio_disc_cmpl.num_uuids++; + } + + result.adv_audio_disc_cmpl.bd_addr = p_bd_addr; + APPL_TRACE_DEBUG("Sending Call back with no of uuids's" + "p_lea_cb->uuids.size() %d", p_lea_cb->uuids.size()); + bta_dm_search_cb.p_search_cback(BTA_DM_LE_AUDIO_SEARCH_CMPL_EVT, &result); + } else { + APPL_TRACE_DEBUG("%s Discovery in progress", __func__); + } + } else { + APPL_TRACE_DEBUG(" %s No Control Block", __func__); + } +} + + +/***************************************************************************** + * + * Function bta_add_adv_audio_uuid + * + * Description This is GATT client callback function used in DM. + * + * Parameters: + * + ******************************************************************************/ +void bta_add_adv_audio_uuid(RawAddress peer_address, + tBTA_GATT_ID srvc_uuid) { + auto itr = find(uuid_srv_disc_search.begin(), + uuid_srv_disc_search.end(), srvc_uuid.uuid); + + if(itr != uuid_srv_disc_search.end()) { + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = bta_get_lea_ctrl_cb(peer_address); + if (p_lea_cb != NULL) { + APPL_TRACE_DEBUG(" %s Control Block Found", __func__); + + std::vector::iterator itr; + itr = std::find(p_lea_cb->uuids.begin(), p_lea_cb->uuids.end(), srvc_uuid.uuid); + if (itr == p_lea_cb->uuids.end()) { + p_lea_cb->uuids.push_back(srvc_uuid.uuid); + } + } else { + APPL_TRACE_DEBUG(" %s No Control Block", __func__); + } + } +} + + + + +/******************************************************************************* + * + * Function bta_set_lea_ctrl_cb + * + * Description This is GATT client callback function used in DM. + * + * Parameters: + * + ******************************************************************************/ + +tBTA_LE_AUDIO_DEV_INFO* bta_set_lea_ctrl_cb(RawAddress peer_addr) { + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = NULL; + + p_lea_cb = bta_get_lea_ctrl_cb(peer_addr); + + if (p_lea_cb == NULL) { + APPL_TRACE_DEBUG("%s Control block create ", __func__); + + for (int i = 0; i < MAX_LEA_DEVICES ; i++) { + if (!bta_le_audio_dev_cb.bta_lea_dev_info[i].in_use) { + bta_le_audio_dev_cb.bta_lea_dev_info[i].peer_address = peer_addr; + bta_le_audio_dev_cb.bta_lea_dev_info[i].in_use = true; + bta_le_audio_dev_cb.bta_lea_dev_info[i].csip_disc_progress = true; + bta_le_audio_dev_cb.bta_lea_dev_info[i].is_csip_support = false; + bta_le_audio_dev_cb.bta_lea_dev_info[i].gatt_disc_progress = true; + bta_le_audio_dev_cb.num_lea_devices++; + return (&(bta_le_audio_dev_cb.bta_lea_dev_info[i])); + } + } + } else { + return p_lea_cb; + } + return NULL; +} + +/******************************************************************************* + * + * Function bta_dm_reset_adv_audio_dev_info + * + * Description This is GATT client callback function used in DM. + * + * Parameters: + * + ******************************************************************************/ +void bta_dm_reset_adv_audio_dev_info(RawAddress p_addr) { + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = bta_get_lea_ctrl_cb(p_addr); + + if (p_lea_cb != NULL) { + p_lea_cb->peer_address = RawAddress::kEmpty; + p_lea_cb->disc_progress = 0; + p_lea_cb->conn_id = 0; + p_lea_cb->transport = 0; + p_lea_cb->in_use = false; + p_lea_cb->t_role_handle = 0; + p_lea_cb->is_has_found = false; + p_lea_cb->is_t_audio_srvc_found = false; + p_lea_cb->pacs_char_handle = 0; + p_lea_cb->using_bredr_bonding = 0; + p_lea_cb->gatt_disc_progress = false; + p_lea_cb->uuids.clear(); + bta_le_audio_dev_cb.gatt_op_addr = RawAddress::kEmpty; + bta_le_audio_dev_cb.pending_peer_addr = RawAddress::kEmpty; + bta_le_audio_dev_cb.num_lea_devices--; + bta_le_audio_dev_cb.bond_progress = false; + APPL_TRACE_DEBUG("bta_dm_reset_adv_audio_dev_info %s transport %d ", + p_lea_cb->peer_address.ToString().c_str(), p_lea_cb->transport); + } +} + +/******************************************************************************* + * + * Function bta_dm_set_adv_audio_dev_info + * + * Description This is GATT client callback function used in DM. + * + * Parameters: + * + ******************************************************************************/ +void bta_dm_set_adv_audio_dev_info(tBTA_GATTC_OPEN* p_data) { + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = bta_set_lea_ctrl_cb(p_data->remote_bda); + + if (p_lea_cb != NULL) { + p_lea_cb->peer_address = p_data->remote_bda; + p_lea_cb->disc_progress = 0; + p_lea_cb->conn_id = p_data->conn_id; + p_lea_cb->transport = p_data->transport;//BTM_UseLeLink(p_data->remote_bda); + APPL_TRACE_DEBUG("bta_dm_set_adv_audio_dev_info %s transport %d ", + p_lea_cb->peer_address.ToString().c_str(), p_lea_cb->transport); + } +} + +/******************************************************************************* + * + * Function is_adv_audio_unicast_supported + * + * Description This function checks whether unicast support is there or not on + * remote side + * + * Parameters: + * + ******************************************************************************/ + +bool is_adv_audio_unicast_supported(RawAddress rem_bda, int conn_id) { + const std::vector* services = BTA_GATTC_GetServices(conn_id); + + if (services) { + for (const gatt::Service& service : *services) { + uint16_t uuid_val = service.uuid.As16Bit(); + if (uuid_val == UUID_SERVCLASS_ASCS) + return true; + } + } + + return false; +} + +/******************************************************************************* + * + * Function is_adv_audio_group_supported + * + * Description This function checks whether csip support is there or not on + * remote side + * + * Parameters: + * + ******************************************************************************/ + +bool is_adv_audio_group_supported(RawAddress rem_bda, int conn_id) { + const std::vector* services = BTA_GATTC_GetServices(conn_id); + + if (services) { + for (const gatt::Service& service : *services) { + if (is_le_audio_service(service.uuid)) { + uint16_t uuid_val = service.uuid.As16Bit(); + if (uuid_val == UUID_SERVCLASS_CSIS) + return true; + } + } + } + + return false; +} + +/******************************************************************************* + * + * Function bta_dm_lea_gattc_callback + * + * Description This is GATT client callback function used in DM. + * + * Parameters: + * + ******************************************************************************/ + +void bta_dm_lea_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { + APPL_TRACE_DEBUG("bta_dm_lea_gattc_callback event = %d", event); + + switch (event) { + case BTA_GATTC_OPEN_EVT: + if (p_data->status != GATT_SUCCESS) { + btif_dm_release_action_uuid(bta_le_audio_dev_cb.pending_peer_addr); + } else { + if (is_remote_support_adv_audio(bta_le_audio_dev_cb.pending_peer_addr)) { + bta_dm_set_adv_audio_dev_info(&p_data->open); + } + bta_dm_proc_open_evt(&p_data->open); + } + break; + + case BTA_GATTC_SEARCH_RES_EVT: + if (is_remote_support_adv_audio(bta_le_audio_dev_cb.pending_peer_addr)) { + bta_add_adv_audio_uuid(bta_le_audio_dev_cb.pending_peer_addr, + p_data->srvc_res.service_uuid); + } + break; + + case BTA_GATTC_SEARCH_CMPL_EVT: + if (is_remote_support_adv_audio(bta_le_audio_dev_cb.pending_peer_addr)) { + bta_get_adv_audio_role(bta_le_audio_dev_cb.pending_peer_addr, + p_data->search_cmpl.conn_id, + p_data->search_cmpl.status); + if (is_adv_audio_group_supported(bta_le_audio_dev_cb.pending_peer_addr, + p_data->search_cmpl.conn_id)) { + RawAddress p_id_addr = + bta_get_rem_dev_id_addr(bta_le_audio_dev_cb.pending_peer_addr); + if (p_id_addr != RawAddress::kEmpty) { + BTA_CsipFindCsisInstance(p_data->search_cmpl.conn_id, + p_data->search_cmpl.status, + p_id_addr); + } else { + BTA_CsipFindCsisInstance(p_data->search_cmpl.conn_id, + p_data->search_cmpl.status, + bta_le_audio_dev_cb.pending_peer_addr); + } + } + } + break; + + case BTA_GATTC_CLOSE_EVT: + APPL_TRACE_DEBUG("BTA_GATTC_CLOSE_EVT reason = %d, data conn_id %d," + "search conn_id %d", p_data->close.reason, p_data->close.conn_id, + bta_dm_search_cb.conn_id); + + if (is_remote_support_adv_audio(bta_le_audio_dev_cb.pending_peer_addr)) { + bta_dm_reset_adv_audio_dev_info(bta_le_audio_dev_cb.pending_peer_addr); + } + break; + + default: + break; + } +} + +/****************************************************************************** + * + * Function bta_dm_adv_audio_gatt_conn + * + * Description This API opens the gatt conn after finding sdp record + * during BREDR Discovery + * + * Parameters: none + * + ******************************************************************************/ +void bta_dm_adv_audio_gatt_conn(RawAddress p_bd_addr) { + APPL_TRACE_DEBUG("bta_dm_adv_audio_gatt_conn "); + + bta_le_audio_dev_cb.pending_peer_addr = p_bd_addr; + + tBTA_LE_AUDIO_DEV_INFO *tmp_lea_cb = bta_get_lea_ctrl_cb(p_bd_addr); + if (tmp_lea_cb && tmp_lea_cb->in_use) { + APPL_TRACE_DEBUG("bta_dm_adv_audio_gatt_conn Already exists %d", + tmp_lea_cb->conn_id); + return; + } + + BTA_GATTC_AppRegister(bta_dm_lea_gattc_callback, + base::Bind([](uint8_t client_id, uint8_t status) { + if (status == GATT_SUCCESS) { + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = + bta_set_lea_ctrl_cb(bta_le_audio_dev_cb.pending_peer_addr); + if (p_lea_cb) { + APPL_TRACE_DEBUG("bta_dm_adv_audio_gatt_conn Client Id: %d", + client_id); + p_lea_cb->gatt_if = client_id; + p_lea_cb->using_bredr_bonding = true; + BTA_GATTC_Open(client_id, bta_le_audio_dev_cb.pending_peer_addr, + true, GATT_TRANSPORT_LE, false); + } + } + }), false); + +} + +/****************************************************************************** + * + * Function bta_dm_adv_audio_close + * + * Description This API closes the gatt conn with was opened by dm layer + * for service discovery (or) opened after finding sdp record + * during BREDR Discovery + * + * Parameters: none + * + ******************************************************************************/ +void bta_dm_adv_audio_close(RawAddress p_bd_addr) { + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = bta_get_lea_ctrl_cb(p_bd_addr); + APPL_TRACE_DEBUG("%s", __func__); + + if (p_lea_cb) { + APPL_TRACE_DEBUG("%s %d", __func__, p_lea_cb->gatt_if); + if (p_lea_cb->using_bredr_bonding) { + APPL_TRACE_DEBUG("%s closing LE conn est due to bredr bonding %d", __func__, + p_lea_cb->gatt_if); + BTA_GATTC_AppDeregister(p_lea_cb->gatt_if); + } else { + bta_sys_start_timer(bta_dm_search_cb.gatt_close_timer, + BTA_DM_ADV_AUDIO_GATT_CLOSE_DELAY_TOUT, + BTA_DM_DISC_CLOSE_TOUT_EVT, 0); + } + } +} + +/******************************************************************************* + * + * Function bta_get_lea_ctrl_cb + * + * Description This API returns pairing control block of LE AUDIO DEVICE + * + * Parameters: tBTA_DEV_PAIRING_CB + * + ******************************************************************************/ +tBTA_DEV_PAIRING_CB* bta_get_lea_pair_cb(RawAddress peer_addr) { + tBTA_DEV_PAIRING_CB *p_lea_pair_cb = NULL; + p_lea_pair_cb = &bta_lea_pairing_cb.bta_dev_pair_db[0]; + APPL_TRACE_DEBUG("%s %s ", __func__, peer_addr.ToString().c_str()); + + for (int i = 0; i < MAX_LEA_DEVICES; i++) { + if ((p_lea_pair_cb[i].in_use) && + (p_lea_pair_cb[i].p_addr == peer_addr)) { + APPL_TRACE_DEBUG("%s Found %s index i %d ", __func__, + p_lea_pair_cb[i].p_addr.ToString().c_str(), i); + return &p_lea_pair_cb[i]; + } + } + return NULL; +} + + + +/******************************************************************************* + * + * Function bta_set_lea_ctrl_cb + * + * Description This is GATT client callback function used in DM. + * + * Parameters: + * + ******************************************************************************/ + +tBTA_DEV_PAIRING_CB* bta_set_lea_pair_cb(RawAddress peer_addr) { + tBTA_DEV_PAIRING_CB *p_lea_pair_cb = NULL; + APPL_TRACE_DEBUG("bta_set_lea_ctrl_cb %s", peer_addr.ToString().c_str()); + + p_lea_pair_cb = bta_get_lea_pair_cb(peer_addr); + + if (p_lea_pair_cb == NULL) { + APPL_TRACE_DEBUG("bta_set_lea_ctrl_cb Control block create "); + + for (int i = 0; i < MAX_LEA_DEVICES ; i++) { + if (!bta_lea_pairing_cb.bta_dev_pair_db[i].in_use) { + bta_lea_pairing_cb.bta_dev_pair_db[i].p_addr = peer_addr; + bta_lea_pairing_cb.bta_dev_pair_db[i].in_use = true; + bta_lea_pairing_cb.is_pairing_progress = true; + bta_lea_pairing_cb.num_devices++; + return (&(bta_lea_pairing_cb.bta_dev_pair_db[i])); + } + } + } else { + return p_lea_pair_cb; + } + return NULL; +} + +/******************************************************************************* + * + * Function bta_dm_reset_adv_audio_dev_info + * + * Description This API resets all the pairing information related to le + * audio remote device. + * Parameters: none + * + ******************************************************************************/ +void bta_dm_reset_lea_pairing_info(RawAddress p_addr) { + + APPL_TRACE_DEBUG("%s Addr %s", __func__, p_addr.ToString().c_str()); + + auto itr = bta_lea_pairing_cb.dev_addr_map.find(p_addr); + if (itr != bta_lea_pairing_cb.dev_addr_map.end()) { + bta_lea_pairing_cb.dev_addr_map.erase(p_addr); + } + + itr = bta_lea_pairing_cb.dev_rand_addr_map.find(p_addr); + if (itr != bta_lea_pairing_cb.dev_rand_addr_map.end()) { + bta_lea_pairing_cb.dev_rand_addr_map.erase(p_addr); + } + + tBTA_DEV_PAIRING_CB *p_lea_pair_cb = NULL; + p_lea_pair_cb = bta_get_lea_pair_cb(p_addr); + if (p_lea_pair_cb) { + APPL_TRACE_DEBUG("%s RESETTING VALUES", __func__); + p_lea_pair_cb->in_use = false; + p_lea_pair_cb->is_dumo_device = false; + p_lea_pair_cb->is_le_pairing = false; + p_lea_pair_cb->dev_type = 0; + if (p_lea_pair_cb->p_id_addr != RawAddress::kEmpty) { + itr = bta_lea_pairing_cb.dev_addr_map.find(p_lea_pair_cb->p_id_addr); + APPL_TRACE_DEBUG("%s RESETTING Addr %s", __func__, + p_lea_pair_cb->p_id_addr.ToString().c_str()); + if (itr != bta_lea_pairing_cb.dev_addr_map.end()) { + APPL_TRACE_DEBUG("%s Clearing INSIDE LEA ADDR DB MAP", + __func__); + bta_lea_pairing_cb.dev_addr_map.erase(p_lea_pair_cb->p_id_addr); + } + p_lea_pair_cb->p_id_addr = RawAddress::kEmpty; + p_lea_pair_cb->transport = 0; + p_lea_pair_cb->p_addr = RawAddress::kEmpty; + } + bta_lea_pairing_cb.is_pairing_progress = false; + bta_lea_pairing_cb.num_devices--; + bta_lea_pairing_cb.is_sdp_discover = true; + } else { + APPL_TRACE_DEBUG("%s INVALID CONTROL BLOCK", __func__); + } +} + +/***************************************************************************** + * + * Function bta_dm_ble_adv_audio_idaddr_map + * + * Description storing the identity address information in the device + * control block. It will used for DUMO devices + * + * Returns none + * + *****************************************************************************/ +void bta_dm_ble_adv_audio_idaddr_map(RawAddress p_bd_addr, + RawAddress p_id_addr) { + APPL_TRACE_DEBUG("%s p_bd_addr %s id_addr %s ", __func__, + p_bd_addr.ToString().c_str(), p_id_addr.ToString().c_str()); + if (is_remote_support_adv_audio(p_bd_addr)) { + bta_lea_pairing_cb.dev_addr_map[p_id_addr] = p_bd_addr; + bta_lea_pairing_cb.dev_rand_addr_map[p_bd_addr] = p_id_addr; + + tBTA_DEV_PAIRING_CB *p_lea_pair_cb = NULL; + p_lea_pair_cb = bta_get_lea_pair_cb(p_bd_addr); + if (p_lea_pair_cb) { + if (p_id_addr != p_bd_addr) { + APPL_TRACE_DEBUG("%s is_dumo_device %s", __func__, + p_id_addr.ToString().c_str()); + p_lea_pair_cb->p_id_addr = p_id_addr; + p_lea_pair_cb->is_dumo_device = true; + } + } + } +} + +bool bta_remote_dev_identity_addr_match(RawAddress p_addr) { + APPL_TRACE_DEBUG("%s ", __func__); + + auto itr = bta_lea_pairing_cb.dev_addr_map.find(p_addr); + + if (itr != bta_lea_pairing_cb.dev_addr_map.end()) { + APPL_TRACE_DEBUG("%s Identity BD_ADDR %s", __func__, + p_addr.ToString().c_str()); + return true; + } + return false; +} + +bool bta_is_bredr_primary_transport(RawAddress p_bd_addr) { + + tBTA_DEV_PAIRING_CB *p_lea_pair_cb = NULL; + + p_lea_pair_cb = bta_get_lea_pair_cb(p_bd_addr); + APPL_TRACE_DEBUG("%s ", __func__); + if (p_lea_pair_cb) { + APPL_TRACE_DEBUG("%s Transport %d ", __func__, p_lea_pair_cb->transport); + if (p_lea_pair_cb->transport == BT_TRANSPORT_BR_EDR) { + return true; + } + } + + return false; +} + +bool bta_remote_device_is_dumo(RawAddress p_bd_addr) { + + auto itr = bta_lea_pairing_cb.dev_addr_map.find(p_bd_addr); + APPL_TRACE_DEBUG("%s Addr %s", __func__, p_bd_addr.ToString().c_str()); + + if (itr != bta_lea_pairing_cb.dev_addr_map.end()) { + APPL_TRACE_DEBUG("%s DUMO DEVICE Identity BD_ADDR %s", __func__, + p_bd_addr.ToString().c_str()); + return true; + } + + auto itr2 = bta_lea_pairing_cb.dev_rand_addr_map.find(p_bd_addr); + if (itr2 != bta_lea_pairing_cb.dev_rand_addr_map.end()) { + APPL_TRACE_DEBUG("%s Dumo addressed %s %s ", __func__, + itr2->first.ToString().c_str(), itr2->second.ToString().c_str()); + return true; + } + return false; +} + +RawAddress bta_get_rem_dev_id_addr(RawAddress p_bd_addr) { + tBTA_DEV_PAIRING_CB *p_lea_pair_cb = NULL; + APPL_TRACE_DEBUG("%s ", __func__); + + p_lea_pair_cb = bta_get_lea_pair_cb(p_bd_addr); + if (p_lea_pair_cb) { + APPL_TRACE_DEBUG("%s %s", __func__, + p_lea_pair_cb->p_id_addr.ToString().c_str()); + return p_lea_pair_cb->p_id_addr; + } + return RawAddress::kEmpty; +} + +/***************************************************************************** + * + * Function bta_adv_audio_update_bond_db + * + * Description Updates pairing control block of the device and the bonding + * is initiated using LE transport or not. + * + * Returns void + * + *****************************************************************************/ +void bta_adv_audio_update_bond_db(RawAddress p_bd_addr, uint8_t transport) { + tBTA_DEV_PAIRING_CB *p_dev_pair_cb = bta_set_lea_pair_cb(p_bd_addr); + + APPL_TRACE_DEBUG("%s", __func__); + if (p_dev_pair_cb) { + APPL_TRACE_DEBUG("%s Addr %s Transport %d", __func__, + p_bd_addr.ToString().c_str(), transport); + p_dev_pair_cb->p_addr = p_bd_addr; + p_dev_pair_cb->transport = transport; + if (transport == BT_TRANSPORT_LE) { + if (is_remote_support_adv_audio(p_dev_pair_cb->p_addr)) + p_dev_pair_cb->is_le_pairing = true; + else + p_dev_pair_cb->is_le_pairing = false; + } else + p_dev_pair_cb->is_le_pairing = false; + } +} + +/***************************************************************************** + * + * Function is_le_audio_service + * + * Description It checks whether the given service is related to the LE + * Audio service or not. + * + * Returns true for LE Audio service which are registered. + false by default + * + *****************************************************************************/ +bool is_le_audio_service(Uuid uuid) { + + uint16_t uuid_val = 0; + bool status = false; + + size_t len = uuid.GetShortestRepresentationSize(); + if (len == Uuid::kNumBytes16) { + uuid_val = uuid.As16Bit(); + APPL_TRACE_DEBUG("is_le_audio_service : 0x%X 0x%X ", uuid.As16Bit(), uuid_val); + //TODO check the service contains any LE AUDIO service or not + switch (uuid_val) { + case UUID_SERVCLASS_CSIS: + FALLTHROUGH_INTENDED; /* FALLTHROUGH */ + case UUID_SERVCLASS_BASS: + FALLTHROUGH_INTENDED; /* FALLTHROUGH */ + case UUID_SERVCLASS_T_ADV_AUDIO: + FALLTHROUGH_INTENDED; /* FALLTHROUGH */ + case UUID_SERVCLASS_ASCS: + FALLTHROUGH_INTENDED; /* FALLTHROUGH */ + case UUID_SERVCLASS_BAAS: + FALLTHROUGH_INTENDED; /* FALLTHROUGH */ + case UUID_SERVCLASS_PACS: + { + auto itr = find(uuid_srv_disc_search.begin(), + uuid_srv_disc_search.end(), uuid); + if (itr != uuid_srv_disc_search.end()) + status = true; + } + break; + default: + APPL_TRACE_DEBUG("%s : Not a LEA service ", __func__); + } + } else if(len == Uuid::kNumBytes128) { + if (uuid == UUID_SERVCLASS_WMCP) { + APPL_TRACE_DEBUG("%s: WMCP Service UUId found", __func__); + auto itr = find(uuid_srv_disc_search.begin(), + uuid_srv_disc_search.end(), uuid); + if (itr != uuid_srv_disc_search.end()) + status = true; + } + } + + return status; +} + +/***************************************************************************** + * + * Function bta_is_adv_audio_valid_bdaddr + * + * Description This API is used for DUMO device. If the device contains + * two address (random and public), it checks for valid + * address. + * + * Returns 0 - for random address in dumo device + * 1 - for public address in dumo device + * + ****************************************************************************/ +int bta_is_adv_audio_valid_bdaddr(RawAddress p_bd_addr) { + tBTA_DEV_PAIRING_CB *p_lea_pair_cb = NULL; + p_lea_pair_cb = bta_get_lea_pair_cb(p_bd_addr); + + if (p_lea_pair_cb) { + APPL_TRACE_DEBUG("%s p_lea_pair_cb %s", __func__, + p_lea_pair_cb->p_addr.ToString().c_str()); + auto itr = bta_lea_pairing_cb.dev_addr_map.find(p_bd_addr); + if (itr == bta_lea_pairing_cb.dev_addr_map.end() && + (p_lea_pair_cb->is_dumo_device)) { + APPL_TRACE_DEBUG("%s Ignore BD_ADDR because of ID %s", __func__, + p_lea_pair_cb->p_id_addr.ToString().c_str()); + return 0; + } + } + return 1; +} + +/***************************************************************************** + * + * Function devclass2uint + * + * Description This API is to derive the class of device based of dev_class + * + * Returns uint32_t - class of device + * + ****************************************************************************/ +static uint32_t devclass2uint(DEV_CLASS dev_class) { + uint32_t cod = 0; + + if (dev_class != NULL) { + /* if COD is 0, irrespective of the device type set it to Unclassified + * device */ + cod = (dev_class[2]) | (dev_class[1] << 8) | (dev_class[0] << 16); + } + return cod; +} + +/***************************************************************************** + * + * Function bta_is_remote_support_lea + * + * Description This API is to check the remote device contains LEA service + * or not. It checks in Inquiry database initially. + * If the address is Public identity address then it will + * check in the pairing database of that remote device. + * + * Returns true - if remote device inquiry db contains LEA service + * + ****************************************************************************/ +bool bta_is_remote_support_lea(RawAddress p_addr) { + tBTM_INQ_INFO* p_inq_info; + + p_inq_info = BTM_InqDbRead(p_addr); + if (p_inq_info != NULL) { + uint32_t cod = devclass2uint(p_inq_info->results.dev_class); + BTIF_TRACE_DEBUG("%s cod is 0x%06x", __func__, cod); + if ((cod & MAJOR_LE_AUDIO_VENDOR_COD) + == MAJOR_LE_AUDIO_VENDOR_COD) { + return true; + } + } + + /* check the address is public identity address and its related to random + * address which supports to LEA then that Public ID address should return + * true. + */ + auto itr = bta_lea_pairing_cb.dev_addr_map.find(p_addr); + if (itr != bta_lea_pairing_cb.dev_addr_map.end()) { + BTIF_TRACE_DEBUG("%s Idenity address mapping", __func__); + return true; + } + + return false; +} + +void bta_find_adv_audio_group_instance(uint16_t conn_id, tGATT_STATUS status, + RawAddress p_addr) { + RawAddress p_id_addr = + bta_get_rem_dev_id_addr(p_addr); + if (p_id_addr != RawAddress::kEmpty) { + BTA_CsipFindCsisInstance(conn_id, status, p_id_addr); + } else { + BTA_CsipFindCsisInstance(conn_id, status, p_addr); + } +} + +/******************************************************************************* + * + * Function is_gatt_srvc_disc_pending + * + * Description This function checks whether gatt_srvc_disc is processing + * or not + * + * Parameters: + * + ******************************************************************************/ +bool is_gatt_srvc_disc_pending(RawAddress rem_bda) { + tBTA_LE_AUDIO_DEV_INFO *p_lea_cb = bta_get_lea_ctrl_cb(rem_bda); + + APPL_TRACE_DEBUG("%s ", __func__); + if (p_lea_cb == NULL) { + return false; + } else { + APPL_TRACE_DEBUG("%s gatt_disc_progress %d ", __func__, + p_lea_cb->gatt_disc_progress); + return p_lea_cb->gatt_disc_progress; + } +} + +/****************************************************************************** + * + * Function bta_get_pseudo_addr_with_id_addr + * + * Description This function returns the mapping id_addr(if present) to + * pseudo addr + * + * Parameters: + * + *****************************************************************************/ +RawAddress bta_get_pseudo_addr_with_id_addr(RawAddress p_addr) { + auto itr = bta_lea_pairing_cb.dev_addr_map.find(p_addr); + + APPL_TRACE_DEBUG("%s p_addr %s ", __func__, p_addr.ToString().c_str()); + if (itr != bta_lea_pairing_cb.dev_addr_map.end()) { + APPL_TRACE_DEBUG("%s addr is mapped to %s ", __func__, + itr->second.ToString().c_str()); + if (itr->second != RawAddress::kEmpty) { + return itr->second; + } + } + return p_addr; +} diff --git a/le_audio/system/bt/bta/include/bta_ascs_client_api.h b/le_audio/system/bt/bta/include/bta_ascs_client_api.h new file mode 100644 index 00000000000..5bcf48e5cb3 --- /dev/null +++ b/le_audio/system/bt/bta/include/bta_ascs_client_api.h @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * 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. + */ +#pragma once + +#include +#include + +namespace bluetooth { +namespace bap { +namespace ascs { + +class AscsClient { + public: + virtual ~AscsClient() = default; + + static void Init(bluetooth::bap::ascs::AscsClientCallbacks* callbacks); + + static void CleanUp(uint16_t client_id); + + static AscsClient* Get(); + + virtual void Connect(uint16_t client_id, const RawAddress& address, + bool is_direct) = 0; + + virtual void Disconnect(uint16_t client_id, const RawAddress& address) = 0; + + virtual void StartDiscovery(uint16_t client_id, + const RawAddress& address) = 0; + + virtual void GetAseState(uint16_t client_id, const RawAddress& address, + uint8_t ase_id) = 0; + + virtual void CodecConfig(uint16_t client_id, const RawAddress& address, + std::vector codec_configs); + + virtual void QosConfig(uint16_t client_id, const RawAddress& address, + std::vector qos_configs); + + virtual void Enable(uint16_t client_id, const RawAddress& address, + std::vector enable_ops); + + virtual void Disable(uint16_t client_id, const RawAddress& address, + std::vector disable_ops); + + virtual void StartReady(uint16_t client_id, const RawAddress& address, + std::vector start_ready_ops); + + virtual void StopReady(uint16_t client_id, const RawAddress& address, + std::vector stop_ready_ops); + + virtual void Release(uint16_t client_id, const RawAddress& address, + std::vector release_ops); + + virtual void UpdateStream(uint16_t client_id, const RawAddress& address, + std::vector metadata_ops); +}; + +} // namespace ascs +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/include/bta_bap_uclient_api.h b/le_audio/system/bt/bta/include/bta_bap_uclient_api.h new file mode 100644 index 00000000000..c2d6d2dd4e1 --- /dev/null +++ b/le_audio/system/bt/bta/include/bta_bap_uclient_api.h @@ -0,0 +1,53 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +#pragma once + +#include +#include "connected_iso_api.h" +#include +#include "bta_ascs_client_api.h" +#include "bta/bap/uclient_alarm.h" + +namespace bluetooth { +namespace bap { +namespace ucast { + +using bluetooth::bap::pacs::PacsClientCallbacks; +using bluetooth::bap::ascs::AscsClientCallbacks; +using bluetooth::bap::cis::CisInterfaceCallbacks; +using bluetooth::bap::ucast::StreamConnect; +using bluetooth::bap::ucast::StreamType; +using bluetooth::bap::alarm::BapAlarmCallbacks; + +class UcastClient : public PacsClientCallbacks, + public AscsClientCallbacks, + public CisInterfaceCallbacks, + public BapAlarmCallbacks { + public: + virtual ~UcastClient() = default; + + static void Initialize(UcastClientCallbacks* callbacks); + static void CleanUp(); + static UcastClient* Get(); + + // APIs exposed to upper layer + virtual void Connect(std::vector address, bool is_direct, + std::vector streams) = 0; + virtual void Disconnect(const RawAddress& address, + std::vector streams) = 0; + virtual void Start(const RawAddress& address, + std::vector streams) = 0; + virtual void Stop(const RawAddress& address, + std::vector streams) = 0; + virtual void Reconfigure(const RawAddress& address, + std::vector streams) = 0; + virtual void UpdateStream(const RawAddress& address, + std::vector update_streams) = 0; +}; + +} // namespace ucast +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/include/bta_cc_api.h b/le_audio/system/bt/bta/include/bta_cc_api.h new file mode 100644 index 00000000000..f7dbd6929c4 --- /dev/null +++ b/le_audio/system/bt/bta/include/bta_cc_api.h @@ -0,0 +1,472 @@ +/* + *Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright (C) 2003-2012 Broadcom Corporation + * + * 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. + */ +#pragma once + +#include +#include +#include +#include +#include "bta_gatt_api.h" +#include +#include + +using bluetooth::Uuid; +using bluetooth::call_control::CallControllerCallbacks; +#define MAX_RESPONSE_DATA_LEN 255 +#define MAX_CCS_CONNECTION 5 +#define MAX_BEARER_NAME 255 +#define MAX_UCI_NAME 255 +#define MAX_URI_LENGTH 255 +#define MAX_BEARER_LIST_LEN 255 +#define MAX_FRIENDLY_NAME_LEN 255 +//Atmost there could be only two Indicies (for JOIN) +//Keeping this as Internal max as SPec don't define any upper limit on this +#define MAX_NUM_INDICIES 10 + +#define INBAND_RINGTONE_FEATURE_BIT 0x01 +#define SILENT_MODE_FEATURE_BIT 0x02 + +typedef enum { + CCS_NONE_EVENT = 120, + CCS_INIT_EVENT, + CCS_CLEANUP_EVENT, + CCS_CALL_STATE_UPDATE, + CCS_BEARER_NAME_UPDATE, + CCS_BEARER_UCI_UPDATE, + CCS_BEARER_URI_SCHEMES_SUPPORTED, + CCS_UPDATE, + CCS_OPT_OPCODES, + CCS_BEARER_CURRENT_CALL_LIST_UPDATE, + CCS_BEARER_SIGNAL_STRENGTH_UPDATE, + CCS_SIGNAL_STRENGTH_REPORT_INTERVAL, + CCS_STATUS_FLAGS_UPDATE, + CCS_INCOMING_CALL_UPDATE, + CCS_INCOMING_TARGET_URI_UPDATE, + CCS_TERMINATION_REASON_UPDATE, + CCS_BEARER_TECHNOLOGY_UPDATE, + CCS_CCID_UPDATE, + CCS_ACTIVE_DEVICE_UPDATE, + CCS_CALL_CONTROL_RESPONSE, + //events to handle in CCS state machine + CCS_NOTIFY_ALL, + CCS_WRITE_RSP, + CCS_READ_RSP, + CCS_DESCRIPTOR_WRITE_RSP, + CCS_DESCRIPTOR_READ_RSP, + CCS_CONNECTION, + CCS_DISCONNECTION, + CCS_CONNECTION_UPDATE, + CCS_CONGESTION_UPDATE, + CCS_PHY_UPDATE, + CCS_MTU_UPDATE, + CCS_SET_ACTIVE_DEVICE, + CCS_CONNECTION_CLOSE_EVENT, + CCS_BOND_STATE_CHANGE_EVENT, +}cc_event_t; + +typedef enum { + CCS_STATUS_SUCCESS = 0x00, + CCS_OPCODE_NOT_SUPPORTED, + CCS_OPCODE_UNSUCCESSFUL, + CCS_INVALID_INDEX, + CCS_STATE_MISMATCH, + CCS_LACK_OF_RESOURCES, + CCS_INVALID_OUTGOING_URI, + CCS_CALL_STATE_INACTIVE, +}cc_error_t; + +typedef enum { + CCS_DISCONNECTED = 0x00, + CCS_CONNECTED, + CCS_MAX_DEVICE_STATE +} call_connect_state_t; + +typedef enum { + CALL_ACCEPT = 0x00, + CALL_TERMINATE, + CALL_LOCAL_HOLD, + CALL_LOCAL_RETRIEVE, + CALL_ORIGINATE, + CALL_JOIN, + } cc_opcode_t; + + typedef enum { + CC_TERM_INVALID_ORIG_URI = 0x00, + CC_TERM_FAILED, + CC_TERM_END_FROM_REMOTE, + CC_TERM_END_FROM_SERVER, + CC_TERM_LINE_BUSY, + CC_TERM_NW_CONGESTION, + CC_TERM_END_FROM_CLIENT, + CC_TERM_NO_SERVICE, + CC_TERM_NO_ANSWER, + } cc_term_reason_t; + + typedef enum { + CCS_STATE_INCOMING = 0x00, + CCS_STATE_DIALING, + CCS_STATE_ALERTING, + CCS_STATE_ACTIVE, + CCS_STATE_LOCAL_HELD, + CCS_STATE_REMOTELY_HELD, + CCS_STATE_LOCAL_REMOTE_HELD, + CCS_STATE_DISCONNECTED, + } cc_state_t; + + //connection state machine + bool DeviceStateConnectionHandler(uint32_t event, void* param); + bool DeviceStateDisConnectingHandler(uint32_t event, void* param); + bool DeviceStateDisconnectedHandler(uint32_t event, void* param); + +typedef struct { + int server_if; + Uuid ccs_service_uuid; + Uuid bearer_provider_name_uuid; + Uuid call_control_point_uuid; + Uuid call_control_point_opcode_supported_uuid; + Uuid bearer_uci_uuid; + Uuid bearer_technology_uuid; + Uuid bearer_uri_schemes_supported_uuid; + Uuid bearer_signal_strength_uuid; + Uuid bearer_signal_strength_report_interval_uuid; + Uuid bearer_list_currentcalls_uuid; + Uuid incoming_call_target_beareruri_uuid; + Uuid call_status_flags_uuid; + Uuid call_state_uuid; + Uuid gtbs_ccid_uuid; + Uuid call_termination_reason_uuid; + Uuid incoming_call_uuid; + Uuid call_friendly_name_uuid; + //handle for characteristics + uint16_t call_state_handle; + uint16_t bearer_provider_name_handle; + uint16_t call_control_point_opcode_supported_handle; + uint16_t call_control_point_handle; + uint16_t bearer_uci_handle; + uint16_t bearer_technology_handle; + uint16_t bearer_uri_schemes_supported_handle; + uint16_t bearer_signal_strength_handle; + uint16_t bearer_signal_strength_report_interval_handle; + uint16_t bearer_list_currentcalls_handle; + uint16_t incoming_call_target_beareruri_handle; + uint16_t call_status_flags_handle; + uint16_t call_termination_reason_handle; + uint16_t incoming_call_handle; + uint16_t call_friendly_name_handle; + uint16_t ccid_handle; + uint16_t call_state_desc; + uint16_t bearer_provider_name_desc; + uint16_t call_control_point_opcode_supported_desc; + uint16_t call_control_point_desc; + uint16_t bearer_uci_desc; + uint16_t bearer_technology_desc; + uint16_t bearer_uri_schemes_supported_desc; + uint16_t bearer_signal_strength_desc; + uint16_t bearer_signal_strength_report_interval_desc; + uint16_t bearer_list_currentcalls_desc; + uint16_t incoming_call_target_bearerURI_desc; + uint16_t call_status_flags_desc; + uint16_t call_termination_reason_desc; + uint16_t incoming_call_desc; + uint16_t call_friendly_name_desc; + uint16_t ccid_desc; +}CcsControlServiceInfo_t; + +typedef struct { + call_connect_state_t state; + uint16_t call_state_notify; + uint16_t bearer_provider_name_notify; + uint16_t call_control_point_notify; + uint16_t call_control_point_opcode_supported_notify; + uint16_t bearer_technology_changed_notify; + uint16_t bearer_uci_notify; + uint16_t bearer_uri_schemes_supported_notify; + uint16_t bearer_current_calls_list_notify; + uint16_t bearer_signal_strength_notify; + uint16_t bearer_signal_strength_report_interval_notify; + uint16_t incoming_call_state_notify; + uint16_t incoming_call_target_URI_notify; + uint16_t status_flags_notify; + uint16_t call_termination_reason_notify; + uint16_t call_friendly_name_notify; + uint8_t signal_strength_report_interval; + alarm_t* signal_strength_reporting_timer; + bool congested; + int conn_id; + int trans_id; + int timeout; + int latency; + int interval; + int rx_phy; + int tx_phy; + int mtu; + + RawAddress peer_bda; + bool (*DeviceStateHandlerPointer[2])(uint32_t event, void* param); +}CallControllerDeviceList; + +typedef struct { + std::vector address; + int set_id; +}CallActiveDevice; + +typedef struct { + uint8_t index; + uint8_t state; + uint8_t flags; +}tCCS_CALL_STATE; + +typedef struct { + uint8_t index; + uint8_t incoming_target_uri[MAX_URI_LENGTH]; +}tCCS_INCOMING_CALL_URI; + +typedef struct { + uint8_t index; + uint8_t incoming_uri[MAX_URI_LENGTH]; +}tCCS_INCOMING_CALL; + +typedef struct { + uint8_t operation; + uint8_t index[MAX_NUM_INDICIES]; + uint8_t supported_flags; + char* uri; + uint32_t ccid; +}tCCS_CALL_CONTROL_POINT; + +typedef struct { + uint8_t opcode; + uint8_t index; + uint8_t response_status; + RawAddress remote_address; +}tCCS_CALL_CONTROL_RESPONSE; + +typedef struct { + uint16_t supported_flags; +}tCCS_STATUS_FLAGS; + +typedef struct { + uint16_t supp_opcode; +}tCCS_SUPP_OPTIONAL_OPCODES; + + +typedef struct { + uint8_t index; + uint8_t reason; +}tCCS_TERM_REASON; + +typedef struct { + uint8_t index; + uint8_t name[MAX_FRIENDLY_NAME_LEN]; +}tCCS_FRIENDLY_NAME; + +typedef struct { + uint32_t ccid; +}tCCS_CONTENT_CONTROL_ID; + +typedef struct { + RawAddress addr; +}tCCS_CONNECTION_CLOSE; + +typedef struct { + uint8_t list_length; + uint8_t call_index; + uint8_t call_state; + uint8_t call_flags; + uint8_t call_uri[MAX_URI_LENGTH]; + }tCCS_BEARER_LIST_CURRENT_CALLS; + +typedef struct { + uint8_t name[MAX_BEARER_NAME]; + uint8_t uci[MAX_UCI_NAME]; + uint8_t length; + uint8_t technology_type; + uint8_t signal; + uint8_t signal_report_interval; + int bearer_list_len; + uint8_t bearer_schemes_list[MAX_BEARER_LIST_LEN]; +}tCCS_BEARER_PROVIDER_INFO; + +typedef struct { + bool status; +}tCCS_BEARER_URI_SCHEMES; + +//Union ops +struct tCCS_CHAR_DESC_WRITE { + tCCS_CHAR_DESC_WRITE() {}; + ~tCCS_CHAR_DESC_WRITE() {}; + std::vector value; + uint8_t status; + uint16_t notification; + uint32_t trans_id; + uint32_t desc_handle; + uint32_t char_handle; + bool need_rsp; + bool prep_rsp; + //is to send notification +}; + +struct tCCS_CHAR_DESC_READ { + tCCS_CHAR_DESC_READ() {}; + ~tCCS_CHAR_DESC_READ() {}; + uint8_t status; + uint32_t trans_id; + uint32_t desc_handle; + uint32_t char_handle; +}; + +struct tCCS_CHAR_GATT_READ { + tCCS_CHAR_GATT_READ() {}; + ~tCCS_CHAR_GATT_READ() {}; + uint8_t status; + uint32_t trans_id; + uint32_t char_handle; +}; + +struct tCCS_CHAR_WRITE { + tCCS_CHAR_WRITE() {}; + ~tCCS_CHAR_WRITE() {}; + uint8_t status; + bool need_rsp; + bool prep_rsp; + uint16_t offset; + uint16_t trans_id; + uint32_t char_handle; + int len; + std::vector value; + uint8_t *data; +}; + +struct tCCS_CONNECTION { + uint8_t status; + CallControllerDeviceList remoteDevice; +}; + +struct tCCS_CONN_UPDATE { + tCCS_CONN_UPDATE() {}; + ~tCCS_CONN_UPDATE() {}; + uint8_t status; + CallControllerDeviceList *remoteDevice; +}; + +struct tCCS_DISCONNECTION { + tCCS_DISCONNECTION() {}; + ~tCCS_DISCONNECTION() {}; + uint8_t status; + CallControllerDeviceList *remoteDevice; +}; + +struct tCCS_CONGESTION { + tCCS_CONGESTION() {}; + ~tCCS_CONGESTION() {}; + bool congested; + CallControllerDeviceList *remoteDevice; +}; + +struct tCCS_PHY{ + tCCS_PHY(); + ~tCCS_PHY(); + uint8_t status; + uint8_t tx_phy; + uint8_t rx_phy; + CallControllerDeviceList *remoteDevice; +}; + +struct tCCS_MTU { + tCCS_MTU() {}; + ~tCCS_MTU() {}; + uint8_t status; + uint16_t mtu; + CallControllerDeviceList *remoteDevice; +}; + +struct tCCS_SET_ACTIVE_DEVICE { + tCCS_SET_ACTIVE_DEVICE() {}; + ~tCCS_SET_ACTIVE_DEVICE() {}; + RawAddress address; + uint16_t set_id; +}; + +struct tCALL_CONTROL_UPDATE { + tCALL_CONTROL_UPDATE() {}; + ~tCALL_CONTROL_UPDATE() {}; + std::vector data; +}; + +union CALL_CONTROL_OPERATION{ + CALL_CONTROL_OPERATION() : CallControllerOp() { + }; + ~CALL_CONTROL_OPERATION() {}; + tCALL_CONTROL_UPDATE CallControllerOp; + tCCS_SET_ACTIVE_DEVICE SetActiveDeviceOp; + tCCS_CHAR_DESC_WRITE WriteDescOp; + tCCS_CHAR_DESC_READ ReadDescOp; + tCCS_CHAR_WRITE WriteOp; + tCCS_CHAR_GATT_READ ReadOp; + tCCS_CONNECTION ConnectionOp; + tCCS_CONN_UPDATE ConnectionUpdateOp; + tCCS_DISCONNECTION DisconnectionOp; + tCCS_CONGESTION CongestionOp; + tCCS_MTU MtuOp; + tCCS_PHY PhyOp; +}; + +typedef union CALL_CONTROL_OPERATION tCCS_OPERATION; + +struct tcc_resp_t { + tcc_resp_t() {}; + ~tcc_resp_t() {}; + uint32_t event = 0; + uint16_t handle = 0; + uint16_t status = 0; + bool force = false; + CallControllerDeviceList *remoteDevice = nullptr; + tGATTS_RSP rsp_value; + tCCS_OPERATION oper; + }; + +class CallController { + + public: + virtual ~CallController() = default; + static void Initialize(bluetooth::call_control::CallControllerCallbacks* callbacks, + Uuid app_id, int max_ccs_clients, bool inband_ringing_enabled); + static void CleanUp(); + static CallController* Get(); + static bool IsCcServiceRunnig(); + + virtual void CallState(int len, std::vector value) = 0; + virtual void BearerInfoName(uint8_t* bearer_name) = 0; + virtual void UpdateBearerTechnology(int tech_type) = 0; + virtual void UpdateSupportedBearerList(uint8_t* list) = 0; + virtual void UpdateIncomingCallTargetUri(int index, uint8_t* target_uri) = 0; + virtual void UpdateIncomingCall(int index, uint8_t* Uri) = 0; + virtual void UpdateBearerSignalStrength(int signal) = 0; + virtual void UpdateStatusFlags(uint8_t status_flag) = 0; + virtual void CallControlOptionalOpSupported(int feature) = 0; + virtual void CallControlResponse(uint8_t op, uint8_t index, uint32_t status, const RawAddress& address)= 0; + virtual void SetActiveDevice(const RawAddress& address, int setId) = 0; + virtual void ContentControlId(uint32_t ccid) = 0; + virtual void Disconnect(const RawAddress& bd_add) = 0; +}; + +void HandleCcsEvent(uint32_t event, void* param); +bool CCSHandler(uint32_t event, void* param); +void CcpCongestionUpdate(tcc_resp_t * p_data); diff --git a/le_audio/system/bt/bta/include/bta_csip_api.h b/le_audio/system/bt/bta/include/bta_csip_api.h new file mode 100644 index 00000000000..3250e7359ec --- /dev/null +++ b/le_audio/system/bt/bta/include/bta_csip_api.h @@ -0,0 +1,382 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +/****************************************************************************** + * + * This is the public interface file to provide CSIP API's. + * + ******************************************************************************/ + +#ifndef BTA_CSIP_API_H +#define BTA_CSIP_API_H + +//#include "bta_api.h" +#include +#include + +#include "bta_gatt_api.h" //temp + +#include + +#define SIRK_SIZE 16 // SIRK Size +#define UNLOCK_VALUE 0x01 // UNLOCK Value +#define LOCK_VALUE 0x02 // LOCK Value +#define ENCRYPTED_SIRK 0x00 // Encrypted SIRK Type +#define PLAINTEXT_SIRK 0x01 // Plain Text SIRK +#define INVALID_SET_ID 0x10 // Invalid set id + +/* status for applications for LOCK Status changed callback*/ +enum { + LOCK_RELEASED, // (LOCK Released successfully) + LOCK_RELEASED_TIMEOUT, // (LOCK Released by timeout) + ALL_LOCKS_ACQUIRED, // (LOCK Acquired for all requested set members) + SOME_LOCKS_ACQUIRED_REASON_TIMEOUT, // (Request timeout for some set members) + SOME_LOCKS_ACQUIRED_REASON_DISC, // (Some of the set members were disconnected) + LOCK_DENIED, // (Denied by one of the set members) + INVALID_REQUEST_PARAMS, // (Upper layer provided invalid parameters) + LOCK_RELEASE_NOT_ALLOWED, // (Response from remote (PTS)) + INVALID_VALUE, // (Response from remote (PTS)) +}; + +/* LOCK Request Error Codes from set members */ +#define CSIP_LOCK_DENIED 0x80 +#define CSIP_LOCK_RELEASE_NOT_ALLOWED 0x81 +#define CSIP_INVALID_LOCK_VALUE 0x82 +#define CSIP_LOCK_ALREADY_GRANTED 0x84 + +/* Events when CSIP operations are completed */ +#define BTA_CSIP_NEW_SET_FOUND_EVT 1 +#define BTA_CSIP_SET_MEMBER_FOUND_EVT 2 +#define BTA_CSIP_CONN_STATE_CHG_EVT 3 +#define BTA_CSIP_LOCK_STATUS_CHANGED_EVT 4 +#define BTA_CSIP_LOCK_AVAILABLE_EVT 5 +#define BTA_CSIP_SET_SIZE_CHANGED 6 +#define BTA_CSIP_SET_SIRK_CHANGED 7 + +/* CSIP operation completed event*/ +typedef uint8_t tBTA_CSIP_EVT; + +enum { + BTA_CSIP_SUCCESS, + BTA_CSIP_FAILURE, +}; + +/* CSIP Operation Status*/ +typedef uint8_t tBTA_CSIP_STATUS; + +/* CSIP GATT Connection States (to be notified to upper layer)*/ +/* Mapping to BluetoothProfile Connection States*/ +enum { + BTA_CSIP_DISCONNECTED, + BTA_CSIP_CONNECTED = 0x02, +}; + + /* CSIP device GATT Connection state */ +typedef uint8_t tBTA_CSIP_CONN_STATE; + +enum { + BTA_CSIP_CONN_ESTABLISHED = 0x40, // reason values to be decided + BTA_CSIP_CONN_ESTABLISHMENT_FAILED, + BTA_CSIP_APP_ALREADY_CONNECTED, + BTA_CSIP_APP_ALREADY_DISCONNECTED, + BTA_CSIP_APP_DISCONNECTED, + BTA_CSIP_DISCONNECT_WITHOUT_CONNECT, + BTA_CSIP_COORDINATED_SET_NOT_SUPPORTED, +}; + +/* CSIP device GATT Connection state */ +typedef uint8_t tBTA_CSIP_CONN_STATUS; + +/* Params in callback to requesting app when lock status has been changed */ +typedef struct { + uint8_t app_id; + uint8_t set_id; + uint8_t value = UNLOCK_VALUE; + uint8_t status; + std::vector addr; +} tBTA_LOCK_STATUS_CHANGED; + +/* Params in callback to registered app when coordinated set member is discovered */ +typedef struct { + uint8_t set_id; + bluetooth::Uuid uuid; + RawAddress addr; +} tBTA_SET_MEMBER_FOUND; + +/* Params in callback to registered app when discovery for coordinated set is completed */ +/*TODO: not required, to be removed */ +typedef struct { + uint8_t set_id; + bluetooth::Uuid uuid; + std::vector addr; +} tBTA_SET_DISC_CMPL; + +/* params in callback to app when lock is available on earlier denied member*/ +typedef struct { + uint8_t app_id; + uint8_t set_id; + RawAddress addr; +} tBTA_LOCK_AVAILABLE; + +/* params in callback to app space when new set is found*/ +typedef struct { + uint8_t set_id; + uint8_t sirk[SIRK_SIZE]; + uint8_t size; + bool lock_support; + RawAddress addr; + bluetooth::Uuid including_srvc_uuid; +} tBTA_CSIP_NEW_SET_FOUND; + +/* params in callback to app when set size is changed */ +typedef struct { + uint8_t set_id; + uint8_t size; + RawAddress addr; +} tBTA_CSIP_SET_SIZE_CHANGED; + +/* params in callback to app when set sirk is changed */ +typedef struct { + uint8_t set_id; + uint8_t* sirk; + RawAddress addr; +} tBTA_CSIP_SET_SIRK_CHANGED; + +/* params in callback to app when connection state has been changed */ +typedef struct { + uint8_t app_id; + RawAddress addr; + tBTA_CSIP_CONN_STATE state; + tBTA_CSIP_CONN_STATUS status; +} tBTA_CSIP_CONN_STATE_CHANGED; + +/* callbacks params on completion of CSIP operations */ +typedef union { + tBTA_LOCK_STATUS_CHANGED lock_status_param; + tBTA_CSIP_CONN_STATE_CHANGED conn_params; + tBTA_SET_MEMBER_FOUND set_member_param; + tBTA_SET_DISC_CMPL set_disc_cmpl_param; + tBTA_LOCK_AVAILABLE lock_available_param; + tBTA_CSIP_NEW_SET_FOUND new_set_params; + tBTA_CSIP_CONN_STATE_CHANGED conn_chg_params; + tBTA_CSIP_SET_SIZE_CHANGED size_chg_params; + tBTA_CSIP_SET_SIRK_CHANGED sirk_chg_params; +} tBTA_CSIP_DATA; + +/* CSIP callbacks to be given to upper layers*/ + +/* Callback given when one of the operation mentioned in */ +typedef void (tBTA_CSIP_CBACK) (tBTA_CSIP_EVT event, tBTA_CSIP_DATA* p_data); + +/* Callback when application is registered with CSIP */ +typedef void (tBTA_CSIP_CLT_REG_CB) (tBTA_CSIP_STATUS status, uint8_t app_id); + +/* parameters used for api BTA_CsipSetLockValue() */ +typedef struct { + uint8_t app_id; + uint8_t set_id; + uint8_t lock_value; + std::vector members_addr; +} tBTA_SET_LOCK_PARAMS; + +/* Coordinated set details */ +typedef struct { + uint8_t set_id; + uint8_t size; + uint8_t total_discovered; + bool lock_support; + std::vector set_members; + bluetooth::Uuid p_srvc_uuid; +} tBTA_CSIP_CSET; + +using BtaCsipAppRegisteredCb = + base::Callback; + +/********************************************************************************* + * + * Function BTA_RegisterCsipApp + * + * Description This function is called to register application or module to + * to register with CSIP for using CSIP functionalities. + * + * Parameters p_csip_cb: callback to be received in registering app when + * required CSIP operation is completed. + * reg_cb : callback when app/module is registered with CSIP. + * + * Returns None + * + *********************************************************************************/ +extern void BTA_RegisterCsipApp(tBTA_CSIP_CBACK* p_csip_cb, + BtaCsipAppRegisteredCb reg_cb); + +/********************************************************************************* + * + * Function BTA_UnregisterCsipApp + * + * Description This function is called to register application or module to + * to register with CSIP for using CSIP functionalities. + * + * Parameters app_id: Identifier of the app that needs to be unregistered. + * + * Returns None + * + *********************************************************************************/ +extern void BTA_UnregisterCsipApp(uint8_t app_id); + +/********************************************************************************* + * + * Function BTA_CsipSetLockValue + * + * Description This function is called to request or release lock for the + * coordinated set. + * + * Parameters lock_param: parameters to acquire or release lock. + * (tBTA_SET_LOCK_PARAMS). + * + * Returns None + * + *********************************************************************************/ +extern void BTA_CsipSetLockValue(tBTA_SET_LOCK_PARAMS lock_param); + +/********************************************************************************* + * + * Function BTA_CsipGetCoordinatedSet + * + * Description This function is called to fetch details of the coordinated set. + * + * Parameters set_id: identifier of the coordinated set whose details are + * required to be fetched. + * + * Returns tBTA_CSIP_CSET (containing details of coordinated set). + * + *********************************************************************************/ +extern tBTA_CSIP_CSET BTA_CsipGetCoordinatedSet(uint8_t set_id); + +/********************************************************************************* + * + * Function BTA_CsipSetLockValue + * + * Description This function is called to request or release lock for the + * coordinated set. + * + * Parameters None. + * + * Returns vector: (all discovered coordinated set) + * + *********************************************************************************/ +extern std::vector BTA_CsipGetDiscoveredSets(); + +/********************************************************************************* + * + * Function BTA_CsipConnect + * + * Description This function is called to establish GATT Connection. + * + * Parameters bd_addr : Address of the remote device. + * + * Returns None. + * + * Note This shouldnt be used by registered module. CSIP Profile + * internally manages GATT Connection. + * + *********************************************************************************/ +extern void BTA_CsipConnect (uint8_t app_id, const RawAddress& bd_addr); + +/********************************************************************************* + * + * Function BTA_CsipConnect + * + * Description This function is called to establish GATT Connection. + * + * Parameters bd_addr : Address of the remote device. + * + * Returns None. + * + * Note This shouldnt be used by registered module. CSIP Profile + * internally manages GATT Connection. + * + *********************************************************************************/ +extern void BTA_CsipDisconnect (uint8_t app_id, const RawAddress& bd_addr); + + +/********************************************************************************* + * + * Function BTA_CsipEnable + * + * Description This function is invoked to initialize CSIP in BTA layer. + * + * Parameters p_cback: callbacks registered with btif_csip module. + * + * Returns None. + * + * Note This API shouldn't be used by other BT modules. + * + *********************************************************************************/ +extern void BTA_CsipEnable(tBTA_CSIP_CBACK *p_cback); + +/********************************************************************************* + * + * Function BTA_CsipEnable + * + * Description This function is invoked to deinitialize CSIP in BTA layer. + * + * Parameters None. + * + * Returns None. + * + * Note This API shouldn't be used by other BT modules. + * + *********************************************************************************/ +extern void BTA_CsipDisable(); + +/********************************************************************************* + * + * Function BTA_CsipFindCsisInstance + * + * Description This function is invoked to find presence of CSIS service on + * remote device and start coordinated set discovery. + * + * Parameters conn_id: GATT Connection ID used for getting remote services + * status : Status of the discovery procedure + * + * Returns None. + * + * Note This API shouldn't be used by other BT modules. + * + *********************************************************************************/ +extern void BTA_CsipFindCsisInstance(uint16_t conn_id, tGATT_STATUS status, + RawAddress& bd_addr); + +/********************************************************************************* + * + * Function BTA_CsipRemoveUnpairedSetMember + * + * Description This function is called when a given set member is unpaired. + * + * Parameters addr: BD Address of the set member. + * + * Returns None. + * + * Note This API shouldn't be used by other BT modules. + * + *********************************************************************************/ +void BTA_CsipRemoveUnpairedSetMember(RawAddress addr); + +/********************************************************************************* + * + * Function BTA_CsipGetDeviceSetId + * + * Description This API is used to get set id of the remote device. + * + * Parameters addr: BD Address of the set member. + * uuid: UUID of the service which includes CSIS service. + * + * Returns None. + * + *********************************************************************************/ +uint8_t BTA_CsipGetDeviceSetId(RawAddress addr, bluetooth::Uuid uuid); + + +#endif /* BTA_CSIP_API_H */ diff --git a/le_audio/system/bt/bta/include/bta_dm_adv_audio.h b/le_audio/system/bt/bta/include/bta_dm_adv_audio.h new file mode 100644 index 00000000000..f7f59f8e032 --- /dev/null +++ b/le_audio/system/bt/bta/include/bta_dm_adv_audio.h @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ +#ifndef BTA_DM_ADV_AUDIO_H +#define BTA_DM_ADV_AUDIO_H + +#include "stack/include/bt_types.h" +#include "bta/dm/bta_dm_int.h" +#include +#include +#include "bt_target.h" +#include "bta_sys.h" + +#include "bta_gatt_api.h" + +#define UUID_SERVCLASS_CSIS 0x1846 /* Coordinated Set Identification Service */ +#define UUID_SERVCLASS_PACS 0x1850 /* LE AUDIO PACS */ +#define UUID_SERVCLASS_ASCS 0x184E /* LE AUDIO ASCS */ +#define UUID_SERVCLASS_BASS 0x184F /* LE AUDIO BASS */ +#define UUID_SERVCLASS_BAAS 0x1851 /* LE AUDIO BAAS */ +#define UUID_SERVCLASS_BRASS 0x1852 /* LE AUDIO BRASS */ +#define UUID_SERVCLASS_T_ADV_AUDIO 0x1FA0 /* LE AUDIO T_ADV_AUDIO */ +#define UUID_SERVCLASS_CSIS_LOCK 0x2B86 /* LE AUDIO CSIS LOCK */ +#define UUID_SERVCLASS_T_ADV_AUDIO_ROLE_CHAR 0xFE00 /*LE AUDIO CSIS LOCK */ +#define UUID_SERVCLASS_T_ADV_AUDIO_MEDIA_SINK 0x6AD0 +#define UUID_SERVCLASS_T_ADV_AUDIO_VOICE 0x6AD5 +#define UUID_SERVCLASS_T_ADV_AUDIO_CONN_LESS_MEDIA_SINK 0xFFA6 +#define UUID_SERVCLASS_T_ADV_AUDIO_ASSIST 0xFFA7 +#define UUID_SERVCLASS_T_ADV_AUDIO_DELEGATE 0xFFA8 +#define UUID_SERVCLASS_HAS 0x6AD2 +#define UUID_SERVCLASS_SOURCE_CONTEXT 0x2BCE +#define UUID_SERVCLASS_PACS_CT_SUPPORT 0x6AD4 +#define UUID_SERVCLASS_PACS_UMR_SUPPORT 0x6AD1 + +#define BTA_DM_LE_AUDIO_SEARCH_CMPL_EVT 7 + +#define BTA_DM_GROUP_DATA_TYPE 0x2E + +typedef struct { + RawAddress peer_address; + bool in_use =false; + bool is_t_audio_srvc_found = false; + bool is_has_found = false; + std::vector uuids; + int transport; //BTM_UseLeLink(remote_bda); + int conn_id = 0; + int8_t disc_progress = 0; + uint8_t gatt_if; + bool using_bredr_bonding = false; + uint16_t t_role_handle = 0; + uint16_t pacs_char_handle = 0; + bool csip_disc_progress = true; + bool is_csip_support = false; + bool gatt_disc_progress = false; +} tBTA_LE_AUDIO_DEV_INFO; + +typedef struct { + RawAddress p_addr; + RawAddress p_id_addr; + bool is_le_pairing = false; + bool in_use = false; + bool is_dumo_device = false; + uint8_t dev_type; + uint8_t transport; + bool sdp_disc_status = true; +} tBTA_DEV_PAIRING_CB; + +#define MAX_LEA_DEVICES 3 +typedef struct { + tBTA_DEV_PAIRING_CB bta_dev_pair_db[MAX_LEA_DEVICES]; + uint8_t num_devices; + bool is_pairing_progress = false; + std::map dev_addr_map; + std::map dev_rand_addr_map; + RawAddress pending_address; + bool is_sdp_discover = true; +} tBTA_LEA_PAIRING_DB; + + +typedef struct { + tBTA_LE_AUDIO_DEV_INFO bta_lea_dev_info[MAX_LEA_DEVICES]; + RawAddress pending_peer_addr; + RawAddress gatt_op_addr = RawAddress::kEmpty; + int num_lea_devices = 0; + bool bond_progress = false; +} tBTA_LE_AUDIO_DEV_CB; + +extern tBTA_LE_AUDIO_DEV_CB bta_le_audio_dev_cb; +extern bool is_remote_support_adv_audio(const RawAddress remote_bdaddr); +extern void bta_adv_audio_update_bond_db(RawAddress p_bd_addr, uint8_t transport); +extern bool is_le_audio_service(bluetooth::Uuid uuid); +extern int bta_is_adv_audio_valid_bdaddr(RawAddress p_bd_addr); +extern bool bta_is_le_audio_supported(RawAddress p_bd_addr); +extern bool bta_remote_device_is_dumo(RawAddress p_bd_addr); +extern RawAddress bta_get_rem_dev_id_addr(RawAddress p_bd_addr); +extern tBTA_DEV_PAIRING_CB* bta_get_lea_pair_cb(RawAddress peer_addr); +extern bool bta_lea_addr_match(RawAddress p_bd_addr); +extern void bta_dm_reset_lea_pairing_info(RawAddress p_addr); +extern bool bta_is_bredr_primary_transport(RawAddress p_bd_addr); +extern bool bta_is_remote_support_lea(RawAddress p_addr); +extern bool bta_remote_dev_identity_addr_match(RawAddress p_addr); +extern void bta_dm_lea_disc_complete(RawAddress p_bd_addr); +extern void bta_dm_csis_disc_complete(RawAddress p_bd_addr, bool status); +extern tBTA_LE_AUDIO_DEV_INFO* bta_get_lea_ctrl_cb(RawAddress peer_addr); +extern void bta_gap_gatt_read_cb(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data); +extern void bta_get_adv_audio_role(RawAddress peer_address, uint16_t conn_id, + tGATT_STATUS status); +extern void bta_dm_csis_disc_complete(RawAddress p_bd_addr, bool status); +extern void bta_dm_lea_disc_complete(RawAddress p_bd_addr); +extern void bta_add_adv_audio_uuid(RawAddress peer_address, + tBTA_GATT_ID srvc_uuid); +extern tBTA_LE_AUDIO_DEV_INFO* bta_set_lea_ctrl_cb(RawAddress peer_addr); +extern void bta_dm_reset_adv_audio_dev_info(RawAddress p_addr); +extern void bta_dm_set_adv_audio_dev_info(tBTA_GATTC_OPEN* p_data); +extern bool is_adv_audio_group_supported(RawAddress rem_bda, int conn_id); +extern void bta_dm_lea_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data); +extern void bta_dm_adv_audio_gatt_conn(RawAddress p_bd_addr); +extern void bta_dm_adv_audio_close(RawAddress p_bd_addr); +extern tBTA_DEV_PAIRING_CB* bta_get_lea_pair_cb(RawAddress peer_addr); +extern tBTA_DEV_PAIRING_CB* bta_set_lea_pair_cb(RawAddress peer_addr); +extern void bta_dm_reset_lea_pairing_info(RawAddress p_addr); +extern void bta_dm_ble_adv_audio_idaddr_map(RawAddress p_bd_addr, + RawAddress p_id_addr); +extern bool bta_remote_dev_identity_addr_match(RawAddress p_addr); +extern bool bta_lea_is_le_pairing(RawAddress p_bd_addr); +extern bool bta_remote_device_is_dumo(RawAddress p_bd_addr); +extern RawAddress bta_get_rem_dev_id_addr(RawAddress p_bd_addr); +extern void bta_adv_audio_update_bond_db(RawAddress p_bd_addr, uint8_t transport); +extern int bta_is_adv_audio_valid_bdaddr(RawAddress p_bd_addr); +extern void bta_find_adv_audio_group_instance(uint16_t conn_id, tGATT_STATUS status, + RawAddress p_addr); +extern bool bta_is_remote_support_lea(RawAddress p_addr); +extern bool is_gatt_srvc_disc_pending(RawAddress rem_bda); +extern RawAddress bta_get_pseudo_addr_with_id_addr(RawAddress p_addr); +#endif /* BTA_DM_ADV_AUDIO_H*/ diff --git a/le_audio/system/bt/bta/include/bta_mcp_api.h b/le_audio/system/bt/bta/include/bta_mcp_api.h new file mode 100644 index 00000000000..8ac66057d21 --- /dev/null +++ b/le_audio/system/bt/bta/include/bta_mcp_api.h @@ -0,0 +1,475 @@ +/****************************************************************************** + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +#ifndef BTA_MCP_API_H +#define BTA_MCP_API_H + +#include +#include +#include +#include +#include +#include "bta_gatt_api.h" + +using bluetooth::Uuid; +using bluetooth::mcp_server::McpServerCallbacks; + + +#define MAX_PLAYER_NAME_SIZE GATT_MAX_ATTR_LEN +#define MAX_TRACK_TITLE_SIZE GATT_MAX_ATTR_LEN +#define MAX_RESPONSE_DATA_LEN GATT_MAX_ATTR_LEN +#define MAX_MCP_CONNECTION 5 + +#define TRACK_POSITION_UNAVAILABLE 0xFFFFFFFF +#define TRACK_DURATION_UNAVAILABLE 0xFFFFFFFF + +/* Media Control Point Opcodes Supported characteristics bit values */ +#define MCP_MEDIA_CONTROL_SUP_PLAY 1<<0 +#define MCP_MEDIA_CONTROL_SUP_PAUSE 1<<1 +#define MCP_MEDIA_CONTROL_SUP_FAST_REWIND 1<<2 +#define MCP_MEDIA_CONTROL_SUP_FAST_FORWARD 1<<3 +#define MCP_MEDIA_CONTROL_SUP_STOP 1<<4 +#define MCP_MEDIA_CONTROL_SUP_MOVE_RELATIVE 1<<5 +#define MCP_MEDIA_CONTROL_SUP_PREVIOUS_SEGMENT 1<<6 +#define MCP_MEDIA_CONTROL_SUP_NEXT_SEGMENT 1<<7 +#define MCP_MEDIA_CONTROL_SUP_FIRST_SEGMENT 1<<8 +#define MCP_MEDIA_CONTROL_SUP_LAST_SEGMENT 1<<9 +#define MCP_MEDIA_CONTROL_SUP_GOTO_SEGMENT 1<<10 +#define MCP_MEDIA_CONTROL_SUP_PREVIOUS_TRACK 1<<11 +#define MCP_MEDIA_CONTROL_SUP_NEXT_TRACK 1<<12 +#define MCP_MEDIA_CONTROL_SUP_FIRST_TRACK 1<<13 +#define MCP_MEDIA_CONTROL_SUP_LAST_TRACK 1<<14 +#define MCP_MEDIA_CONTROL_SUP_GOTO_TRACK 1<<15 +#define MCP_MEDIA_CONTROL_SUP_PREVIOUS_GROUP 1<<16 +#define MCP_MEDIA_CONTROL_SUP_NEXT_GROUP 1<<17 +#define MCP_MEDIA_CONTROL_SUP_FIRST_GROUP 1<<18 +#define MCP_MEDIA_CONTROL_SUP_LAST_GROUP 1<<19 +#define MCP_MEDIA_CONTROL_SUP_GOTO_GROUP 1<<20 + +//media control point opcodes +#define MCP_MEDIA_CONTROL_OPCODE_PLAY 0x01 +#define MCP_MEDIA_CONTROL_OPCODE_PAUSE 0x02 +#define MCP_MEDIA_CONTROL_OPCODE_FAST_REWIND 0x03 +#define MCP_MEDIA_CONTROL_OPCODE_FAST_FORWARD 0x04 +#define MCP_MEDIA_CONTROL_OPCODE_STOP 0x05 +#define MCP_MEDIA_CONTROL_OPCODE_PREV_TRACK 0x30 +#define MCP_MEDIA_CONTROL_OPCODE_NEXT_TRACK 0x31 +#define MCP_MEDIA_CONTROL_OPCODE_MOVE_RELATIVE 0x10 + +/* Playing Order Supported characteristic bit values */ +#define MCP_PLAYING_OREDR_SINGLE_ONCE 1<<0 +#define MCP_PLAYING_OREDR_SINGLE_REPEAT 1<<1 +#define MCP_PLAYING_OREDR_IN_ORDER_ONCE 1<<2 +#define MCP_PLAYING_OREDR_IN_ORDER_REPEAT 1<<3 +#define MCP_PLAYING_OREDR_OLDEST_ONCE 1<<4 +#define MCP_PLAYING_OREDR_OLDEST_REPEAT 1<<5 +#define MCP_PLAYING_OREDR_NEWEST_ONCE 1<<6 +#define MCP_PLAYING_OREDR_NEWEST_REPEAT 1<<7 +#define MCP_PLAYING_OREDR_SHUFFLE_ONCE 1<<8 +#define MCP_PLAYING_OREDR_SHUFFLE_REPEAT 1<<9 + +#define MCP_DEFAULT_MEDIA_CTRL_SUPP_FEAT MCP_MEDIA_CONTROL_SUP_PLAY| \ + MCP_MEDIA_CONTROL_SUP_PAUSE| \ + MCP_MEDIA_CONTROL_SUP_FAST_REWIND| \ + MCP_MEDIA_CONTROL_SUP_FAST_FORWARD| \ + MCP_MEDIA_CONTROL_SUP_STOP| \ + MCP_MEDIA_CONTROL_SUP_PREVIOUS_TRACK| \ + MCP_MEDIA_CONTROL_SUP_NEXT_TRACK + +typedef enum { + // TO-DO: Naming in such a way to distinguish BTIF and lower layer events + MCP_NONE_EVENT = 70, + MCP_INIT_EVENT, + MCP_CLEANUP_EVENT, + MCP_MEDIA_STATE_UPDATE, + MCP_MEDIA_PLAYER_NAME_UPDATE, + MCP_MEDIA_SUPPORTED_OPCODE_UPDATE, + MCP_MEDIA_CONTROL_POINT_UPDATE, + MCP_PLAYING_ORDER_SUPPORTED_UPDATE, + MCP_PLAYING_ORDER_UPDATE, + MCP_TRACK_CHANGED_UPDATE, + MCP_TRACK_POSITION_UPDATE, + MCP_TRACK_DURATION_UPDATE, + MCP_TRACK_TITLE_UPDATE, + MCP_CCID_UPDATE, + MCP_ACTIVE_DEVICE_UPDATE, + MCP_ACTIVE_PROFILE, + + //local event to handle in mcp state machine, + MCP_PLAYING_ORDER_SUPPORTED_READ, + MCP_PLAYING_ORDER_READ, + MCP_MEDIA_STATE_READ, + MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_READ, + MCP_MEDIA_PLAYER_NAME_READ, + MCP_TRACK_TITLE_READ, + MCP_TRACK_POSITION_READ, + MCP_TRACK_DURATION_READ, + MCP_CCID_READ, + MCP_SEEKING_SPEED_READ, + MCP_MEDIA_STATE_READ_DESCRIPTOR, + MCP_MEDIA_CONTROL_POINT_READ_DESCRIPTOR, + MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_DESCRIPTOR_READ, + MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_READ, + MCP_TRACK_CHANGED_DESCRIPTOR_READ, + MCP_TRACK_TITLE_DESCRIPTOR_READ, + MCP_TRACK_POSITION_DESCRIPTOR_READ, + MCP_TRACK_DURATION_DESCRIPTOR_READ, + MCP_PLAYING_ORDER_DESCRIPTOR_READ, + MCP_MEDIA_STATE_DESCRIPTOR_WRITE, + MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_WRITE, + MCP_MEDIA_CONTROL_POINT_DESCRIPTOR_WRITE, + MCP_MEDIA_CONTROL_POINT_SUPPORTED_DESCRIPTOR_WRITE, + MCP_TRACK_CHANGED_DESCRIPTOR_WRITE, + MCP_TRACK_TITLE_DESCRIPTOR_WRITE, + MCP_TRACK_POSITION_DESCRIPTOR_WRITE, + MCP_TRACK_DURATION_DESCRIPTOR_WRITE, + MCP_PLAYING_ORDER_DESCRIPTOR_WRITE, + MCP_MEDIA_CONTROL_POINT_WRITE, + MCP_PLAYING_ORDER_WRITE, + MCP_TRACK_POSITION_WRITE, +//device state event + MCP_NOTIFY_ALL, + MCP_WRITE_RSP, + MCP_READ_RSP, + MCP_DESCRIPTOR_WRITE_RSP, + MCP_DESCRIPTOR_READ_RSP, + MCP_CONNECTION, + MCP_DISCONNECTION, + MCP_CONNECTION_UPDATE, + MCP_CONGESTION_UPDATE, + MCP_PHY_UPDATE, + MCP_MTU_UPDATE, + MCP_SET_ACTIVE_DEVICE, + MCP_CONNECTION_CLOSE_EVENT, + MCP_BOND_STATE_CHANGE_EVENT, +//media write op code event + MCP_MEDIA_CONTROL_PLAY_READ_REQ, + MCP_MEDIA_CONTROL_PAUSE_REQ, + MCP_MEDIA_CONTROL_FAST_FORWARD_REQ, + MCP_MEDIA_CONTROL_FAST_REWIND_REQ, + MCP_MEDIA_CONTROL_MOVE_RELATIVE_REQ, + MCP_MEDIA_CONTROL_STOP_REQ, + MCP_MEDIA_CONTROL_NEXT_TRACK_REQ, + MCP_MEDIA_CONTROL_PREVIOUS_TRACK_REQ, + MCP_PLAYING_OREDR_SHUFFLE_REPEAT_REQ, + +}mcp_event_t; + +//state handler declaration +typedef bool (*mcp_handler)(uint32_t event, void* param, uint8_t state); +typedef enum { + //media conrol point success or error code + MCP_STATUS_SUCCESS = 1, + MCP_OPCODE_NOT_SUPPORTED, + MCP_MEDIA_PLAYER_INACTIVE, + MCP_COMMAND_CANNOT_COMPLETED, + + BT_STATUS_DEVICE_NOT_CONNECTED, + BT_STATUS_HANLDE_NOT_MATCHED, +}mcp_error_t; + +typedef enum { + MCP_DISCONNECTED = 0x00, + MCP_CONNECTED, + MCP_MAX_DEVICE_STATE +} remote_device_state_t; + +typedef enum { + MCP_STATE_INACTIVE = 0x00, + MCP_STATE_PLAYING, + MCP_STATE_PAUSE, + MCP_STATE_SEEKING, + MCP_MAX_MEDIA_STATE +} mcp_state_t; + +typedef struct { + uint8_t media_state; + uint16_t media_ctrl_point; + uint32_t media_supported_feature; + uint8_t player_name[MAX_PLAYER_NAME_SIZE]; + uint16_t player_name_len; + uint8_t track_changed; + int32_t duration; + int32_t position; + uint16_t playing_order_supported; + uint8_t playing_order_value; + uint8_t title[MAX_TRACK_TITLE_SIZE]; + uint16_t track_title_len; + uint8_t ccid; + uint8_t seeking_speed; + mcp_handler MediaStateHandlerPointer[MCP_MAX_MEDIA_STATE]; +} MediaPlayerInfo_t; + +typedef struct { + int server_if; + Uuid mcs_service_uuid; + Uuid media_state_uuid; + Uuid media_player_name_uuid; + Uuid media_control_point_uuid; + Uuid media_control_point_opcode_supported_uuid; + Uuid track_changed_uuid; + Uuid track_title_uuid; + Uuid track_duration_uuid; + Uuid track_position_uuid; + Uuid playing_order_supported_uuid; + Uuid playing_order_uuid; + Uuid ccid_uuid; + Uuid seeking_speed_uuid; + //handle for characteristics + uint16_t media_state_handle; + uint16_t media_player_name_handle; + uint16_t media_control_point_opcode_supported_handle; + uint16_t media_control_point_handle; + uint16_t track_changed_handle; + uint16_t track_title_handle; + uint16_t track_duration_handle; + uint16_t track_position_handle; + uint16_t playing_order_supported_handle; + uint16_t playing_order_handle; + uint16_t ccid_handle; + uint16_t seeking_speed_handle; + uint16_t media_state_desc; + uint16_t media_player_name_desc; + uint16_t media_control_point_opcode_supported_desc; + uint16_t media_control_point_desc; + uint16_t track_changed_desc; + uint16_t track_title_desc; + uint16_t track_duration_desc; + uint16_t track_position_desc; + uint16_t playing_order_supported_desc; + uint16_t playing_order_desc; + uint16_t ccid_desc; + uint16_t seeking_speed_desc; +} mcsServerServiceInfo_t; + +typedef struct { + remote_device_state_t state; + uint8_t active_profile; + uint16_t media_state_notify; + uint16_t media_player_name_notify; + uint16_t media_control_point_notify; + uint16_t media_control_point_opcode_supported_notify; + uint16_t track_changed_notify; + uint16_t track_duration_notify; + uint16_t track_title_notify; + uint16_t track_position_notify; + uint16_t playing_order_notify; + uint16_t seeking_speed_notify; + bool congested; + int conn_id; + int trans_id; + int timeout; + int latency; + int interval; + int rx_phy; + int tx_phy; + int mtu; + RawAddress peer_bda; + mcp_handler DeviceStateHandlerPointer[MCP_MAX_DEVICE_STATE]; +}RemoteDevice; + +typedef struct { + std::vector address; + int set_id; +}ActiveDevice; + +typedef struct { + uint8_t status; + uint16_t notification; + uint32_t trans_id; + uint32_t desc_handle; + uint32_t char_handle; + bool need_rsp; + bool prep_rsp; + //is to send notification + uint8_t *data; + uint16_t len; +}tMCP_DESC_WRITE; + +typedef struct { + uint8_t status; + uint32_t trans_id; + uint32_t desc_handle; + uint32_t char_handle; +}tMCP_DESC_READ; + +typedef struct { + bool is_long; + uint8_t status; + uint32_t trans_id; + uint32_t char_handle; +}tMCP_READ; + +typedef struct { + uint8_t status; + bool need_rsp; + bool prep_rsp; + uint16_t offset; + uint32_t trans_id; + uint16_t char_handle; + //is to send notification + uint8_t data[GATT_MAX_ATTR_LEN]; +}tMCP_WRITE; + +typedef struct { + uint8_t status; + RemoteDevice remoteDevice; +}tMCP_CONNECTION; + +typedef struct { + uint8_t status; + int timeout; + int latency; + int interval; +}tMCP_CONN_UPDATE; + +typedef struct { + uint8_t status; + RemoteDevice *remoteDevice; +}tMCP_DISCONNECTION; + +typedef struct { + bool congested; + RemoteDevice *remoteDevice; +} tMCP_CONGESTION; + +typedef struct { + uint8_t status; + uint8_t tx_phy; + uint8_t rx_phy; + RemoteDevice *remoteDevice; +} tMCP_PHY; + +typedef struct { + uint8_t status; + uint16_t mtu; + RemoteDevice *remoteDevice; +} tMCP_MTU; + +typedef struct { + uint8_t state; +}tMCP_MEDIA_STATE; + +typedef struct { + uint32_t req_opcode; + uint8_t result; +}tMCP_MEDIA_CONTROL_POINT; + +typedef struct { + uint32_t supported; +}tMCP_MEDIA_OPCODE_SUPPORT; + +typedef struct { + uint8_t *name; + uint16_t len; +}tMCP_MEDIA_PLAYER_NAME; + +typedef struct { + bool status; +}tMCP_TRACK_CHANGED; + +typedef struct { + int32_t position; +}tMCP_TRACK_POSTION; + +typedef struct { + uint32_t duration; +}tMCP_TRACK_DURATION; + +typedef struct { + uint8_t *title; + uint16_t len; +}tMCP_TRACK_TITLE; + +typedef struct { + uint8_t ccid; +}tMCP_CONTENT_CONTROL_ID; + +typedef struct { + uint8_t seek_speed; +}tMCP_SEEKING_SPEED_CONTROL_ID; + +typedef struct { + RawAddress addr; +}tMCP_CONNECTION_CLOSE; + +typedef struct { + RawAddress addr; + int state; +}tMCP_BOND_STATE_CHANGE; + +typedef struct { + uint32_t order_supported; +}tMCP_PLAYING_ORDER_SUPPORT; + +typedef struct { + uint8_t order; +}tMCP_PLAYING_ORDER; + +typedef struct { + RawAddress address; + uint16_t set_id; + uint8_t profile; +}tMCP_SET_ACTIVE_DEVICE; + +typedef struct { + uint8_t *data; + uint16_t len; +}tMCP_MEDIA_UPDATE; + +union tMCP_MEDIA_OPERATION{ + tMCP_MEDIA_UPDATE MediaUpdateOp; + tMCP_SET_ACTIVE_DEVICE SetActiveDeviceOp; + tMCP_DESC_WRITE WriteDescOp; + tMCP_DESC_READ ReadDescOp; + tMCP_WRITE WriteOp; + tMCP_READ ReadOp; + tMCP_CONNECTION ConnectionOp; + tMCP_CONN_UPDATE ConnectionUpdateOp; + tMCP_DISCONNECTION DisconnectionOp; + tMCP_CONGESTION CongestionOp; + tMCP_MTU MtuOp; + tMCP_PHY PhyOp; +}; + +typedef union tMCP_MEDIA_OPERATION tMCP_MEDIA_OPERATION; + +struct mcp_resp_t { + uint32_t event; + uint16_t handle; + uint16_t status; + RemoteDevice *remoteDevice; + tGATTS_RSP rsp_value; + tMCP_MEDIA_OPERATION oper; +}; + +typedef struct mcp_resp_t mcp_resp_t; + +class McpServer { + public: + virtual ~McpServer() = default; + static void Initialize(bluetooth::mcp_server::McpServerCallbacks* callbacks, Uuid ap_id); + static void CleanUp(); + static McpServer* Get(); + static bool isMcpServiceRunnig(); + virtual void MediaState(uint8_t state) = 0; + virtual void MediaPlayerName(uint8_t* player_name) = 0; + virtual void MediaControlPointOpcodeSupported(uint32_t feature) = 0; + virtual void MediaControlPoint(uint8_t value) = 0; + virtual void TrackChanged(bool status) = 0; + virtual void TrackDuration(int32_t duration) = 0; + virtual void TrackTitle(uint8_t* title) = 0; + virtual void TrackPosition(int32_t position) = 0; + virtual void PlayingOrderSupported(uint16_t order) = 0; + virtual void PlayingOrder(uint8_t value) = 0; + virtual void SetActiveDevice(const RawAddress& address, int setId, int profile) = 0; + virtual void ContentControlId(uint8_t ccid) = 0; + virtual void DisconnectMcp(const RawAddress& address) = 0; + virtual void BondStateChange(const RawAddress& address, int state) = 0; +}; + +void McpCongestionUpdate(mcp_resp_t *p_data); + +#endif // BTA_MCP_API_H diff --git a/le_audio/system/bt/bta/include/bta_pacs_client_api.h b/le_audio/system/bt/bta/include/bta_pacs_client_api.h new file mode 100644 index 00000000000..1a364d8dc5c --- /dev/null +++ b/le_audio/system/bt/bta/include/bta_pacs_client_api.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * 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. + */ +#pragma once + +#include +#include + +namespace bluetooth { +namespace bap { +namespace pacs { + +class PacsClient { + public: + virtual ~PacsClient() = default; + + static void Initialize(bluetooth::bap::pacs::PacsClientCallbacks* callbacks); + static void CleanUp(uint16_t client_id); + static PacsClient* Get(); + virtual void Connect(uint16_t client_id, const RawAddress& address, + bool is_direct) = 0; + virtual void Disconnect(uint16_t client_id, + const RawAddress& address) = 0; + virtual void StartDiscovery(uint16_t client_id, + const RawAddress& address) = 0; + virtual void GetAudioAvailability(uint16_t client_id, + const RawAddress& address) = 0; +}; + +} // namespace pacs +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/include/bta_vcp_controller_api.h b/le_audio/system/bt/bta/include/bta_vcp_controller_api.h new file mode 100644 index 00000000000..8518262a91c --- /dev/null +++ b/le_audio/system/bt/bta/include/bta_vcp_controller_api.h @@ -0,0 +1,148 @@ +/* + *Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/****************************************************************************** + * + * Copyright 2018 The Android Open Source Project + * + * 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. + * + ******************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +enum { + BTA_VCP_DISCONNECTED = 0x0, + BTA_VCP_CONNECTING, + BTA_VCP_CONNECTED, + BTA_VCP_DISCONNECTING, +}; + +enum { + VCS_VOLUME_STATE_READ_CMPL_EVT = 0x0, + VCS_VOLUME_FLAGS_READ_CMPL_EVT, + VCS_VOLUME_STATE_CCC_WRITE_CMPL_EVT, + VCS_VOLUME_FLAGS_CCC_WRITE_CMPL_EVT, +}; + +enum { + VCS_CONTROL_POINT_OP_REL_VOLUME_DOWN = 0x0, + VCS_CONTROL_POINT_OP_REL_VOLUME_UP, + VCS_CONTROL_POINT_OP_UNMUTE_REL_VOLUME_DOWN, + VCS_CONTROL_POINT_OP_UNMUTE_REL_VOLUME_UP, + VCS_CONTROL_POINT_OP_SET_ABS_VOL, + VCS_CONTROL_POINT_OP_UNMUTE, + VCS_CONTROL_POINT_OP_MUTE, +}; + +enum { + VCS_UNMUTE_STATE = 0x0, + VCS_MUTE_STATE, +}; + +typedef struct { + uint8_t op_id; + uint8_t change_counter; + uint8_t volume_setting; +} SetAbsVolumeOp; + +typedef struct { + uint8_t op_id; + uint8_t change_counter; +} MuteOp; + +typedef struct { + uint8_t op_id; + uint8_t change_counter; +} UnmuteOp; + +struct VolumeState { + uint8_t volume_setting; + uint8_t mute; + uint8_t change_counter; + + VolumeState() + : volume_setting(0), + mute(0), + change_counter(0) {} +}; + +struct VolumeControlService { + uint16_t volume_state_handle; + uint16_t volume_control_point_handle; + uint16_t volume_flags_handle; + uint16_t volume_state_ccc_handle; + uint16_t volume_flags_ccc_handle; + + VolumeState volume_state; + uint8_t volume_flags; + uint8_t pending_volume_setting; + uint8_t pending_mute_setting; + uint8_t retry_cmd; + + VolumeControlService() + : volume_state_handle(0), + volume_control_point_handle(0), + volume_flags_handle(0), + volume_state_ccc_handle(0), + volume_flags_ccc_handle(0), + volume_state(), + volume_flags(0), + pending_volume_setting(0), + pending_mute_setting(0), + retry_cmd(0) {} +}; + +struct RendererDevice { + RawAddress address; + uint16_t conn_id; + uint8_t state; + bool bg_conn; + bool service_changed_rcvd; + VolumeControlService vcs; + + RendererDevice(const RawAddress& address) + : address(address), + conn_id(0), + state(BTA_VCP_DISCONNECTED), + bg_conn(false), + service_changed_rcvd(false), + vcs() {} + + RendererDevice() : RendererDevice(RawAddress::kEmpty) {} +}; + +class VcpController { + public: + virtual ~VcpController() = default; + + static void Initialize(bluetooth::vcp_controller::VcpControllerCallbacks* callbacks); + static void CleanUp(); + static VcpController* Get(); + static bool IsVcpControllerRunning(); + static int GetDeviceCount(); + + virtual void Connect(const RawAddress& address, bool isDirect) = 0; + virtual void Disconnect(const RawAddress& address) = 0; + virtual void SetAbsVolume(const RawAddress& address, uint8_t volume) = 0; + virtual void Mute(const RawAddress& address) = 0; + virtual void Unmute(const RawAddress& address) = 0; +}; + diff --git a/le_audio/system/bt/bta/include/connected_iso_api.h b/le_audio/system/bt/bta/include/connected_iso_api.h new file mode 100644 index 00000000000..99406937cb9 --- /dev/null +++ b/le_audio/system/bt/bta/include/connected_iso_api.h @@ -0,0 +1,97 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +#pragma once + +#include +#include + +#include "stack/include/bt_types.h" +#include + +namespace bluetooth { +namespace bap { +namespace cis { + +using bluetooth::bap::ucast::CISConfig; +using bluetooth::bap::ucast::CIGConfig; + +constexpr uint8_t DIR_TO_AIR = 0x1 << 0; +constexpr uint8_t DIR_FROM_AIR = 0x1 << 1; + +typedef uint8_t sdu_interval_t[3]; + +enum class CisState { + INVALID = 0, + READY, + DESTROYING, + ESTABLISHING, + ESTABLISHED +}; + +enum class CigState { + INVALID = 0, + IDLE, + CREATING, + CREATED, + REMOVING +}; + +enum IsoHciStatus { + ISO_HCI_SUCCESS = 0, + ISO_HCI_FAILED, + ISO_HCI_IN_PROGRESS +}; + +class CisInterfaceCallbacks { + public: + virtual ~CisInterfaceCallbacks() = default; + + /** Callback for connection state change */ + virtual void OnCigState(uint8_t cig_id, CigState state) = 0; + + virtual void OnCisState(uint8_t cig_id, uint8_t cis_id, + uint8_t direction, CisState state) = 0; +}; + +class CisInterface { + public: + virtual ~CisInterface() = default; + + static void Initialize(CisInterfaceCallbacks* callbacks); + static void CleanUp(); + static CisInterface* Get(); + + virtual CigState GetCigState(const uint8_t &cig_id); + + virtual CisState GetCisState(const uint8_t &cig_id, uint8_t cis_id); + + virtual uint8_t GetCisCount(const uint8_t &cig_id) = 0; + + virtual IsoHciStatus CreateCig(RawAddress client_peer_bda, + bool reconfig, + CIGConfig &cig_config, + std::vector &cis_configs) = 0; + + virtual IsoHciStatus RemoveCig(RawAddress peer_bda, + uint8_t cig_id) = 0; + + virtual IsoHciStatus CreateCis(uint8_t cig_id, std::vector cis_ids, + RawAddress peer_bda) = 0; + + virtual IsoHciStatus DisconnectCis(uint8_t cig_id, uint8_t cis_id, + uint8_t direction) = 0; + + virtual IsoHciStatus SetupDataPath(uint8_t cig_id, uint8_t cis_id, + uint8_t direction, + uint8_t path_id) = 0; + + virtual IsoHciStatus RemoveDataPath(uint8_t cig_id, uint8_t cis_id, + uint8_t direction) = 0; +}; + +} // namespace ucast +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/bta/mcp/bta_mcp_main.cc b/le_audio/system/bt/bta/mcp/bta_mcp_main.cc new file mode 100644 index 00000000000..f6959bd4451 --- /dev/null +++ b/le_audio/system/bt/bta/mcp/bta_mcp_main.cc @@ -0,0 +1,3075 @@ +/****************************************************************************** + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + + + +/****************************************************************************** + * + * This file contains the MCP server main functions and state machine. + * + ******************************************************************************/ + +#include "bta_api.h" +#include "bt_target.h" +#include "bta_mcp_api.h" +#include "gatts_ops_queue.h" +#include "btm_int.h" +#include "device/include/controller.h" +#include "osi/include/properties.h" +#include "bta_sys.h" +#include "btif_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using bluetooth::Uuid; +using bluetooth::bap::GattsOpsQueue; + +class McpServerImpl; +static McpServerImpl *instance; + +//global variables +mcsServerServiceInfo_t mcsServerServiceInfo; +MediaPlayerInfo_t mediaPlayerInfo; + +void HandleMcsEvent(uint32_t event, void* param); + +typedef base::Callback service)> + OnMcpServiceAdded; + +static void OnMcpServiceAddedCb(uint8_t status, int serverIf, + std::vector service); + +/* Media state handlers */ +static bool MediaStateInactiveHandler(uint32_t event, void* param, uint8_t state); +static bool MediaStatePauseHandler(uint32_t event, void* param, uint8_t state); +static bool MediaStatePlayingHandler(uint32_t event, void* param, uint8_t state); +static bool MediaStateSeekingHandler(uint32_t event, void* param, uint8_t state); + +/* Connection state machine handlers */ +static bool DeviceStateConnectionHandler(uint32_t event, void* param, uint8_t state); +static bool DeviceStateDisconnectedHandler(uint32_t event, void* param, uint8_t state); + +Uuid MCS_UUID = Uuid::FromString("1848"); +Uuid GMCS_UUID = Uuid::FromString("1849"); + +Uuid DESCRIPTOR_UUID = Uuid::FromString("2902"); + +Uuid GMCS_MEDIA_STATE_UUID = Uuid::FromString("2BA3"); +Uuid GMCS_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED = Uuid::FromString("2BA5"); +Uuid GMCS_MEDIA_PLAYER_NAME_UUID = Uuid::FromString("2B93"); +Uuid GMCS_MEDIA_CONTROL_POINT = Uuid::FromString("2BA4"); +Uuid GMCS_TRACK_CHANGED = Uuid::FromString("2B96"); +Uuid GMCS_TRACK_TITLE = Uuid::FromString("2B97"); +Uuid GMCS_TRACK_DURATION = Uuid::FromString("2B98"); +Uuid GMCS_TRACK_POSITION = Uuid::FromString("2B99"); +Uuid GMCS_PLAYING_ORDER_SUPPORTED = Uuid::FromString("2BA2"); +Uuid GMCS_PLAYING_ORDER = Uuid::FromString("2BA1"); +Uuid GMCS_CONTENT_CONTROLID = Uuid::FromString("2BBA"); +Uuid GMCS_SEEKING_SPEED_UUID = Uuid::FromString("2B9B"); + + + bool is_pts_running() { + char value[PROPERTY_VALUE_MAX] = {'\0'}; + bool pts_test_enabled = false; + osi_property_get("persist.vendor.service.bt.mcs.pts", value, "false"); + pts_test_enabled = (strcmp(value, "true") == 0); + LOG(INFO) << "pts test enabled " << pts_test_enabled; + return pts_test_enabled; + } + + int playing_order_opcode(int data) { + int event = 0; + if (data & MCP_PLAYING_OREDR_SHUFFLE_REPEAT) { + event = MCP_PLAYING_OREDR_SHUFFLE_REPEAT_REQ; + } else + LOG(INFO) << "opcode not matched or not supported"; + return event; + } + + bool is_opcode_supported(int data) { + LOG(INFO) << __func__ << "data " << data << "media_supported_feature " << mediaPlayerInfo.media_supported_feature; + switch (data) { + case MCP_MEDIA_CONTROL_OPCODE_PLAY: + return (mediaPlayerInfo.media_supported_feature & MCP_MEDIA_CONTROL_SUP_PLAY); + case MCP_MEDIA_CONTROL_OPCODE_PAUSE: + return (mediaPlayerInfo.media_supported_feature & MCP_MEDIA_CONTROL_SUP_PAUSE); + case MCP_MEDIA_CONTROL_OPCODE_FAST_REWIND: + return (mediaPlayerInfo.media_supported_feature & MCP_MEDIA_CONTROL_SUP_FAST_REWIND); + case MCP_MEDIA_CONTROL_OPCODE_FAST_FORWARD: + return (mediaPlayerInfo.media_supported_feature & MCP_MEDIA_CONTROL_SUP_FAST_FORWARD); + case MCP_MEDIA_CONTROL_OPCODE_STOP: + return (mediaPlayerInfo.media_supported_feature & MCP_MEDIA_CONTROL_SUP_STOP); + case MCP_MEDIA_CONTROL_OPCODE_PREV_TRACK: + return (mediaPlayerInfo.media_supported_feature & MCP_MEDIA_CONTROL_SUP_PREVIOUS_TRACK); + case MCP_MEDIA_CONTROL_OPCODE_NEXT_TRACK: + return (mediaPlayerInfo.media_supported_feature & MCP_MEDIA_CONTROL_SUP_NEXT_TRACK); + + // Fallthrough for all unknown key mappings + default: + LOG(INFO) << __func__ << "opcode is not supported"; + return false; + } + } + +const char* get_mcp_event_name(uint32_t event) { + switch (event) { + CASE_RETURN_STR(MCP_INIT_EVENT) + CASE_RETURN_STR(MCP_CLEANUP_EVENT) + CASE_RETURN_STR(MCP_MEDIA_STATE_UPDATE) + CASE_RETURN_STR(MCP_MEDIA_PLAYER_NAME_UPDATE) + CASE_RETURN_STR(MCP_MEDIA_SUPPORTED_OPCODE_UPDATE) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_POINT_UPDATE) + CASE_RETURN_STR(MCP_PLAYING_ORDER_SUPPORTED_UPDATE) + CASE_RETURN_STR(MCP_PLAYING_ORDER_UPDATE) + CASE_RETURN_STR(MCP_TRACK_CHANGED_UPDATE) + CASE_RETURN_STR(MCP_TRACK_POSITION_UPDATE) + CASE_RETURN_STR(MCP_TRACK_DURATION_UPDATE) + CASE_RETURN_STR(MCP_TRACK_TITLE_UPDATE) + CASE_RETURN_STR(MCP_CCID_UPDATE) + CASE_RETURN_STR(MCP_ACTIVE_DEVICE_UPDATE) + CASE_RETURN_STR(MCP_ACTIVE_PROFILE) + + //local event to handle in mcp state machine, + CASE_RETURN_STR(MCP_PLAYING_ORDER_SUPPORTED_READ) + CASE_RETURN_STR(MCP_PLAYING_ORDER_READ) + CASE_RETURN_STR(MCP_MEDIA_STATE_READ) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_READ) + CASE_RETURN_STR(MCP_MEDIA_PLAYER_NAME_READ) + CASE_RETURN_STR(MCP_TRACK_TITLE_READ) + CASE_RETURN_STR(MCP_TRACK_POSITION_READ) + CASE_RETURN_STR(MCP_TRACK_DURATION_READ) + CASE_RETURN_STR(MCP_CCID_READ) + CASE_RETURN_STR(MCP_SEEKING_SPEED_READ) + CASE_RETURN_STR(MCP_MEDIA_STATE_READ_DESCRIPTOR) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_POINT_READ_DESCRIPTOR) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_DESCRIPTOR_READ) + CASE_RETURN_STR(MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_READ) + CASE_RETURN_STR(MCP_TRACK_CHANGED_DESCRIPTOR_READ) + CASE_RETURN_STR(MCP_TRACK_TITLE_DESCRIPTOR_READ) + CASE_RETURN_STR(MCP_TRACK_POSITION_DESCRIPTOR_READ) + CASE_RETURN_STR(MCP_TRACK_DURATION_DESCRIPTOR_READ) + CASE_RETURN_STR(MCP_PLAYING_ORDER_DESCRIPTOR_READ) + CASE_RETURN_STR(MCP_MEDIA_STATE_DESCRIPTOR_WRITE) + CASE_RETURN_STR(MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_WRITE) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_POINT_DESCRIPTOR_WRITE) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_POINT_SUPPORTED_DESCRIPTOR_WRITE) + CASE_RETURN_STR(MCP_TRACK_CHANGED_DESCRIPTOR_WRITE) + CASE_RETURN_STR(MCP_TRACK_TITLE_DESCRIPTOR_WRITE) + CASE_RETURN_STR(MCP_TRACK_POSITION_DESCRIPTOR_WRITE) + CASE_RETURN_STR(MCP_TRACK_DURATION_DESCRIPTOR_WRITE) + CASE_RETURN_STR(MCP_PLAYING_ORDER_DESCRIPTOR_WRITE) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_POINT_WRITE) + CASE_RETURN_STR(MCP_PLAYING_ORDER_WRITE) + CASE_RETURN_STR(MCP_TRACK_POSITION_WRITE) + + CASE_RETURN_STR(MCP_NOTIFY_ALL) + CASE_RETURN_STR(MCP_WRITE_RSP) + CASE_RETURN_STR(MCP_READ_RSP) + CASE_RETURN_STR(MCP_DESCRIPTOR_WRITE_RSP) + CASE_RETURN_STR(MCP_DESCRIPTOR_READ_RSP) + CASE_RETURN_STR(MCP_CONNECTION) + CASE_RETURN_STR(MCP_DISCONNECTION) + CASE_RETURN_STR(MCP_CONNECTION_UPDATE) + CASE_RETURN_STR(MCP_CONGESTION_UPDATE) + CASE_RETURN_STR(MCP_PHY_UPDATE) + CASE_RETURN_STR(MCP_MTU_UPDATE) + CASE_RETURN_STR(MCP_SET_ACTIVE_DEVICE) + CASE_RETURN_STR(MCP_CONNECTION_CLOSE_EVENT) + CASE_RETURN_STR(MCP_BOND_STATE_CHANGE_EVENT) + //media write op code event + CASE_RETURN_STR(MCP_MEDIA_CONTROL_PLAY_READ_REQ) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_PAUSE_REQ) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_FAST_FORWARD_REQ) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_FAST_REWIND_REQ) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_MOVE_RELATIVE_REQ) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_STOP_REQ) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_NEXT_TRACK_REQ) + CASE_RETURN_STR(MCP_MEDIA_CONTROL_PREVIOUS_TRACK_REQ) + CASE_RETURN_STR(MCP_PLAYING_OREDR_SHUFFLE_REPEAT_REQ) + default: + return "Unknown Event"; + } +} + +const char* get_mcp_media_state_name(uint8_t media_state) { + switch (media_state) { + CASE_RETURN_STR(MCP_STATE_INACTIVE) + CASE_RETURN_STR(MCP_STATE_PLAYING) + CASE_RETURN_STR(MCP_STATE_PAUSE) + CASE_RETURN_STR(MCP_STATE_SEEKING) + default: + return "Unknown Media State"; + } +} + +class RemoteDevices { + private: + ActiveDevice activeDevice; + //int max_connection; + public: + bool Add(RemoteDevice device) { + if (devices.size() == MAX_MCP_CONNECTION) { + return false; + } + if (FindByAddress(device.peer_bda) != nullptr) return false; + device.DeviceStateHandlerPointer[MCP_DISCONNECTED] = DeviceStateDisconnectedHandler; + device.DeviceStateHandlerPointer[MCP_CONNECTED] = DeviceStateConnectionHandler; + devices.push_back(device); + return true; + } + + void Remove(RawAddress& address) { + for (auto it = devices.begin(); it != devices.end();) { + if (it->peer_bda != address) { + ++it; + continue; + } + + it = devices.erase(it); + return; + } + } + + RemoteDevice* FindByAddress(const RawAddress& address) { + auto iter = std::find_if(devices.begin(), devices.end(), + [&address](const RemoteDevice& device) { + return device.peer_bda == address; + }); + + return (iter == devices.end()) ? nullptr : &(*iter); + } + + RemoteDevice* FindByConnId(uint16_t conn_id) { + auto iter = std::find_if(devices.begin(), devices.end(), + [&conn_id](const RemoteDevice& device) { + return device.conn_id == conn_id; + }); + + return (iter == devices.end()) ? nullptr : &(*iter); + } + + size_t size() { return (devices.size()); } + + std::vector GetRemoteDevices() { + return devices; + } + std::vector FindNotifyDevices(uint16_t handle) { + std::vector notify_devices; + for (size_t it = 0; it != devices.size(); it++){ + if(mcsServerServiceInfo.media_state_handle == handle && + devices[it].media_state_notify) { + notify_devices.push_back(devices[it]); + } else if(mcsServerServiceInfo.media_player_name_handle == handle && + devices[it].media_player_name_notify) { + notify_devices.push_back(devices[it]); + } else if (mcsServerServiceInfo.media_control_point_opcode_supported_handle == handle && + devices[it].media_control_point_opcode_supported_notify) { + notify_devices.push_back(devices[it]); + } else if(mcsServerServiceInfo.media_control_point_handle == handle && + devices[it].media_control_point_notify) { + notify_devices.push_back(devices[it]); + } else if(mcsServerServiceInfo.track_changed_handle == handle && + devices[it].track_changed_notify) { + notify_devices.push_back(devices[it]); + } else if(mcsServerServiceInfo.track_title_handle == handle && + devices[it].track_title_notify) { + notify_devices.push_back(devices[it]); + } else if(mcsServerServiceInfo.track_duration_handle == handle && + devices[it].track_duration_notify) { + notify_devices.push_back(devices[it]); + } else if(mcsServerServiceInfo.track_position_handle == handle && + devices[it].track_position_notify) { + notify_devices.push_back(devices[it]); + } else if(mcsServerServiceInfo.playing_order_handle == handle && + devices[it].playing_order_notify) { + notify_devices.push_back(devices[it]); + } + } + return notify_devices; + } + void AddSetActiveDevice(tMCP_SET_ACTIVE_DEVICE *device) { + if (device->set_id == activeDevice.set_id) { + activeDevice.address.push_back(device->address); + } else { + activeDevice.address.clear(); + activeDevice.set_id = device->set_id; + activeDevice.address.push_back(device->address); + } + } + + bool FindActiveDevice(RemoteDevice *remoteDevice) { + bool flag = false; + for (auto& it : activeDevice.address) { + if(remoteDevice->peer_bda == it) { + flag = true; + break; + } + } + return flag; + } + + std::vector devices; +}; + + +class McpServerImpl : public McpServer { + bluetooth::mcp_server::McpServerCallbacks* callbacks; + Uuid app_uuid; + + public: + RemoteDevices remoteDevices; + virtual ~McpServerImpl() = default; + + + McpServerImpl(bluetooth::mcp_server::McpServerCallbacks* callback, Uuid uuid) + :callbacks(callback), + app_uuid(uuid){ + LOG(INFO) << "McpServerImpl gatts app register"; + HandleMcsEvent(MCP_INIT_EVENT, &app_uuid); + + } + + void SetActiveDevice(const RawAddress& address, int setId, int profile) { + LOG(INFO) << __func__ ; + tMCP_SET_ACTIVE_DEVICE SetActiveDeviceOp; + SetActiveDeviceOp.set_id = setId; + SetActiveDeviceOp.address = address; + SetActiveDeviceOp.profile = profile; + HandleMcsEvent(MCP_ACTIVE_DEVICE_UPDATE, &SetActiveDeviceOp); + } + + void MediaState(uint8_t state) { + LOG(INFO) << __func__ << " state: " << unsigned(state); + tMCP_MEDIA_STATE MediaStateOp; + MediaStateOp.state = state; + HandleMcsEvent(MCP_MEDIA_STATE_UPDATE, &MediaStateOp); + } + + void MediaPlayerName(uint8_t* player_name) { + LOG(INFO) << __func__; + tMCP_MEDIA_PLAYER_NAME MediaPlayerNameOp; + MediaPlayerNameOp.name = player_name; + MediaPlayerNameOp.len = strlen((char *)player_name); + if(MediaPlayerNameOp.len != 0) + HandleMcsEvent(MCP_MEDIA_PLAYER_NAME_UPDATE, &MediaPlayerNameOp); + } + + void MediaControlPointOpcodeSupported(uint32_t feature) { + LOG(INFO) << __func__; + tMCP_MEDIA_OPCODE_SUPPORT MediaControlPointOpcodeSupportedOp; + MediaControlPointOpcodeSupportedOp.supported = feature; + HandleMcsEvent(MCP_MEDIA_SUPPORTED_OPCODE_UPDATE, &MediaControlPointOpcodeSupportedOp); + } + + void MediaControlPoint(uint8_t value) { + LOG(INFO) << __func__; + tMCP_MEDIA_CONTROL_POINT MediaControlPoint; + MediaControlPoint.req_opcode = value; + MediaControlPoint.result = MCP_STATUS_SUCCESS; // success + HandleMcsEvent(MCP_MEDIA_CONTROL_POINT_UPDATE, &MediaControlPoint); + } + + void TrackChanged(bool status) { + LOG(INFO) << __func__; + tMCP_TRACK_CHANGED TrackChangedOp; + TrackChangedOp.status = status; + HandleMcsEvent(MCP_TRACK_CHANGED_UPDATE, &TrackChangedOp); + } + + void TrackTitle(uint8_t* track_name) { + LOG(INFO) << __func__; + tMCP_TRACK_TITLE TrackTitleOp; + TrackTitleOp.title = track_name; + TrackTitleOp.len = strlen((char *)track_name); + if (TrackTitleOp.len != 0) + HandleMcsEvent(MCP_TRACK_TITLE_UPDATE, &TrackTitleOp); + } + + void TrackDuration(int32_t duration) { + LOG(INFO) << __func__; + tMCP_TRACK_DURATION TrackDurationOp; + TrackDurationOp.duration = duration; + HandleMcsEvent(MCP_TRACK_DURATION_UPDATE, &TrackDurationOp); + } + + void TrackPosition(int32_t position) { + LOG(INFO) << __func__; + tMCP_TRACK_POSTION TrackPositionOp; + TrackPositionOp.position = position; + HandleMcsEvent(MCP_TRACK_POSITION_UPDATE, &TrackPositionOp); + } + + void PlayingOrderSupported(uint16_t order) { + LOG(INFO) << __func__; + tMCP_PLAYING_ORDER_SUPPORT PlayingOrderSupportedOp; + PlayingOrderSupportedOp.order_supported = order; + HandleMcsEvent(MCP_PLAYING_ORDER_SUPPORTED_UPDATE, &PlayingOrderSupportedOp); + } + + void PlayingOrder(uint8_t value) { + LOG(INFO) << __func__; + tMCP_PLAYING_ORDER PlayingOrderOp; + PlayingOrderOp.order = value; + HandleMcsEvent(MCP_PLAYING_ORDER_UPDATE, &PlayingOrderOp); + } + + void ContentControlId(uint8_t ccid) { + LOG(INFO) << __func__; + tMCP_CONTENT_CONTROL_ID ContentControlIdOp; + ContentControlIdOp.ccid = ccid; + HandleMcsEvent(MCP_CCID_UPDATE, &ContentControlIdOp); + } + + void DisconnectMcp(const RawAddress& bd_addr) { + LOG(INFO) << __func__; + tMCP_CONNECTION_CLOSE ConnectClosingOp; + ConnectClosingOp.addr = bd_addr; + HandleMcsEvent(MCP_CONNECTION_CLOSE_EVENT, &ConnectClosingOp); + } + + void BondStateChange(const RawAddress& bd_addr, int state) { + LOG(INFO) << __func__; + tMCP_BOND_STATE_CHANGE BondStateChangeOP; + BondStateChangeOP.addr = bd_addr; + BondStateChangeOP.state = state; + HandleMcsEvent(MCP_BOND_STATE_CHANGE_EVENT, &BondStateChangeOP); + } + + void OnConnectionStateChange(uint8_t state, const RawAddress& address) { + LOG(INFO) << __func__ << " bta"; + callbacks->OnConnectionStateChange(state, address); + } + + void MediaControlPointChangeReq(uint8_t state, const RawAddress& address) { + callbacks->MediaControlPointChangeReq(state, address); + LOG(INFO) << __func__; + } + + void TrackPositionChangeReq(int32_t position) { + callbacks->TrackPositionChangeReq(position); + LOG(INFO) << __func__; + } + + void PlayingOrderChangeReq(uint16_t playingOrder) { + callbacks->PlayingOrderChangeReq(playingOrder); + LOG(INFO) << __func__; + } + +}; + + +void McpServer::CleanUp() { + HandleMcsEvent(MCP_CLEANUP_EVENT, NULL); + delete instance; + instance = nullptr; +} + +McpServer* McpServer::Get() { + CHECK(instance); + return instance; +} + +void McpServer::Initialize(bluetooth::mcp_server::McpServerCallbacks* callbacks, Uuid uuid) { + if (instance) { + LOG(ERROR) << "Already initialized!"; + } else { + instance = new McpServerImpl(callbacks, uuid); + } +} + +bool McpServer::isMcpServiceRunnig() { return instance; } + +static std::vector McpAddService(int server_if) { + + std::vector mcs_services; + mcs_services.clear(); + //service + btgatt_db_element_t service = {.uuid = GMCS_UUID, .type = BTGATT_DB_PRIMARY_SERVICE, 0}; + mcs_services.push_back(service); + mcsServerServiceInfo.mcs_service_uuid = service.uuid; + + //media state service + btgatt_db_element_t mcs_media_state_char = {.uuid = GMCS_MEDIA_STATE_UUID, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_READ| + GATT_CHAR_PROP_BIT_NOTIFY, + .permissions = GATT_PERM_READ}; + mcs_services.push_back(mcs_media_state_char); + mcsServerServiceInfo.media_state_uuid = mcs_media_state_char.uuid; + + //1st desc + btgatt_db_element_t desc1 = {.uuid = DESCRIPTOR_UUID, + .type = BTGATT_DB_DESCRIPTOR, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + mcs_services.push_back(desc1); + + //media supported feature + btgatt_db_element_t mcs_media_opcode_supported_char = {.uuid = GMCS_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_READ| + GATT_CHAR_PROP_BIT_NOTIFY, + .permissions = GATT_PERM_READ}; + + mcs_services.push_back(mcs_media_opcode_supported_char); + mcsServerServiceInfo.media_control_point_opcode_supported_uuid = + mcs_media_opcode_supported_char.uuid; + + //2nd desc + btgatt_db_element_t desc2 = {.uuid = DESCRIPTOR_UUID, + .type = BTGATT_DB_DESCRIPTOR, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + mcs_services.push_back(desc2); + + ////media player name + btgatt_db_element_t mcs_media_player_name_char = {.uuid = GMCS_MEDIA_PLAYER_NAME_UUID, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_READ| + GATT_CHAR_PROP_BIT_NOTIFY, + .permissions = GATT_PERM_READ}; + mcs_services.push_back(mcs_media_player_name_char); + mcsServerServiceInfo.media_player_name_uuid = mcs_media_player_name_char.uuid; + + //3rd desc + btgatt_db_element_t desc3 = {.uuid = DESCRIPTOR_UUID, + .type = BTGATT_DB_DESCRIPTOR, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + mcs_services.push_back(desc3); + + //media control point + btgatt_db_element_t mcs_media_control_point_char = {.uuid = GMCS_MEDIA_CONTROL_POINT, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_WRITE_NR| + GATT_CHAR_PROP_BIT_WRITE| + GATT_CHAR_PROP_BIT_NOTIFY, + .permissions = GATT_PERM_WRITE}; + + mcs_services.push_back(mcs_media_control_point_char); + + mcsServerServiceInfo.media_player_name_uuid = mcs_media_control_point_char.uuid; + + //4th desc + btgatt_db_element_t desc4 = {.uuid = DESCRIPTOR_UUID, + .type = BTGATT_DB_DESCRIPTOR, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + mcs_services.push_back(desc4); + + btgatt_db_element_t track_changed_char = {.uuid = GMCS_TRACK_CHANGED, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_NOTIFY, + .permissions = GATT_PERM_READ}; + + mcs_services.push_back(track_changed_char); + mcsServerServiceInfo.track_changed_uuid = track_changed_char.uuid; + + //5th desc + btgatt_db_element_t desc5 = {.uuid = DESCRIPTOR_UUID, + .type = BTGATT_DB_DESCRIPTOR, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + mcs_services.push_back(desc5); + + btgatt_db_element_t track_title_char = {.uuid = GMCS_TRACK_TITLE, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_NOTIFY| + GATT_CHAR_PROP_BIT_READ, + .permissions = GATT_PERM_READ}; + + mcs_services.push_back(track_title_char); + mcsServerServiceInfo.track_title_uuid = track_title_char.uuid; + + //6th desc + btgatt_db_element_t desc6 = {.uuid = DESCRIPTOR_UUID, + .type = BTGATT_DB_DESCRIPTOR, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + mcs_services.push_back(desc6); + + + btgatt_db_element_t track_duration_char = {.uuid = GMCS_TRACK_DURATION, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_NOTIFY| + GATT_CHAR_PROP_BIT_READ, + .permissions = GATT_PERM_READ}; + + mcs_services.push_back(track_duration_char); + mcsServerServiceInfo.track_duration_uuid = track_duration_char.uuid; + + //7th desc + btgatt_db_element_t desc7 = {.uuid = DESCRIPTOR_UUID, + .type = BTGATT_DB_DESCRIPTOR, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + mcs_services.push_back(desc7); + + btgatt_db_element_t track_position_char = {.uuid = GMCS_TRACK_POSITION, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_READ| + GATT_CHAR_PROP_BIT_WRITE_NR| + GATT_CHAR_PROP_BIT_WRITE| + GATT_CHAR_PROP_BIT_NOTIFY, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + + mcs_services.push_back(track_position_char); + mcsServerServiceInfo.track_position_uuid = track_position_char.uuid; + + //8th desc + btgatt_db_element_t desc8 = {.uuid = DESCRIPTOR_UUID, + .type = BTGATT_DB_DESCRIPTOR, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + mcs_services.push_back(desc8); + + btgatt_db_element_t playing_order_supported_char = {.uuid = GMCS_PLAYING_ORDER_SUPPORTED, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_READ, + .permissions = GATT_PERM_READ}; + + mcs_services.push_back(playing_order_supported_char); + mcsServerServiceInfo.playing_order_supported_uuid = playing_order_supported_char.uuid; + + btgatt_db_element_t playing_order_char = {.uuid = GMCS_PLAYING_ORDER, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_READ| + GATT_CHAR_PROP_BIT_WRITE_NR| + GATT_CHAR_PROP_BIT_WRITE| + GATT_CHAR_PROP_BIT_NOTIFY, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + + mcs_services.push_back(playing_order_char); + mcsServerServiceInfo.playing_order_uuid = playing_order_char.uuid; + + //10th desc + btgatt_db_element_t desc10 = {.uuid = DESCRIPTOR_UUID, + .type = BTGATT_DB_DESCRIPTOR, + .permissions = GATT_PERM_READ|GATT_PERM_WRITE}; + mcs_services.push_back(desc10); + + btgatt_db_element_t ccid_char = {.uuid = GMCS_CONTENT_CONTROLID, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_READ, + .permissions = GATT_PERM_READ}; + + mcs_services.push_back(ccid_char); + mcsServerServiceInfo.ccid_uuid = ccid_char.uuid; + + btgatt_db_element_t seek_speed_char = {.uuid = GMCS_SEEKING_SPEED_UUID, + .type = BTGATT_DB_CHARACTERISTIC, + .properties = GATT_CHAR_PROP_BIT_READ, + .permissions = GATT_PERM_READ}; + mcs_services.push_back(seek_speed_char); + mcsServerServiceInfo.seeking_speed_uuid = seek_speed_char.uuid; + + return mcs_services; +} + + +static void OnMcpServiceAddedCb(uint8_t status, int serverIf, + std::vector service) { + + if (service[0].uuid == Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER) || + service[0].uuid == Uuid::From16Bit(UUID_SERVCLASS_GAP_SERVER)) { + LOG(INFO) << "%s: Attempt to register restricted service"<< __func__; + return; + } + for(int i = 0; i < (int)service.size(); i++) { + + if (service[i].uuid == GMCS_UUID) { + LOG(INFO) << __func__ << " mcs service added attr handle " << service[i].attribute_handle; + } else if (service[i].uuid == GMCS_MEDIA_STATE_UUID) { + mcsServerServiceInfo.media_state_handle = service[i++].attribute_handle; + mcsServerServiceInfo.media_state_desc = service[i].attribute_handle; + LOG(INFO) << __func__ << " media_state_handle" << + mcsServerServiceInfo.media_state_handle; + LOG(INFO) << __func__ << " media_state_handle desc" << + mcsServerServiceInfo.media_state_desc; + } else if(service[i].uuid == GMCS_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED) { + mcsServerServiceInfo.media_control_point_opcode_supported_handle = + service[i++].attribute_handle; + LOG(INFO) << __func__ << " media_control_point_opcode_supported_handle register" << + mcsServerServiceInfo.media_control_point_opcode_supported_handle; + mcsServerServiceInfo.media_control_point_opcode_supported_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << " media_control_point_opcode_supported_handle desc register" << + mcsServerServiceInfo.media_control_point_opcode_supported_desc; + } else if(service[i].uuid == GMCS_MEDIA_PLAYER_NAME_UUID) { + mcsServerServiceInfo.media_player_name_handle = + service[i++].attribute_handle; + LOG(INFO) << __func__ << " media_player_name_handle GMCS_MEDIA_PLAYER_NAME_UUID register" + << mcsServerServiceInfo.media_player_name_handle; + mcsServerServiceInfo.media_player_name_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << " media_player_name_handle GMCS_MEDIA_PLAYER_NAME_UUID desc" + << mcsServerServiceInfo.media_player_name_desc; + } else if(service[i].uuid == GMCS_MEDIA_CONTROL_POINT) { + mcsServerServiceInfo.media_control_point_handle = + service[i++].attribute_handle; + LOG(INFO) << __func__ << " media_control_point_handle GMCS_MEDIA_CONTROL_POINT register" + << mcsServerServiceInfo.media_control_point_handle; + mcsServerServiceInfo.media_control_point_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << " media_control_point_handle GMCS_MEDIA_CONTROL_POINT desc" + << mcsServerServiceInfo.media_control_point_desc; + } else if(service[i].uuid == GMCS_TRACK_CHANGED) { + mcsServerServiceInfo.track_changed_handle = + service[i++].attribute_handle; + LOG(INFO) << __func__ << "track_changed_handle GMCS_TRACK_CHANGED register" + << mcsServerServiceInfo.track_changed_handle; + mcsServerServiceInfo.track_changed_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << "track_changed_handle GMCS_TRACK_CHANGED desc" + << mcsServerServiceInfo.track_changed_desc; + } else if(service[i].uuid == GMCS_TRACK_TITLE) { + mcsServerServiceInfo.track_title_handle = + service[i++].attribute_handle; + LOG(INFO) << __func__ << "track_title_handle GMCS_TRACK_TITLE register" + << mcsServerServiceInfo.track_title_handle; + mcsServerServiceInfo.track_title_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << "track_title_handle GMCS_TRACK_TITLE desc" + << mcsServerServiceInfo.track_title_desc; + } else if(service[i].uuid == GMCS_TRACK_DURATION) { + mcsServerServiceInfo.track_duration_handle = + service[i++].attribute_handle; + + LOG(INFO) << __func__ << "track_duration_handle GMCS_TRACK_DURATION register" + << mcsServerServiceInfo.track_duration_handle; + mcsServerServiceInfo.track_duration_desc = + service[i].attribute_handle; + + LOG(INFO) << __func__ << "track_duration_handle GMCS_TRACK_DURATION desc" + << mcsServerServiceInfo.track_duration_desc; + + } else if(service[i].uuid == GMCS_TRACK_POSITION) { + mcsServerServiceInfo.track_position_handle = + service[i++].attribute_handle; + LOG(INFO) << __func__ << "track_position_handle GMCS_TRACK_POSITION register" + << mcsServerServiceInfo.track_position_handle; + mcsServerServiceInfo.track_position_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << "track_position_handle GMCS_TRACK_POSITION desc" + << mcsServerServiceInfo.track_position_handle; + + } else if(service[i].uuid == GMCS_PLAYING_ORDER_SUPPORTED) { + mcsServerServiceInfo.playing_order_supported_handle = + service[i].attribute_handle; + LOG(INFO) << __func__ << "playing_order_supported_handle GMCS_PLAYING_ORDER_SUPPORTED register" + << mcsServerServiceInfo.playing_order_supported_handle; + } else if(service[i].uuid == GMCS_PLAYING_ORDER) { + mcsServerServiceInfo.playing_order_handle = + service[i++].attribute_handle; + LOG(INFO) << __func__ << "playing_order_handle GMCS_PLAYING_ORDER register" + << mcsServerServiceInfo.playing_order_handle; + mcsServerServiceInfo.playing_order_desc = + service[i].attribute_handle; + LOG(INFO) << __func__ << "playing_order_handle GMCS_PLAYING_ORDER desc" + << mcsServerServiceInfo.playing_order_desc; + } else if(service[i].uuid == GMCS_CONTENT_CONTROLID) { + mcsServerServiceInfo.ccid_handle = + service[i].attribute_handle; + LOG(INFO) << __func__ << "ccid_handle GMCS_CONTENT_CONTROLID register" << + mcsServerServiceInfo.ccid_handle; + } else if(service[i].uuid == GMCS_SEEKING_SPEED_UUID) { + mcsServerServiceInfo.seeking_speed_handle = + service[i].attribute_handle; + LOG(INFO) << __func__ << "ccid_handle GMCS_SEEKING_SPEED_ID register" << + mcsServerServiceInfo.seeking_speed_handle; + } + } //for +} + + +void BTMcpCback(tBTA_GATTS_EVT event, tBTA_GATTS* param) { + HandleMcsEvent((uint32_t)event, param); +} + +//mcs handle event +void HandleMcsEvent(uint32_t event, void* param) { + LOG(INFO) << __func__ << " mcs handle event " << get_mcp_event_name(event); + tBTA_GATTS* p_data = NULL; + uint32_t proc_event; + mcp_resp_t *rsp = (mcp_resp_t *)osi_malloc(sizeof(mcp_resp_t)); + if (rsp == NULL) { + LOG(INFO) << __func__ << " mcs handle return rsp not allocated "; + return; + } + uint8_t status = BT_STATUS_SUCCESS; + proc_event = MCP_NONE_EVENT; + rsp->event = MCP_NONE_EVENT; + switch (event) { + + case MCP_INIT_EVENT: + { + // Uuid app_uuid = (Uuid)*param; + Uuid aap_uuid = Uuid::FromString("1849"); + mediaPlayerInfo.media_state = MCP_STATE_INACTIVE; + mediaPlayerInfo.MediaStateHandlerPointer[MCP_STATE_INACTIVE] = + MediaStateInactiveHandler; + mediaPlayerInfo.MediaStateHandlerPointer[MCP_STATE_PAUSE] = + MediaStatePauseHandler; + mediaPlayerInfo.MediaStateHandlerPointer[MCP_STATE_PLAYING] = + MediaStatePlayingHandler; + mediaPlayerInfo.MediaStateHandlerPointer[MCP_STATE_SEEKING] = + MediaStateSeekingHandler; + + mediaPlayerInfo.media_supported_feature = MCP_DEFAULT_MEDIA_CTRL_SUPP_FEAT; + mediaPlayerInfo.ccid = 0; + mediaPlayerInfo.seeking_speed = 0; + mediaPlayerInfo.duration = TRACK_POSITION_UNAVAILABLE; + mediaPlayerInfo.position = TRACK_DURATION_UNAVAILABLE; + mediaPlayerInfo.track_changed = false; + mediaPlayerInfo.playing_order_value = 1; + mediaPlayerInfo.playing_order_supported = 1; + mediaPlayerInfo.player_name_len = 0; + mediaPlayerInfo.track_title_len = 0; + mediaPlayerInfo.media_ctrl_point = 0; + //adding app with random uuid + BTA_GATTS_AppRegister(aap_uuid, BTMcpCback, true); + break; + } + + case MCP_CLEANUP_EVENT: + { + //initiate disconnection to all connected device + //unregister APP + BTA_GATTS_AppDeregister(mcsServerServiceInfo.server_if); + break; + } + case BTA_GATTS_REG_EVT: + { + p_data = (tBTA_GATTS*)param; + if (p_data->reg_oper.status == BT_STATUS_SUCCESS) { + mcsServerServiceInfo.server_if = p_data->reg_oper.server_if; + std::vector service; + service = McpAddService(mcsServerServiceInfo.server_if); + if (service[0].uuid == Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER) || + service[0].uuid == Uuid::From16Bit(UUID_SERVCLASS_GAP_SERVER)) { + LOG(INFO) << __func__ << " service app register uuid is not valid"; + break; + } + LOG(INFO) << __func__ << " service app register"; + BTA_GATTS_AddService(mcsServerServiceInfo.server_if, service, base::Bind(&OnMcpServiceAddedCb)); + } + break; + } + + case BTA_GATTS_DEREG_EVT: + { + break; + } + + case BTA_GATTS_CONF_EVT: { + p_data = (tBTA_GATTS*)param; + uint16_t conn_id = p_data->req_data.conn_id; + uint8_t status = p_data->req_data.status; + LOG(INFO) << __func__ << "conn_id :" << conn_id << "status:" << status; + if (status == BT_STATUS_SUCCESS) { + LOG(INFO) << __func__ << "Notification callback for conn_id :" << conn_id; + GattsOpsQueue::NotificationCallback(conn_id); + } + break; + } + + case BTA_GATTS_CONGEST_EVT: + { + p_data = (tBTA_GATTS*)param; + RemoteDevice *remoteDevice; + remoteDevice = instance->remoteDevices.FindByConnId(p_data->congest.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " connection entry not found conn_id :" + << p_data->congest.conn_id; + break; + } + // rsp->ConngestionOp.status = p_data->req_data.status; + rsp->remoteDevice = remoteDevice; + rsp->oper.CongestionOp.congested = p_data->congest.congested; + proc_event = MCP_CONGESTION_UPDATE; + rsp->event = MCP_CONGESTION_UPDATE; + break; + } + case BTA_GATTS_MTU_EVT: { + p_data = (tBTA_GATTS*)param; + RemoteDevice *remoteDevice; + remoteDevice = instance->remoteDevices.FindByConnId(p_data->req_data.p_data->mtu); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " connection entry not found conn_id :" + << p_data->congest.conn_id; + break; + } + LOG(INFO) << __func__ << " conn_id :"<< p_data->req_data.p_data->mtu; + LOG(INFO) <<"mtu " <req_data.p_data->mtu; + proc_event = MCP_MTU_UPDATE; + rsp->event = MCP_MTU_UPDATE; + rsp->remoteDevice = remoteDevice; + rsp->oper.MtuOp.mtu = p_data->req_data.p_data->mtu; + break; + } + case BTA_GATTS_CONNECT_EVT: { + p_data = (tBTA_GATTS*)param; + LOG(INFO) << __func__ << " remote devices connected"; + // need to discuss how to get encryption support + /* + #if (!defined(BTA_SKIP_BLE_START_ENCRYPTION) || BTA_SKIP_BLE_START_ENCRYPTION == FALSE) + btif_gatt_check_encrypted_link(p_data->conn.remote_bda, + p_data->conn.transport); + #endif*/ + RemoteDevice remoteDevice; + memset(&remoteDevice, 0, sizeof(remoteDevice)); + if(instance->remoteDevices.FindByAddress(p_data->conn.remote_bda)) { + LOG(INFO) << __func__ << " remote devices already there is connected list"; + status = BT_STATUS_FAIL; + return; + } + remoteDevice.peer_bda = p_data->conn.remote_bda; + remoteDevice.conn_id = p_data->conn.conn_id; + if(instance->remoteDevices.Add(remoteDevice) == false) { + LOG(INFO) << __func__ << " remote device is not added : max connection reached"; + // need to check disconnection required + break; + } + remoteDevice.state = MCP_DISCONNECTED; + + LOG(INFO) << __func__ << " remote devices connected conn_id: "<< remoteDevice.conn_id << + "bd_addr " << remoteDevice.peer_bda; + rsp->remoteDevice = instance->remoteDevices.FindByAddress(p_data->conn.remote_bda); + if ( rsp->remoteDevice == NULL) { + LOG(INFO) << __func__ ; + break; + } + proc_event = MCP_CONNECTION; + rsp->event = MCP_CONNECTION; + break; + } + + case BTA_GATTS_CLOSE_EVT: + case BTA_GATTS_DISCONNECT_EVT: { + p_data = (tBTA_GATTS*)param; + LOG(INFO) << __func__ << " remote devices disconnected conn_id " << p_data->conn.conn_id; + RemoteDevice *remoteDevice; + remoteDevice = instance->remoteDevices.FindByConnId(p_data->conn.conn_id); + if((!remoteDevice) ) { + status = BT_STATUS_FAIL; + break; + } + + rsp->remoteDevice = remoteDevice; + proc_event = MCP_DISCONNECTION; + rsp->event = MCP_DISCONNECTION; + LOG(INFO) << __func__ << " disconnected conn_id " << p_data->conn.conn_id; + break; + } + + case BTA_GATTS_STOP_EVT: + // not required + break; + + case BTA_GATTS_DELELTE_EVT: + // not required + break; + + case BTA_GATTS_READ_CHARACTERISTIC_EVT: { + p_data = (tBTA_GATTS*)param; + std::vector value; + RemoteDevice *remoteDevice = + instance->remoteDevices.FindByConnId(p_data->req_data.conn_id); + LOG(INFO) << __func__ << " charateristcs read handle " << + p_data->req_data.p_data->read_req.handle <<" trans_id : " << + p_data->req_data.trans_id; + + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " device not found ignore read operation"; + status = BT_STATUS_FAIL; + break; + } + + LOG(INFO) <<" offset: " << p_data->req_data.p_data->read_req.offset << + " long : " << p_data->req_data.p_data->read_req.is_long; + + rsp->rsp_value.attr_value.auth_req = 0; + rsp->rsp_value.attr_value.handle = p_data->req_data.p_data->read_req.handle; + rsp->rsp_value.attr_value.offset = p_data->req_data.p_data->read_req.offset; + + if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.media_state_handle) { + proc_event = MCP_MEDIA_STATE_READ; + LOG(INFO) << __func__ << " media_state_handle read"; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.media_control_point_opcode_supported_handle) { + proc_event = MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_READ; + LOG(INFO) << __func__ << " media_control_point_opcode_supported_handle read"; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.media_player_name_handle) { + proc_event = MCP_MEDIA_PLAYER_NAME_READ; + LOG(INFO) << __func__ << " media_player_name_handle read"; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.track_title_handle) { + proc_event = MCP_TRACK_TITLE_READ; + LOG(INFO) << __func__ << " track_title_handle read"; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.track_position_handle) { + proc_event = MCP_TRACK_POSITION_READ; + LOG(INFO) << __func__ << " track_position_handle read"; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.track_duration_handle) { + proc_event = MCP_TRACK_DURATION_READ; + LOG(INFO) << __func__ << " track_duration_handle read"; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.ccid_handle) { + LOG(INFO) << __func__ << " ccid_handle read"; + proc_event = MCP_CCID_READ; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.seeking_speed_handle) { + LOG(INFO) << __func__ << " seeking_speed_handle read"; + proc_event = MCP_SEEKING_SPEED_READ; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.playing_order_supported_handle) { + LOG(INFO) << __func__ << " playing_order_supported_handle read"; + proc_event = MCP_PLAYING_ORDER_SUPPORTED_READ; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.playing_order_handle) { + LOG(INFO) << __func__ << " playing_order_handle read"; + proc_event = MCP_PLAYING_ORDER_READ; + } else { + LOG(INFO) << __func__ << " read request for unknown handle" << p_data->req_data.p_data->read_req.handle; + status = BT_STATUS_FAIL; + break; + } + LOG(INFO) << __func__ << " read request handle" << p_data->req_data.p_data->read_req.handle << + "connection id" << p_data->req_data.conn_id; + rsp->event = MCP_READ_RSP; + rsp->oper.ReadOp.trans_id = p_data->req_data.trans_id; + rsp->oper.ReadOp.is_long = p_data->req_data.p_data->read_req.is_long; + rsp->oper.ReadOp.status = BT_STATUS_SUCCESS; + rsp->remoteDevice = remoteDevice; + break; + } + + case BTA_GATTS_READ_DESCRIPTOR_EVT: { + LOG(INFO) << __func__ << " read descriptor"; + p_data = (tBTA_GATTS*)param; + LOG(INFO) << __func__ << " charateristcs read desc handle " << + p_data->req_data.p_data->read_req.handle << " offset : " + << p_data->req_data.p_data->read_req.offset; + RemoteDevice *remoteDevice = + instance->remoteDevices.FindByConnId(p_data->req_data.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " device not found ignore write"; + status = BT_STATUS_FAIL; + break; + } + + if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.media_state_desc) { + LOG(INFO) << __func__ << " media_state_desc read"; + proc_event = MCP_MEDIA_STATE_READ_DESCRIPTOR; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.media_control_point_desc) { + LOG(INFO) << __func__ << " media_control_point_desc read"; + proc_event = MCP_MEDIA_CONTROL_POINT_READ_DESCRIPTOR; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.media_control_point_opcode_supported_desc) { + LOG(INFO) << __func__ << " media_control_point_opcode_supported_desc read"; + proc_event = MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_DESCRIPTOR_READ; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.media_player_name_desc) { + LOG(INFO) << __func__ << " media_player_name_desc read"; + proc_event = MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_READ; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.track_changed_desc) { + LOG(INFO) << __func__ << " track_changed_desc read"; + proc_event = MCP_TRACK_CHANGED_DESCRIPTOR_READ; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.track_title_desc) { + LOG(INFO) << __func__ << " track_title_desc read"; + proc_event = MCP_TRACK_TITLE_DESCRIPTOR_READ; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.track_position_desc) { + LOG(INFO) << __func__ << " track_position_desc read"; + proc_event = MCP_TRACK_POSITION_DESCRIPTOR_READ; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.track_duration_desc) { + LOG(INFO) << __func__ << " track_duration_desc read"; + proc_event = MCP_TRACK_DURATION_DESCRIPTOR_READ; + } else if(p_data->req_data.p_data->read_req.handle == + mcsServerServiceInfo.playing_order_desc) { + LOG(INFO) << __func__ << " playing_order_desc read"; + proc_event = MCP_PLAYING_ORDER_DESCRIPTOR_READ; + } else { + LOG(INFO) << __func__ << " read request for unknown handle" << p_data->req_data.p_data->read_req.handle; + status = BT_STATUS_FAIL; + break; + } + rsp->event = MCP_DESCRIPTOR_READ_RSP; + rsp->rsp_value.attr_value.auth_req = 0; + rsp->rsp_value.attr_value.handle = p_data->req_data.p_data->read_req.handle; + rsp->rsp_value.attr_value.offset = p_data->req_data.p_data->read_req.offset; + //mcp response + rsp->oper.ReadDescOp.desc_handle = p_data->req_data.p_data->read_req.handle; + rsp->oper.ReadDescOp.trans_id = p_data->req_data.trans_id; + rsp->oper.ReadDescOp.status = BT_STATUS_SUCCESS; + rsp->remoteDevice = remoteDevice; + break; + } + + case BTA_GATTS_WRITE_CHARACTERISTIC_EVT: { + p_data = (tBTA_GATTS*)param; + const auto& req = p_data->req_data.p_data->write_req; + LOG(INFO) << __func__ << " write characteristics len : " << req.len << " value "<< req.value[0]; + RemoteDevice *remoteDevice = + instance->remoteDevices.FindByConnId(p_data->req_data.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " device not found ignore write"; + status = BT_STATUS_DEVICE_NOT_CONNECTED; + break; + } + + rsp->event = MCP_WRITE_RSP; + rsp->oper.WriteOp.status = BT_STATUS_SUCCESS; + if (req.handle == mcsServerServiceInfo.playing_order_handle) { + proc_event = MCP_PLAYING_ORDER_WRITE; + } else if (req.handle == mcsServerServiceInfo.media_control_point_handle) { + proc_event = MCP_MEDIA_CONTROL_POINT_WRITE; + } else if (req.handle == mcsServerServiceInfo.track_position_handle) { + proc_event = MCP_TRACK_POSITION_WRITE; + } else { + //characteristics handle not matched. + // + rsp->oper.WriteOp.status = BT_STATUS_HANLDE_NOT_MATCHED; + } + rsp->oper.WriteOp.char_handle = req.handle; + rsp->oper.WriteOp.trans_id = p_data->req_data.trans_id; + rsp->remoteDevice = remoteDevice; + rsp->oper.WriteOp.need_rsp = req.need_rsp; + rsp->oper.WriteOp.offset = req.offset; // need to check requirement + memcpy(rsp->oper.WriteOp.data, req.value, req.len); + LOG(INFO) << __func__ << " Local Tx ID " << rsp->oper.WriteOp.trans_id << " Gatt Tx ID: " << p_data->req_data.trans_id; + break; + } + + case BTA_GATTS_WRITE_DESCRIPTOR_EVT: { + p_data = (tBTA_GATTS* )param; + uint16_t req_value = 0; + const auto& req = p_data->req_data.p_data->write_req; + RemoteDevice *remoteDevice = + instance->remoteDevices.FindByConnId(p_data->req_data.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " device not found ignore notification"; + break; + } + req_value = *(uint16_t* )req.value; + //need to initialized with proper error code + int status = BT_STATUS_SUCCESS; + LOG(INFO) << __func__ << " write descriptor :" << req.handle << + "is resp: " << req.need_rsp << "is prep: " << req.is_prep << " value " << req_value; + + if(req.handle == + mcsServerServiceInfo.media_state_desc) { + LOG(INFO) << __func__ << " media_state_desc descriptor write"; + remoteDevice->media_state_notify = req_value; + proc_event = MCP_MEDIA_STATE_DESCRIPTOR_WRITE; + } else if(req.handle == + mcsServerServiceInfo.media_player_name_desc) { + remoteDevice->media_player_name_notify = req_value; + LOG(INFO) << __func__ << " media_player_name_desc descriptor write"; + proc_event = MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_WRITE; + } else if(req.handle == + mcsServerServiceInfo.media_control_point_desc) { + remoteDevice->media_control_point_notify = req_value; + LOG(INFO) << __func__ << " media_player_control_point_desc descriptor write"; + proc_event = MCP_MEDIA_CONTROL_POINT_DESCRIPTOR_WRITE; + } else if(req.handle == + mcsServerServiceInfo.media_control_point_opcode_supported_desc) { + remoteDevice->media_control_point_opcode_supported_notify = req_value; + LOG(INFO) << __func__ << " media_control_point_opcode_supported_desc descriptor write"; + proc_event = MCP_MEDIA_CONTROL_POINT_SUPPORTED_DESCRIPTOR_WRITE; + } else if(req.handle == + mcsServerServiceInfo.track_changed_desc) { + remoteDevice->track_changed_notify = req_value; + LOG(INFO) << __func__ << " track_changed_desc descriptor write"; + proc_event = MCP_TRACK_CHANGED_DESCRIPTOR_WRITE; + } else if(req.handle == + mcsServerServiceInfo.track_title_desc) { + remoteDevice->track_title_notify = req_value; + LOG(INFO) << __func__ << " track_title_desc descriptor write"; + proc_event = MCP_TRACK_TITLE_DESCRIPTOR_WRITE; + } else if(req.handle == + mcsServerServiceInfo.track_position_desc) { + remoteDevice->track_position_notify = req_value; + LOG(INFO) << __func__ << " track_position_desc descriptor write"; + proc_event = MCP_TRACK_POSITION_DESCRIPTOR_WRITE; + } else if(req.handle == + mcsServerServiceInfo.track_duration_desc) { + remoteDevice->track_duration_notify = req_value; + LOG(INFO) << __func__ << " track_duration_desc descriptor write"; + proc_event = MCP_TRACK_DURATION_DESCRIPTOR_WRITE; + } else if(req.handle == + mcsServerServiceInfo.playing_order_desc) { + remoteDevice->playing_order_notify = req_value; + LOG(INFO) << __func__ << " playing_order_desc descriptor write"; + proc_event = MCP_PLAYING_ORDER_DESCRIPTOR_WRITE; + } else { + LOG(INFO) << __func__ << " descriptor write not matched "; + status = 4; //need to check error code + } + rsp->event = MCP_DESCRIPTOR_WRITE_RSP; + rsp->oper.WriteDescOp.desc_handle = req.handle; + rsp->oper.WriteDescOp.trans_id = p_data->req_data.trans_id; + rsp->oper.WriteDescOp.status = status; + rsp->oper.WriteDescOp.need_rsp = req.need_rsp; + rsp->oper.WriteDescOp.notification = req_value; + rsp->remoteDevice = remoteDevice; + break; + } + + case BTA_GATTS_EXEC_WRITE_EVT: { + p_data = (tBTA_GATTS*)param; + // need to check requirement + break; + } + + case BTA_GATTS_PHY_UPDATE_EVT: { + p_data = (tBTA_GATTS*)param; + RemoteDevice *remoteDevice = + instance->remoteDevices.FindByConnId(p_data->phy_update.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " device not found ignore phy update" + << p_data->phy_update.status; + status = BT_STATUS_FAIL; + break; + } + proc_event = MCP_PHY_UPDATE; + rsp->event = MCP_PHY_UPDATE; + rsp->oper.PhyOp.rx_phy = p_data->phy_update.rx_phy; + rsp->oper.PhyOp.tx_phy = p_data->phy_update.tx_phy; + rsp->remoteDevice = remoteDevice; + break; + } + + case BTA_GATTS_CONN_UPDATE_EVT: { + p_data = (tBTA_GATTS*)param; + RemoteDevice *remoteDevice = + instance->remoteDevices.FindByConnId(p_data->phy_update.conn_id); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " connection update device not found"; + break; + } + LOG(INFO) << __func__ << " connection update status" << p_data->phy_update.status; + proc_event = MCP_CONNECTION_UPDATE; + rsp->event = MCP_CONNECTION_UPDATE; + rsp->oper.ConnectionUpdateOp.latency = p_data->conn_update.latency; + rsp->oper.ConnectionUpdateOp.timeout = p_data->conn_update.timeout; + rsp->oper.ConnectionUpdateOp.interval = p_data->conn_update.interval; + rsp->oper.ConnectionUpdateOp.status = p_data->conn_update.status; + rsp->remoteDevice = remoteDevice; + break; + } + + case MCP_ACTIVE_DEVICE_UPDATE: + { + tMCP_SET_ACTIVE_DEVICE *data = (tMCP_SET_ACTIVE_DEVICE *)param; + LOG(INFO) << __func__ << " address " << data->address; + RemoteDevice *remoteDevice = instance->remoteDevices.FindByAddress(data->address); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " active device update device address not found"; + break; + } + instance->remoteDevices.AddSetActiveDevice(data); + rsp->remoteDevice = remoteDevice; + rsp->oper.SetActiveDeviceOp.profile = data->profile; + proc_event = MCP_ACTIVE_DEVICE_UPDATE; + rsp->event = MCP_ACTIVE_DEVICE_UPDATE; + + + break; + } + + case MCP_MEDIA_STATE_UPDATE: + { + tMCP_MEDIA_STATE *data = (tMCP_MEDIA_STATE *) param; + if (mediaPlayerInfo.media_state != data->state) + mediaPlayerInfo.media_state = data->state; + LOG(INFO) << __func__ << " state: " << unsigned(mediaPlayerInfo.media_state); + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->handle = mcsServerServiceInfo.media_state_handle; + rsp->oper.MediaUpdateOp.data = &mediaPlayerInfo.media_state; + rsp->oper.MediaUpdateOp.len = sizeof(mediaPlayerInfo.media_state); + break; + } + + case MCP_MEDIA_PLAYER_NAME_UPDATE: + { + tMCP_MEDIA_PLAYER_NAME *data = (tMCP_MEDIA_PLAYER_NAME *) param; + if (memcmp(mediaPlayerInfo.player_name, data->name, data->len)) { + memcpy(mediaPlayerInfo.player_name, data, data->len); + mediaPlayerInfo.player_name_len = data->len; + } + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->handle = mcsServerServiceInfo.media_player_name_handle; + rsp->oper.MediaUpdateOp.data = (uint8_t *)mediaPlayerInfo.player_name; + rsp->oper.MediaUpdateOp.len = mediaPlayerInfo.player_name_len; + break; + } + + case MCP_MEDIA_SUPPORTED_OPCODE_UPDATE: + { + tMCP_MEDIA_OPCODE_SUPPORT *data = (tMCP_MEDIA_OPCODE_SUPPORT *)param; + mediaPlayerInfo.media_supported_feature = data->supported; + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->handle = mcsServerServiceInfo.media_control_point_opcode_supported_handle; + rsp->oper.MediaUpdateOp.data = (uint8_t *)&mediaPlayerInfo.media_supported_feature; + rsp->oper.MediaUpdateOp.len = sizeof(mediaPlayerInfo.media_supported_feature); + break; + } + + case MCP_MEDIA_CONTROL_POINT_UPDATE: + { + tMCP_MEDIA_CONTROL_POINT *data = (tMCP_MEDIA_CONTROL_POINT *)param; + mediaPlayerInfo.media_ctrl_point = data->req_opcode | (data->result << 8); + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->handle = mcsServerServiceInfo.media_control_point_handle; + rsp->oper.MediaUpdateOp.data = (uint8_t *)&mediaPlayerInfo.media_ctrl_point; + rsp->oper.MediaUpdateOp.len = sizeof(mediaPlayerInfo.media_ctrl_point); + break; + } + case MCP_PLAYING_ORDER_SUPPORTED_UPDATE: + { + tMCP_PLAYING_ORDER_SUPPORT *data = (tMCP_PLAYING_ORDER_SUPPORT *) param; + mediaPlayerInfo.playing_order_supported = data->order_supported; + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->handle = + mcsServerServiceInfo.media_control_point_opcode_supported_handle; + rsp->oper.MediaUpdateOp.data = (uint8_t *)&mediaPlayerInfo.playing_order_supported; + rsp->oper.MediaUpdateOp.len = sizeof(mediaPlayerInfo.playing_order_supported); + break; + } + + case MCP_PLAYING_ORDER_UPDATE: + { + tMCP_PLAYING_ORDER *data = (tMCP_PLAYING_ORDER *) param; + mediaPlayerInfo.playing_order_value = data->order; + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->handle = mcsServerServiceInfo.playing_order_handle; + rsp->oper.MediaUpdateOp.data = (uint8_t *)&mediaPlayerInfo.playing_order_value; + rsp->oper.MediaUpdateOp.len = sizeof(mediaPlayerInfo.playing_order_value); + break; + } + + case MCP_TRACK_CHANGED_UPDATE: + { + tMCP_TRACK_CHANGED *data = (tMCP_TRACK_CHANGED *) param; + mediaPlayerInfo.track_changed = data->status; + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->handle = mcsServerServiceInfo.track_changed_handle; + rsp->oper.MediaUpdateOp.data = (uint8_t *)&mediaPlayerInfo.track_changed; + rsp->oper.MediaUpdateOp.len = sizeof(mediaPlayerInfo.track_changed); + break; + } + + case MCP_TRACK_POSITION_UPDATE: + { + tMCP_TRACK_POSTION *data = (tMCP_TRACK_POSTION*) param; + if(data->position != mediaPlayerInfo.position) { + mediaPlayerInfo.position = data->position; + } + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->handle = mcsServerServiceInfo.track_position_handle; + rsp->oper.MediaUpdateOp.data = (uint8_t *)&mediaPlayerInfo.position; + rsp->oper.MediaUpdateOp.len = sizeof(mediaPlayerInfo.position); + break; + } + + case MCP_TRACK_DURATION_UPDATE: + { + tMCP_TRACK_DURATION* data = (tMCP_TRACK_DURATION *) param; + mediaPlayerInfo.duration = data->duration; + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->rsp_value.handle = mcsServerServiceInfo.track_duration_handle; + rsp->handle = mcsServerServiceInfo.track_duration_handle; + rsp->oper.MediaUpdateOp.data = (uint8_t *)&mediaPlayerInfo.duration; + rsp->oper.MediaUpdateOp.len = sizeof(mediaPlayerInfo.duration); + break; + } + + case MCP_TRACK_TITLE_UPDATE: + { + tMCP_TRACK_TITLE *data = (tMCP_TRACK_TITLE*) param; + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->handle = mcsServerServiceInfo.track_title_handle; + if (memcmp(mediaPlayerInfo.title, data->title, data->len)) { + memcpy(mediaPlayerInfo.title, data->title, data->len); + mediaPlayerInfo.track_title_len = data->len; + } + rsp->oper.MediaUpdateOp.data = (uint8_t *)mediaPlayerInfo.title; + rsp->oper.MediaUpdateOp.len = sizeof(mediaPlayerInfo.track_title_len); + break; + } + case MCP_CCID_UPDATE: + { + tMCP_CONTENT_CONTROL_ID *data = (tMCP_CONTENT_CONTROL_ID *) param; + mediaPlayerInfo.ccid = (uint8_t)data->ccid; + proc_event = MCP_NOTIFY_ALL; + rsp->event = MCP_NOTIFY_ALL; + rsp->handle = mcsServerServiceInfo.ccid_handle; + rsp->oper.MediaUpdateOp.data = (uint8_t *)&mediaPlayerInfo.ccid; + rsp->oper.MediaUpdateOp.len = sizeof(mediaPlayerInfo.ccid); + break; + } + + case MCP_CONNECTION_CLOSE_EVENT: + { + tMCP_CONNECTION_CLOSE* p_data = (tMCP_CONNECTION_CLOSE *)param; + RemoteDevice* remoteDevice = instance->remoteDevices.FindByAddress(p_data->addr); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " address is not in list"; + break; + } + proc_event = MCP_CONNECTION_CLOSE_EVENT; + rsp->remoteDevice = remoteDevice; + break; + } + + case MCP_BOND_STATE_CHANGE_EVENT: + { + tMCP_BOND_STATE_CHANGE* p_data = (tMCP_BOND_STATE_CHANGE *)param; + RemoteDevice* remoteDevice = instance->remoteDevices.FindByAddress(p_data->addr); + if(remoteDevice == NULL) { + LOG(INFO) << __func__ << " address is not in list"; + break; + } + instance->remoteDevices.Remove(p_data->addr); + break; + } + default: + LOG(INFO) << __func__ << " event not matched !!"; + break; + } + + if(rsp->event != MCP_NONE_EVENT) { + LOG(INFO) << __func__ << " event to media handler " << get_mcp_event_name(rsp->event); + mediaPlayerInfo.MediaStateHandlerPointer[mediaPlayerInfo.media_state](proc_event, rsp, mediaPlayerInfo.media_state); + } + if(rsp) { + LOG(INFO) << __func__ << "free rsp data"; + osi_free(rsp); + } +} + + +bool MediaStateInactiveHandler(uint32_t event, void* param, uint8_t state) { + LOG(INFO) << __func__ << " handle event " << get_mcp_event_name(event) + << " in state " << get_mcp_media_state_name(state); + + mcp_resp_t *p_data = (mcp_resp_t *)param; + + RemoteDevice *device = p_data->remoteDevice; + LOG(INFO) << __func__ << " inactive mcs handle event "<< get_mcp_event_name(p_data->event); + switch(event) { + case MCP_NOTIFY_ALL: { + LOG(INFO) << __func__ << " Notify all handle "<< p_data->handle; + std::vectornotifyDevices = instance->remoteDevices.FindNotifyDevices(p_data->handle); + std::vector::iterator it; + if (notifyDevices.size() <= 0) { + LOG(INFO) << __func__ << " No device register for notification"; + break; + } + for (it = notifyDevices.begin(); it != notifyDevices.end(); it++){ + LOG(INFO) << __func__ << " Notify all handle device id " << it->conn_id; + p_data->remoteDevice = instance->remoteDevices.FindByConnId(it->conn_id); + it->DeviceStateHandlerPointer[it->state](p_data->event, p_data, it->state); + } + break; + } + + case MCP_TRACK_POSITION_WRITE: + case MCP_PLAYING_ORDER_WRITE: { + LOG(INFO) << __func__ << "Ignore other request as player is not active"; + //need to ignore write rsp because no player is active + p_data->rsp_value.attr_value.len = 0; + uint8_t data = (uint8_t)(*p_data->oper.WriteOp.data); + std::vector value; + value.push_back(data); + value.push_back(0x03); // To-Do check ??? + LOG(INFO) << __func__ << "hndl " << p_data->oper.WriteOp.char_handle; + GattsOpsQueue::SendNotification(device->conn_id, p_data->oper.WriteOp.char_handle, value, false); + break; + } + + case MCP_MEDIA_CONTROL_POINT_WRITE: { + LOG(INFO) << __func__ << "Ignore other request as player is not active ctrl pt write"; + //need to ignore write rsp because no player is active + p_data->rsp_value.attr_value.len = 0; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + uint8_t data = (uint8_t)(*p_data->oper.WriteOp.data); + uint8_t status = MCP_MEDIA_PLAYER_INACTIVE; + std::vector value; + bool opcode_support = is_opcode_supported(data); + if (!opcode_support) { + status = MCP_OPCODE_NOT_SUPPORTED; + LOG(INFO) << __func__ << "Sending OPCode Unsupported indication"; + } else { + LOG(INFO) << __func__ << "Sending INACTIVE indication"; + } + value.push_back(data); + value.push_back(status); + LOG(INFO) << __func__ << "handle " << p_data->oper.WriteOp.char_handle; + GattsOpsQueue::SendNotification(device->conn_id, p_data->oper.WriteOp.char_handle, value, false); + break; + } + case MCP_MEDIA_STATE_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.media_state); + *(uint8_t *)p_data->rsp_value.attr_value.value = (uint8_t)mediaPlayerInfo.media_state; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.media_state); + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_READ: { + uint16_t len = mediaPlayerInfo.player_name_len; + if (device->mtu != -1 && len >= device->mtu - 3) //to check player len should not greater than mtu + len = device->mtu - 3; + p_data->rsp_value.attr_value.len = len; + memcpy(p_data->rsp_value.attr_value.value, mediaPlayerInfo.player_name, + p_data->rsp_value.attr_value.len); + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.media_supported_feature); + *(uint32_t*)p_data->rsp_value.attr_value.value = + MCP_DEFAULT_MEDIA_CTRL_SUPP_FEAT; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_READ: { + uint16_t len = mediaPlayerInfo.track_title_len; + if (device->mtu != -1 && len >= device->mtu - 3) //to check title len should not greater than mtu + len = device->mtu - 3; + p_data->rsp_value.attr_value.len = len; + memcpy(p_data->rsp_value.attr_value.value, mediaPlayerInfo.title, + p_data->rsp_value.attr_value.len); + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_POSITION_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.position); + *(int *)p_data->rsp_value.attr_value.value = (int)mediaPlayerInfo.position; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_DURATION_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.duration); + *(int *)p_data->rsp_value.attr_value.value = (int)mediaPlayerInfo.duration; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_PLAYING_ORDER_SUPPORTED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.playing_order_supported); + *(uint16_t *)p_data->rsp_value.attr_value.value = + (uint16_t)mediaPlayerInfo.playing_order_supported; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_PLAYING_ORDER_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.playing_order_value); + *(uint16_t *)p_data->rsp_value.attr_value.value = (uint16_t)mediaPlayerInfo.playing_order_value; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_CCID_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.ccid); + *(uint32_t *)p_data->rsp_value.attr_value.value = (uint8_t)mediaPlayerInfo.ccid; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_SEEKING_SPEED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.seeking_speed); + *(uint8_t *)p_data->rsp_value.attr_value.value = (uint8_t)mediaPlayerInfo.seeking_speed; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_STATE_READ_DESCRIPTOR:{ + *(uint16_t *)p_data->rsp_value.attr_value.value = (uint16_t)device->media_state_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_state_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_READ_DESCRIPTOR: { + *(uint16_t *)p_data->rsp_value.attr_value.value = (uint16_t)device->media_control_point_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_control_point_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = + (uint16_t)device->media_control_point_opcode_supported_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_control_point_opcode_supported_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = (uint16_t)device->media_player_name_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_player_name_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_CHANGED_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = (uint16_t)device->track_changed_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_changed_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = (uint16_t)device->track_title_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_title_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_POSITION_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = (uint16_t)device->track_position_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_position_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_DURATION_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = (uint16_t)device->track_duration_notify;; + p_data->rsp_value.attr_value.len = sizeof(device->track_duration_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_PLAYING_ORDER_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = (uint16_t)device->playing_order_notify; + p_data->rsp_value.attr_value.len = sizeof(device->playing_order_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_MEDIA_STATE_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_state; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_state); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_state_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.player_name; + p_data->oper.WriteDescOp.len = mediaPlayerInfo.player_name_len; + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_player_name_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_ctrl_point; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_ctrl_point); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_control_point_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_SUPPORTED_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_supported_feature; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_supported_feature); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_control_point_opcode_supported_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_CHANGED_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.track_changed; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.track_changed); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_changed_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.title; + p_data->oper.WriteDescOp.len = mediaPlayerInfo.track_title_len; + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_title_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_POSITION_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.position; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.position); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_position_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_DURATION_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.duration; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.duration); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_duration_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_PLAYING_ORDER_DESCRIPTOR_WRITE : { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.playing_order_value; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.playing_order_value); + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_CONGESTION_UPDATE: { + McpCongestionUpdate(p_data); + break; + } + + case MCP_CONNECTION_UPDATE: + case MCP_ACTIVE_DEVICE_UPDATE: + case MCP_PHY_UPDATE: + case MCP_CONNECTION: + case MCP_CONNECTION_CLOSE_EVENT: + case MCP_DISCONNECTION: + case MCP_BOND_STATE_CHANGE_EVENT: + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + + default: + LOG(INFO) << __func__ << " event is not in list"; + break; + } + + return BT_STATUS_SUCCESS; +} + + + +bool MediaStatePlayingHandler(uint32_t event, void* param, uint8_t state) { + LOG(INFO) << __func__ << " handle event " << get_mcp_event_name(event) + << " in state " << get_mcp_media_state_name(state); + mcp_resp_t *p_data = (mcp_resp_t *)param; + RemoteDevice *device = p_data->remoteDevice; + switch(event) { + case MCP_NOTIFY_ALL: { + LOG(INFO) << __func__ << " Notify all handle "<< p_data->handle; + std::vectornotifyDevices = instance->remoteDevices.FindNotifyDevices(p_data->handle); + std::vector::iterator it; + if (notifyDevices.size() <= 0) { + LOG(INFO) << __func__ << " No device register for notification"; + } + for (it = notifyDevices.begin(); it != notifyDevices.end(); it++){ + LOG(INFO) << __func__ << " Notify all handle device id " << it->conn_id; + p_data->remoteDevice = instance->remoteDevices.FindByConnId(it->conn_id); + it->DeviceStateHandlerPointer[it->state](p_data->event, p_data, it->state); + } + break; + } + case MCP_PLAYING_ORDER_WRITE: { + uint8_t data = (uint8_t)(*p_data->oper.WriteOp.data); + if (mediaPlayerInfo.playing_order_supported & data) { + instance->PlayingOrderChangeReq(data & mediaPlayerInfo.playing_order_value); + } else { + LOG(INFO) << __func__ << " ignore playing_order_handle write feature is not supported"; + break; + } + p_data->rsp_value.attr_value.len = 0; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_WRITE: { + uint8_t data = (uint8_t)(*p_data->oper.WriteOp.data); + uint8_t opcode_support = is_opcode_supported(data); + LOG(INFO) << __func__ << " data " << data << " Tx ID: " << p_data->oper.WriteOp.trans_id; + if (!opcode_support) { + uint8_t status = MCP_OPCODE_NOT_SUPPORTED; + std::vector value; + value.push_back(data); + value.push_back(status); + LOG(INFO) << __func__ << "hndl " << p_data->oper.WriteOp.char_handle; + LOG(INFO) << __func__ << "opcode not supported " << data; + GattsOpsQueue::SendNotification(device->conn_id, p_data->oper.WriteOp.char_handle, value, false); + } + if (data != MCP_MEDIA_CONTROL_OPCODE_PLAY) { + instance->MediaControlPointChangeReq((uint32_t)data, device->peer_bda); + LOG(INFO) << __func__ << "media_control_point_handle write "; + } else { + LOG(INFO) << __func__ << " ignore media_control_point_handle write feature is not supported/already playing"; + } + p_data->rsp_value.attr_value.len = 0; + device = p_data->remoteDevice; + LOG(INFO) << __func__ << " p_data->event "<< p_data->event; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_POSITION_WRITE: { + uint32_t track_duration = mediaPlayerInfo.duration; + uint32_t track_position = mediaPlayerInfo.position; + uint32_t data; + uint8_t *w_data = p_data->oper.WriteOp.data; + STREAM_TO_UINT32(data, w_data); + uint32_t position = track_duration; + if ((track_position == (uint32_t)data) || (data < 0) || + (track_duration == 0xFFFF) || (track_duration > 0 && track_duration < data)) { + LOG(INFO) << __func__ << " ignore track_position_handle write"; + } else { + position = data; + instance->TrackPositionChangeReq(data); + LOG(INFO) << __func__ << " track_position_handle write"; + } + std::vector value; + value.push_back(position & 0xff); + value.push_back((position >> 8) & 0xff); + value.push_back((position >> 16) & 0xff); + value.push_back((position >> 24) & 0xff); + GattsOpsQueue::SendNotification(device->conn_id, p_data->oper.WriteOp.char_handle, value, false); + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_STATE_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.media_state); + *(uint8_t *)p_data->rsp_value.attr_value.value = *(uint8_t *)&mediaPlayerInfo.media_state; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_READ: { + uint16_t len = mediaPlayerInfo.player_name_len; + if (device->mtu != -1 && len >= device->mtu - 3) //to check player len should not greater than mtu + len = device->mtu - 3; + p_data->rsp_value.attr_value.len = len; + memcpy(p_data->rsp_value.attr_value.value, mediaPlayerInfo.player_name, + p_data->rsp_value.attr_value.len); + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.media_supported_feature); + *(uint32_t*)p_data->rsp_value.attr_value.value = + *(uint32_t *)&mediaPlayerInfo.media_supported_feature; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_READ: { + uint16_t len = mediaPlayerInfo.track_title_len; + if (device->mtu != -1 && len >= device->mtu - 3) //to check title len should not greater than mtu + len = device->mtu - 3; + p_data->rsp_value.attr_value.len = len; + memcpy(p_data->rsp_value.attr_value.value, mediaPlayerInfo.title, + p_data->rsp_value.attr_value.len); + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_POSITION_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.position); + *(int *)p_data->rsp_value.attr_value.value = *(int *)&mediaPlayerInfo.position; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_DURATION_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.duration); + *(int *)p_data->rsp_value.attr_value.value = *(int *)&mediaPlayerInfo.duration; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_PLAYING_ORDER_SUPPORTED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.playing_order_supported); + *(uint16_t *)p_data->rsp_value.attr_value.value = + *(uint16_t *)&mediaPlayerInfo.playing_order_supported; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_PLAYING_ORDER_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.playing_order_value); + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&mediaPlayerInfo.playing_order_value; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_CCID_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.ccid); + *(uint32_t *)p_data->rsp_value.attr_value.value = *(uint8_t *)&mediaPlayerInfo.ccid; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_SEEKING_SPEED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.seeking_speed); + *(uint8_t *)p_data->rsp_value.attr_value.value = (uint8_t)mediaPlayerInfo.seeking_speed; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_MEDIA_STATE_READ_DESCRIPTOR:{ + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->media_state_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_state_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_MEDIA_CONTROL_POINT_READ_DESCRIPTOR: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->media_control_point_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_control_point_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = + *(uint16_t *)&device->media_control_point_opcode_supported_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_control_point_opcode_supported_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->media_player_name_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_player_name_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_CHANGED_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_changed_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_changed_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_TITLE_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_title_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_title_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_POSITION_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_position_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_position_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_DURATION_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_duration_notify;; + p_data->rsp_value.attr_value.len = sizeof(device->track_duration_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_PLAYING_ORDER_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->playing_order_notify; + p_data->rsp_value.attr_value.len = sizeof(device->playing_order_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + LOG(INFO) << __func__ << " calling device state" << device->state; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_MEDIA_STATE_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_state; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_state); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_state_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.player_name; + p_data->oper.WriteDescOp.len = mediaPlayerInfo.player_name_len; + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_player_name_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_ctrl_point; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_ctrl_point); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_control_point_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_SUPPORTED_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_supported_feature; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_supported_feature); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_control_point_opcode_supported_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_CHANGED_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.track_changed; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.track_changed); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_changed_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.title; + p_data->oper.WriteDescOp.len = mediaPlayerInfo.track_title_len; + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_title_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_POSITION_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.position; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.position); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_position_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_DURATION_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.duration; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.duration); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_duration_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_PLAYING_ORDER_DESCRIPTOR_WRITE : { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.playing_order_value; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.playing_order_value); + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_CONGESTION_UPDATE: { + McpCongestionUpdate(p_data); + break; + } + + case MCP_CONNECTION_UPDATE: + case MCP_ACTIVE_DEVICE_UPDATE: + case MCP_PHY_UPDATE: + case MCP_CONNECTION: + case MCP_DISCONNECTION: + case MCP_CONNECTION_CLOSE_EVENT: + case MCP_BOND_STATE_CHANGE_EVENT: + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + + default: + break; + } + + return BT_STATUS_SUCCESS; +} + +bool MediaStateSeekingHandler(uint32_t event, void* param, uint8_t state) { + LOG(INFO) << __func__ << " handle event " << get_mcp_event_name(event) + << " in state " << get_mcp_media_state_name(state); + mcp_resp_t *p_data = (mcp_resp_t *)param; + RemoteDevice *device = p_data->remoteDevice; + switch(event) { + case MCP_NOTIFY_ALL: { + LOG(INFO) << __func__ << " Notify all handle "<< p_data->handle; + std::vectornotifyDevices = + instance->remoteDevices.FindNotifyDevices(p_data->handle); + if (notifyDevices.size() <= 0) { + LOG(INFO) << __func__ << " No device register for notification"; + break; + } + std::vector::iterator it; + for (it = notifyDevices.begin(); it != notifyDevices.end(); it++){ + p_data->remoteDevice = instance->remoteDevices.FindByConnId(it->conn_id); + it->DeviceStateHandlerPointer[it->state](p_data->event, p_data, it->state); + LOG(INFO) << __func__ << " Notify all handle device id " << it->conn_id; + } + + break; + } + + case MCP_PLAYING_ORDER_WRITE: { + uint8_t data = (uint8_t)(*p_data->oper.WriteOp.data); + if (mediaPlayerInfo.playing_order_supported & data) { + instance->PlayingOrderChangeReq(data & mediaPlayerInfo.playing_order_value); + } else { + LOG(INFO) << __func__ << " ignore playing_order_handle write feature is not supported"; + break; + } + p_data->rsp_value.attr_value.len = 0; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_WRITE: { + uint8_t data = (uint8_t)(*p_data->oper.WriteOp.data); + uint8_t opcode_support = is_opcode_supported(data); + if (!opcode_support) { + uint8_t status = MCP_OPCODE_NOT_SUPPORTED; + std::vector value; + value.push_back(data); + value.push_back(status); + LOG(INFO) << __func__ << "hndl " << p_data->oper.WriteOp.char_handle; + LOG(INFO) << __func__ << "opcode not supported " << data; + GattsOpsQueue::SendNotification(device->conn_id, p_data->oper.WriteOp.char_handle, value, false); + } + + if (((data == MCP_MEDIA_CONTROL_OPCODE_PAUSE) || + (data == MCP_MEDIA_CONTROL_OPCODE_PLAY)) && + (instance->remoteDevices.FindActiveDevice(p_data->remoteDevice)) == false) { + LOG(INFO) << __func__ << " media control point write received from inactive device"; + break; + } + if ((data != MCP_MEDIA_CONTROL_OPCODE_FAST_FORWARD) && (data != MCP_MEDIA_CONTROL_OPCODE_FAST_REWIND) + && (data != MCP_MEDIA_CONTROL_OPCODE_MOVE_RELATIVE)) { + instance->MediaControlPointChangeReq(data, device->peer_bda); + LOG(INFO) << __func__ << "media_control_point_handle write "; + } + p_data->rsp_value.attr_value.len = 0; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_POSITION_WRITE: { + uint32_t track_duration = mediaPlayerInfo.duration; + uint32_t track_position = mediaPlayerInfo.position; + uint32_t data; + uint8_t *w_data = p_data->oper.WriteOp.data; + uint32_t position = track_duration; + STREAM_TO_UINT32(data, w_data); + if ((track_position == (uint32_t)data) || (data < 0) || + (track_duration == 0xFFFF) || (track_duration > 0 && track_duration < data)) { + LOG(INFO) << __func__ << " ignore track_position_handle write"; + } else { + position = data; + instance->TrackPositionChangeReq(data); + LOG(INFO) << __func__ << " track_position_handle write"; + } + std::vector value; + value.push_back(position & 0xff); + value.push_back((position >> 8) & 0xff); + value.push_back((position >> 16) & 0xff); + value.push_back((position >> 24) & 0xff); + GattsOpsQueue::SendNotification(device->conn_id, p_data->oper.WriteOp.char_handle, value, false); + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_STATE_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.media_state); + *(uint8_t *)p_data->rsp_value.attr_value.value = *(uint8_t *)&mediaPlayerInfo.media_state; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_READ: { + uint16_t len = mediaPlayerInfo.player_name_len; + if (device->mtu != -1 && len >= device->mtu - 3) //to check player len should not greater than mtu + len = device->mtu - 3; + p_data->rsp_value.attr_value.len = len; + memcpy(p_data->rsp_value.attr_value.value, mediaPlayerInfo.player_name, + p_data->rsp_value.attr_value.len); + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.media_supported_feature); + *(uint32_t*)p_data->rsp_value.attr_value.value = + *(uint32_t *)&mediaPlayerInfo.media_supported_feature; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_READ: { + uint16_t len = mediaPlayerInfo.track_title_len; + if (device->mtu != -1 && len >= device->mtu - 3) //to check title len should not greater than mtu + len = device->mtu - 3; + p_data->rsp_value.attr_value.len = len; + memcpy(p_data->rsp_value.attr_value.value, mediaPlayerInfo.title, + p_data->rsp_value.attr_value.len); + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_POSITION_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.position); + *(int *)p_data->rsp_value.attr_value.value = *(int *)&mediaPlayerInfo.position; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_DURATION_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.duration); + *(int *)p_data->rsp_value.attr_value.value = *(int *)&mediaPlayerInfo.duration; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_PLAYING_ORDER_SUPPORTED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.playing_order_supported); + *(uint16_t *)p_data->rsp_value.attr_value.value = + *(uint16_t *)&mediaPlayerInfo.playing_order_supported; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_PLAYING_ORDER_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.playing_order_value); + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&mediaPlayerInfo.playing_order_value; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_CCID_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.ccid); + *(uint32_t *)p_data->rsp_value.attr_value.value = *(uint8_t *)&mediaPlayerInfo.ccid; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_SEEKING_SPEED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.seeking_speed); + *(uint8_t *)p_data->rsp_value.attr_value.value = (uint8_t)mediaPlayerInfo.seeking_speed; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_STATE_READ_DESCRIPTOR:{ + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->media_state_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_state_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_READ_DESCRIPTOR: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->media_control_point_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_control_point_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = + *(uint16_t *)&device->media_control_point_opcode_supported_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_control_point_opcode_supported_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->media_player_name_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_player_name_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_CHANGED_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_changed_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_changed_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_title_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_title_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_POSITION_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_position_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_position_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_DURATION_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_duration_notify;; + p_data->rsp_value.attr_value.len = sizeof(device->track_duration_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_PLAYING_ORDER_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->playing_order_notify; + p_data->rsp_value.attr_value.len = sizeof(device->playing_order_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_MEDIA_STATE_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_state; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_state); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_state_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.player_name; + p_data->oper.WriteDescOp.len = mediaPlayerInfo.player_name_len; + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_player_name_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_ctrl_point; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_ctrl_point); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_control_point_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_SUPPORTED_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_supported_feature; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_supported_feature); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_control_point_opcode_supported_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_CHANGED_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.track_changed; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.track_changed); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_changed_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.title; + p_data->oper.WriteDescOp.len = mediaPlayerInfo.track_title_len; + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_title_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_POSITION_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.position; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.position); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_position_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_DURATION_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.duration; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.duration); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_duration_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_PLAYING_ORDER_DESCRIPTOR_WRITE : { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.playing_order_value; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.playing_order_value); + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_CONGESTION_UPDATE: { + LOG(INFO) << __func__ << ": device MCP_CONGESTION_UPDATE update: " << event; + McpCongestionUpdate(p_data); + break; + } + + case MCP_CONNECTION_UPDATE: + case MCP_ACTIVE_DEVICE_UPDATE: + case MCP_PHY_UPDATE: + case MCP_CONNECTION: + case MCP_DISCONNECTION: + case MCP_CONNECTION_CLOSE_EVENT: + case MCP_BOND_STATE_CHANGE_EVENT: + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + + default: + break; + } + return BT_STATUS_SUCCESS; +} + + +bool MediaStatePauseHandler(uint32_t event, void* param, uint8_t state) { + LOG(INFO) << __func__ << " handle event " << get_mcp_event_name(event) + << " in state " << get_mcp_media_state_name(state); + + mcp_resp_t *p_data = (mcp_resp_t *)param; + RemoteDevice *device = p_data->remoteDevice; + switch(event) { + case MCP_NOTIFY_ALL: { + LOG(INFO) << __func__ << " Notify all handle "<< p_data->handle; + std::vectornotifyDevices = instance->remoteDevices.FindNotifyDevices(p_data->handle); + std::vector::iterator it; + if (notifyDevices.size() <= 0) { + LOG(INFO) << __func__ << " No device register for notification"; + } + for (it = notifyDevices.begin(); it != notifyDevices.end(); it++){ + p_data->remoteDevice = instance->remoteDevices.FindByConnId(it->conn_id); + it->DeviceStateHandlerPointer[it->state](p_data->event, p_data, it->state); + LOG(INFO) << __func__ << " Notify all handle device id " << it->conn_id; + + } + break; + } + + case MCP_PLAYING_ORDER_WRITE: { + uint8_t data = (uint8_t)(*p_data->oper.WriteOp.data); + if (mediaPlayerInfo.playing_order_supported & data) { + instance->PlayingOrderChangeReq(data & mediaPlayerInfo.playing_order_value); + } else { + LOG(INFO) << __func__ << " ignore playing_order_handle write feature is not supported"; + break; + } + p_data->rsp_value.attr_value.len = 0; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_WRITE: { + uint8_t data = (uint8_t)(*p_data->oper.WriteOp.data); + uint8_t opcode_support = is_opcode_supported(data); + if (!opcode_support) { + uint8_t status = MCP_OPCODE_NOT_SUPPORTED; + std::vector value; + value.push_back(data); + value.push_back(status); + LOG(INFO) << __func__ << "hndl " << p_data->oper.WriteOp.char_handle; + LOG(INFO) << __func__ << "opcode not supported " << data; + GattsOpsQueue::SendNotification(device->conn_id, p_data->oper.WriteOp.char_handle, value, false); + } + + if ((data != MCP_MEDIA_CONTROL_OPCODE_PLAY) && + (instance->remoteDevices.FindActiveDevice(p_data->remoteDevice) == false) + && is_pts_running() == false) { + LOG(INFO) << __func__ << " media control point write received from inactive device"; + break; + } + if (data != MCP_MEDIA_CONTROL_OPCODE_PAUSE) { + // MCP_MEDIA_CONTROL_STOP need to check for stop + instance->MediaControlPointChangeReq(data, device->peer_bda); + LOG(INFO) << __func__ << "media_control_point_handle write "; + } else { + LOG(INFO) << __func__ << " ignore media_control_point_handle write feature is not supported / already paused"; + } + p_data->rsp_value.attr_value.len = 0; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_POSITION_WRITE: { + uint32_t track_duration = mediaPlayerInfo.duration; + uint32_t track_position = mediaPlayerInfo.position; + uint32_t data; + uint8_t *w_data = p_data->oper.WriteOp.data; + STREAM_TO_UINT32(data, w_data); + uint32_t position = track_duration; + if ((track_position == (uint32_t)data) || (data < 0) || + (track_duration == 0xFFFF) || (track_duration > 0 && track_duration < data)) { + LOG(INFO) << __func__ << " ignore track_position_handle write"; + } else { + position = data; + instance->TrackPositionChangeReq(data); + LOG(INFO) << __func__ << " track_position_handle write"; + } + std::vector value; + value.push_back(position & 0xff); + value.push_back((position >> 8) & 0xff); + value.push_back((position >> 16) & 0xff); + value.push_back((position >> 24) & 0xff); + GattsOpsQueue::SendNotification(device->conn_id, p_data->oper.WriteOp.char_handle, value, false); + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_STATE_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.media_state); + *(uint8_t *)p_data->rsp_value.attr_value.value = *(uint8_t *)&mediaPlayerInfo.media_state; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_READ: { + uint16_t len = mediaPlayerInfo.player_name_len; + LOG(INFO) << __func__ << " before player name len: " << len; + LOG(INFO) << __func__ << " before player name mtu: " << device->mtu; + if (device->mtu != -1 && len >= device->mtu - 3) //to check player len should not greater than mtu + len = device->mtu - 3; + p_data->rsp_value.attr_value.len = len; + LOG(INFO) << __func__ << " player name len: " << len; + memcpy(p_data->rsp_value.attr_value.value, mediaPlayerInfo.player_name, + p_data->rsp_value.attr_value.len); + p_data->event = MCP_READ_RSP; + LOG(INFO) << __func__ << " calling player name read"; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.media_supported_feature); + *(uint32_t*)p_data->rsp_value.attr_value.value = + *(uint32_t *)&mediaPlayerInfo.media_supported_feature; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_READ: { + uint16_t len = mediaPlayerInfo.track_title_len; + if (device->mtu != -1 && len >= device->mtu - 3) //to check title len should not greater than mtu + len = device->mtu - 3; + p_data->rsp_value.attr_value.len = len; + memcpy(p_data->rsp_value.attr_value.value, mediaPlayerInfo.title, + p_data->rsp_value.attr_value.len); + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_POSITION_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.position); + *(int *)p_data->rsp_value.attr_value.value = *(int *)&mediaPlayerInfo.position; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_DURATION_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.duration); + *(int *)p_data->rsp_value.attr_value.value = *(int *)&mediaPlayerInfo.duration; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_PLAYING_ORDER_SUPPORTED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.playing_order_supported); + *(uint16_t *)p_data->rsp_value.attr_value.value = + *(uint16_t*)&mediaPlayerInfo.playing_order_supported; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_PLAYING_ORDER_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.playing_order_value); + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&mediaPlayerInfo.playing_order_value; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_CCID_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.ccid); + *(uint32_t *)p_data->rsp_value.attr_value.value = *(uint8_t *)&mediaPlayerInfo.ccid; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_SEEKING_SPEED_READ: { + p_data->rsp_value.attr_value.len = sizeof(mediaPlayerInfo.seeking_speed); + *(uint8_t *)p_data->rsp_value.attr_value.value = (uint8_t)mediaPlayerInfo.seeking_speed; + p_data->event = MCP_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_STATE_READ_DESCRIPTOR:{ + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->media_state_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_state_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_READ_DESCRIPTOR: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->media_control_point_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_control_point_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_OPCODE_SUPPORTED_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = + *(uint16_t *)&device->media_control_point_opcode_supported_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_control_point_opcode_supported_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->media_player_name_notify; + p_data->rsp_value.attr_value.len = sizeof(device->media_player_name_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_CHANGED_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_changed_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_changed_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_title_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_title_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_POSITION_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_position_notify; + p_data->rsp_value.attr_value.len = sizeof(device->track_position_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_DURATION_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->track_duration_notify;; + p_data->rsp_value.attr_value.len = sizeof(device->track_duration_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_PLAYING_ORDER_DESCRIPTOR_READ: { + *(uint16_t *)p_data->rsp_value.attr_value.value = *(uint16_t *)&device->playing_order_notify; + p_data->rsp_value.attr_value.len = sizeof(device->playing_order_notify); + p_data->event = MCP_DESCRIPTOR_READ_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_MEDIA_STATE_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_state; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_state); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_state_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_PLAYER_NAME_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.player_name; + p_data->oper.WriteDescOp.len = mediaPlayerInfo.player_name_len; + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_player_name_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_ctrl_point; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_ctrl_point); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_control_point_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_MEDIA_CONTROL_POINT_SUPPORTED_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.media_supported_feature; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.media_supported_feature); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.media_control_point_opcode_supported_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_CHANGED_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.track_changed; + p_data->oper.WriteDescOp.len = sizeof (mediaPlayerInfo.track_changed); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_changed_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + case MCP_TRACK_TITLE_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.title; + p_data->oper.WriteDescOp.len = mediaPlayerInfo.track_title_len; + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_title_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_POSITION_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.position; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.position); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_position_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_TRACK_DURATION_DESCRIPTOR_WRITE: { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.duration; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.duration); + p_data->oper.WriteDescOp.char_handle = mcsServerServiceInfo.track_duration_handle; + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_PLAYING_ORDER_DESCRIPTOR_WRITE : { + p_data->oper.WriteDescOp.data = (uint8_t *)&mediaPlayerInfo.playing_order_value; + p_data->oper.WriteDescOp.len = sizeof(mediaPlayerInfo.playing_order_value); + p_data->event = MCP_DESCRIPTOR_WRITE_RSP; + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + } + + case MCP_CONGESTION_UPDATE: { + McpCongestionUpdate(p_data); + break; + } + + case MCP_CONNECTION_UPDATE: + case MCP_ACTIVE_DEVICE_UPDATE: + case MCP_PHY_UPDATE: + case MCP_CONNECTION: + case MCP_DISCONNECTION: + case MCP_CONNECTION_CLOSE_EVENT: + case MCP_BOND_STATE_CHANGE_EVENT: + device = p_data->remoteDevice; + device->DeviceStateHandlerPointer[device->state](p_data->event, p_data, device->state); + break; + + default: + break; + } + return BT_STATUS_SUCCESS; +} + +bool DeviceStateConnectionHandler(uint32_t event, void* param, uint8_t state) { + LOG(INFO) << __func__ << " device connected handle " << get_mcp_event_name(event); + mcp_resp_t *p_data = (mcp_resp_t *) param; + switch (event) { + case MCP_NOTIFY_ALL: { + LOG(INFO) << __func__ << " device notify all "; + if (is_pts_running() || p_data->remoteDevice->active_profile == 0x10) {// need to check profile value + std::vector value; + tMCP_MEDIA_UPDATE *notfiyUpdate = &p_data->oper.MediaUpdateOp; + value.assign(notfiyUpdate->data, notfiyUpdate->data + notfiyUpdate->len); + GattsOpsQueue::SendNotification(p_data->remoteDevice->conn_id, + p_data->handle, value, false); + } + break; + } + + case MCP_READ_RSP: + BTA_GATTS_SendRsp(p_data->remoteDevice->conn_id, p_data->oper.ReadOp.trans_id, + BT_STATUS_SUCCESS, &p_data->rsp_value); + + break; + + case MCP_DESCRIPTOR_READ_RSP: + BTA_GATTS_SendRsp(p_data->remoteDevice->conn_id, p_data->oper.ReadDescOp.trans_id, + BT_STATUS_SUCCESS, &p_data->rsp_value); + + break; + + case MCP_DESCRIPTOR_WRITE_RSP: { + LOG(INFO) << __func__ << " device MCP_DESCRIPTOR_WRITE_RSP update rsp :" << p_data->oper.WriteDescOp.need_rsp; + tGATTS_RSP rsp_struct; + rsp_struct.attr_value.handle = p_data->rsp_value.attr_value.handle; + rsp_struct.attr_value.offset = p_data->rsp_value.attr_value.offset; + if (p_data->remoteDevice->congested == false && + p_data->oper.WriteDescOp.need_rsp) { + //send rsp to write + LOG(INFO) << __func__ << " gatt send rsp status" << p_data->oper.WriteDescOp.status; + BTA_GATTS_SendRsp(p_data->remoteDevice->conn_id, p_data->oper.WriteDescOp.trans_id, + p_data->oper.WriteDescOp.status, &rsp_struct); + } + break; + } + + case MCP_WRITE_RSP: { + LOG(INFO) << __func__ << " device MCP_WRITE_RSP update Tx ID: " << p_data->oper.WriteOp.trans_id; + bool need_rsp = p_data->oper.WriteOp.need_rsp; + if (need_rsp) { + BTA_GATTS_SendRsp(p_data->remoteDevice->conn_id, p_data->oper.WriteOp.trans_id, + BT_STATUS_SUCCESS, &p_data->rsp_value); + } + break; + } + + case MCP_CONNECTION_UPDATE: { + p_data->remoteDevice->latency = p_data->oper.ConnectionUpdateOp.latency; + p_data->remoteDevice->timeout = p_data->oper.ConnectionUpdateOp.timeout; + p_data->remoteDevice->interval = p_data->oper.ConnectionUpdateOp.interval; + p_data->remoteDevice->active_profile = 0x10; + break; + } + + case MCP_ACTIVE_DEVICE_UPDATE: { + p_data->remoteDevice->active_profile = p_data->oper.SetActiveDeviceOp.profile; + break; + } + case MCP_PHY_UPDATE: { + p_data->remoteDevice->rx_phy = p_data->oper.PhyOp.rx_phy; + p_data->remoteDevice->tx_phy = p_data->oper.PhyOp.tx_phy; + break; + } + + case MCP_MTU_UPDATE: { + p_data->remoteDevice->mtu = p_data->oper.MtuOp.mtu; + break; + } + + case MCP_CONGESTION_UPDATE: { + McpCongestionUpdate(p_data); + break; + } + + case MCP_DISCONNECTION: { + LOG(INFO) << __func__ << " device event MCP_DISCONNECTION remove "; + instance->remoteDevices.Remove(p_data->remoteDevice->peer_bda); + instance->OnConnectionStateChange(MCP_DISCONNECTED, p_data->remoteDevice->peer_bda); + break; + } + + case MCP_CONNECTION_CLOSE_EVENT: { + LOG(INFO) << __func__ << " device connection closing"; + // Close active connection + if (p_data->remoteDevice->conn_id != 0) + BTA_GATTS_Close(p_data->remoteDevice->conn_id); + else + BTA_GATTS_CancelOpen(mcsServerServiceInfo.server_if, p_data->remoteDevice->peer_bda, true); + + // Cancel pending background connections + BTA_GATTS_CancelOpen(mcsServerServiceInfo.server_if, p_data->remoteDevice->peer_bda, false); + break; + } + + case MCP_BOND_STATE_CHANGE_EVENT: + LOG(INFO) << __func__ << "Bond state change : "; + instance->remoteDevices.Remove(p_data->remoteDevice->peer_bda); + break; + + default: + LOG(INFO) << __func__ << " event not matched"; + break; + } + + return BT_STATUS_SUCCESS; +} + + +bool DeviceStateDisconnectedHandler(uint32_t event, void* param, uint8_t state) { + LOG(INFO) << __func__ << " device disconnected handle " << get_mcp_event_name(event); + mcp_resp_t *p_data = (mcp_resp_t *) param; + switch (event) { + case MCP_CONNECTION: { + p_data->remoteDevice->state = MCP_CONNECTED; + p_data->remoteDevice->media_state_notify = 0x00; + p_data->remoteDevice->media_player_name_notify = 0x00; + p_data->remoteDevice->media_control_point_notify = 0x00; + p_data->remoteDevice->track_changed_notify = 0x00; + p_data->remoteDevice->track_duration_notify = 0x00; + p_data->remoteDevice->track_title_notify = 0x00; + p_data->remoteDevice->track_position_notify = 0x00; + p_data->remoteDevice->playing_order_notify = 0x00; + p_data->remoteDevice->congested = false; + + p_data->remoteDevice->timeout = 0; + p_data->remoteDevice->latency = 0; + p_data->remoteDevice->interval = 0; + p_data->remoteDevice->rx_phy = 0; + p_data->remoteDevice->tx_phy = 0; + p_data->remoteDevice->mtu = -1; + instance->OnConnectionStateChange(MCP_CONNECTED, p_data->remoteDevice->peer_bda); + break; + } + + case MCP_CONGESTION_UPDATE: { + McpCongestionUpdate(p_data); + break; + } + + case MCP_MTU_UPDATE: + case MCP_PHY_UPDATE: + case MCP_READ_RSP: + case MCP_DESCRIPTOR_READ_RSP: + case MCP_WRITE_RSP: + case MCP_DESCRIPTOR_WRITE_RSP: + case MCP_NOTIFY_ALL: + case MCP_DISCONNECTION: + case MCP_CONNECTION_CLOSE_EVENT: + case MCP_BOND_STATE_CHANGE_EVENT: + default: + //ignore event + LOG(INFO) << __func__ << " Ignore event " << get_mcp_event_name(event); + break; + } + return BT_STATUS_SUCCESS; +} + +void McpCongestionUpdate(mcp_resp_t *p_data) { + p_data->remoteDevice->congested = p_data->oper.CongestionOp.congested; + LOG(INFO) << __func__ << ": conn_id: " << p_data->remoteDevice->conn_id + << ", congested: " << p_data->remoteDevice->congested; + + GattsOpsQueue::CongestionCallback(p_data->remoteDevice->conn_id, + p_data->remoteDevice->congested); +} diff --git a/le_audio/system/bt/bta/vcp/bta_vcp_controller.cc b/le_audio/system/bt/bta/vcp/bta_vcp_controller.cc new file mode 100644 index 00000000000..5eb7d6c56ae --- /dev/null +++ b/le_audio/system/bt/bta/vcp/bta_vcp_controller.cc @@ -0,0 +1,1108 @@ +/* + *Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/****************************************************************************** + * + * Copyright 2018 The Android Open Source Project + * + * 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. + * + ******************************************************************************/ + +#include "bt_target.h" +#include "bta_vcp_controller_api.h" +#include "bta_gatt_api.h" +#include "btm_int.h" +#include "device/include/controller.h" +#include "gap_api.h" +#include "gatt_api.h" +#include "gattc_ops_queue.h" +#include "osi/include/properties.h" + +#include +#include +#include +#include +#include +#include + +using base::Closure; +using bluetooth::Uuid; +using bluetooth::bap::GattOpsQueue; +using bluetooth::vcp_controller::ConnectionState; + +// Assigned Numbers for VCS +Uuid VCS_UUID = Uuid::FromString("1844"); +Uuid VCS_VOLUME_STATE_UUID = Uuid::FromString("2B7D"); +Uuid VCS_VOLUME_CONTROL_POINT_UUID = Uuid::FromString("2B7E"); +Uuid VCS_VOLUME_FLAGS_UUID = Uuid::FromString("2B7F"); + +#define VCS_RETRY_SET_ABS_VOL 0x01 +#define VCS_RETRY_SET_MUTE_STATE 0x02 + +// VCS Application Error Code +#define VCS_INVALID_CHANGE_COUNTER 0x80 +#define VCS_OPCODE_NOT_SUPPORTED 0x81 + +void vcp_controller_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data); +void vcp_controller_encryption_callback(const RawAddress*, tGATT_TRANSPORT, void*, tBTM_STATUS); +const char* vcp_controller_gatt_callback_evt_str(uint8_t event); +const char* vcp_controller_handle_vcs_evt_str(uint8_t event); + +class VcpControllerImpl; +static VcpControllerImpl* instance; + +class RendererDevices { + private: + + public: + void Add(RendererDevice device) { + if (FindByAddress(device.address) != nullptr) return; + devices.push_back(device); + } + + void Remove(const RawAddress& address) { + for (auto it = devices.begin(); it != devices.end();) { + if (it->address != address) { + ++it; + continue; + } + + it = devices.erase(it); + return; + } + } + + RendererDevice* FindByAddress(const RawAddress& address) { + auto iter = std::find_if(devices.begin(), devices.end(), + [&address](const RendererDevice& device) { + return device.address == address; + }); + + return (iter == devices.end()) ? nullptr : &(*iter); + } + + RendererDevice* FindByConnId(uint16_t conn_id) { + auto iter = std::find_if(devices.begin(), devices.end(), + [&conn_id](const RendererDevice& device) { + return device.conn_id == conn_id; + }); + + return (iter == devices.end()) ? nullptr : &(*iter); + } + + size_t size() { return (devices.size()); } + + std::vector devices; +}; + +class VcpControllerImpl : public VcpController { + private: + uint8_t gatt_if; + bluetooth::vcp_controller::VcpControllerCallbacks* callbacks; + RendererDevices rendererDevices; + + public: + virtual ~VcpControllerImpl() = default; + + VcpControllerImpl(bluetooth::vcp_controller::VcpControllerCallbacks* callbacks) + : gatt_if(0), + callbacks(callbacks) { + LOG(INFO) << "VcpControllerImpl gattc app register"; + + BTA_GATTC_AppRegister( + vcp_controller_gattc_callback, + base::Bind( + [](uint8_t client_id, uint8_t status) { + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Can't start Vcp profile - no gatt " + "clients left!"; + return; + } + instance->gatt_if = client_id; + }), true); + } + + void Connect(const RawAddress& address, bool isDirect) override { + LOG(INFO) << __func__ << " " << address << ", isDirect = " << logbool(isDirect); + RendererDevice* rendererDevice = rendererDevices.FindByAddress(address); + + if (rendererDevice) { + LOG(INFO) << "Device already in connected/connecting state" << address; + return; + } + + rendererDevices.Add(RendererDevice(address)); + rendererDevice = rendererDevices.FindByAddress(address); + if (!rendererDevice) { + LOG(INFO) << "Device address could not be foundL"; + return; + } + rendererDevice->state = BTA_VCP_CONNECTING; + callbacks->OnConnectionState(ConnectionState::CONNECTING, rendererDevice->address); + + if (!isDirect) { + rendererDevice->bg_conn = true; + } + + BTA_GATTC_Open(gatt_if, address, isDirect, GATT_TRANSPORT_LE, false); + } + + void Disconnect(const RawAddress& address) override { + RendererDevice* rendererDevice = rendererDevices.FindByAddress(address); + + if (!rendererDevice) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + + LOG(INFO) << __func__ << " " << address; + rendererDevice->state = BTA_VCP_DISCONNECTING; + callbacks->OnConnectionState(ConnectionState::DISCONNECTING, rendererDevice->address); + VcpGattClose(rendererDevice); + } + + void SetAbsVolume(const RawAddress& address, uint8_t volume) { + RendererDevice* rendererDevice = rendererDevices.FindByAddress(address); + + if (!rendererDevice) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + + if (rendererDevice->conn_id == 0) { + LOG(INFO) << __func__ << ": GATT is not connected, skip set absolute volume"; + return; + } + + if (rendererDevice->state != BTA_VCP_CONNECTED) { + LOG(INFO) + << __func__ << ": VCP is not connected, skip set absolute volume, state = " + << loghex(rendererDevice->state); + return; + } + // Send the data packet + LOG(INFO) << __func__ << ": Set abs volume. device=" << rendererDevice->address + << ", volume=" << loghex(volume); + + rendererDevice->vcs.pending_volume_setting = volume; + + uint8_t p_buf[256]; + SetAbsVolumeOp set_abs_vol_op; + set_abs_vol_op.op_id = VCS_CONTROL_POINT_OP_SET_ABS_VOL; + set_abs_vol_op.change_counter = rendererDevice->vcs.volume_state.change_counter; + set_abs_vol_op.volume_setting = volume; + + memcpy(p_buf, &set_abs_vol_op , sizeof(set_abs_vol_op)); + std::vector vect_val(p_buf, p_buf + sizeof(set_abs_vol_op)); + + GattOpsQueue::WriteCharacteristic(gatt_if, + rendererDevice->conn_id, rendererDevice->vcs.volume_control_point_handle, vect_val, + GATT_WRITE, VcpControllerImpl::OnSetAbsVolumeStatic, nullptr); + } + + void Mute(const RawAddress& address) { + RendererDevice* rendererDevice = rendererDevices.FindByAddress(address); + + if (!rendererDevice) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + + if (rendererDevice->conn_id == 0) { + LOG(INFO) << __func__ << ": GATT is not connected, skip mute"; + return; + } + + if (rendererDevice->state != BTA_VCP_CONNECTED) { + LOG(INFO) + << __func__ << ": VCP is not connected, skip mute, state = " + << loghex(rendererDevice->state); + return; + } + // Send the data packet + LOG(INFO) << __func__ << ": Mute device=" << rendererDevice->address; + + rendererDevice->vcs.pending_mute_setting = VCS_MUTE_STATE; + + uint8_t p_buf[256]; + MuteOp mute_op; + mute_op.op_id = VCS_CONTROL_POINT_OP_MUTE; + mute_op.change_counter = rendererDevice->vcs.volume_state.change_counter; + + memcpy(p_buf, &mute_op , sizeof(mute_op)); + std::vector vect_val(p_buf, p_buf + sizeof(mute_op)); + + GattOpsQueue::WriteCharacteristic(gatt_if, + rendererDevice->conn_id, rendererDevice->vcs.volume_control_point_handle, vect_val, + GATT_WRITE, VcpControllerImpl::OnMuteStatic, nullptr); + } + + void Unmute(const RawAddress& address) { + RendererDevice* rendererDevice = rendererDevices.FindByAddress(address); + + if (!rendererDevice) { + LOG(WARNING) << "Device not connected to profile" << address; + return; + } + + if (rendererDevice->conn_id == 0) { + LOG(INFO) << __func__ << ": GATT is not connected, skip unmute."; + return; + } + + if (rendererDevice->state != BTA_VCP_CONNECTED) { + LOG(INFO) + << __func__ << ": VCP is not connected, skip unmute, state = " + << loghex(rendererDevice->state); + return; + } + // Send the data packet + LOG(INFO) << __func__ << ": unmute device=" << rendererDevice->address; + + rendererDevice->vcs.pending_mute_setting = VCS_UNMUTE_STATE; + + uint8_t p_buf[256]; + UnmuteOp unmute_op; + unmute_op.op_id = VCS_CONTROL_POINT_OP_UNMUTE; + unmute_op.change_counter = rendererDevice->vcs.volume_state.change_counter; + + memcpy(p_buf, &unmute_op , sizeof(unmute_op)); + std::vector vect_val(p_buf, p_buf + sizeof(unmute_op)); + + GattOpsQueue::WriteCharacteristic(gatt_if, + rendererDevice->conn_id, rendererDevice->vcs.volume_control_point_handle, vect_val, + GATT_WRITE, VcpControllerImpl::OnUnmuteStatic, nullptr); + } + + void VcpGattClose(RendererDevice* rendererDevice) { + LOG(INFO) << __func__ << " " << rendererDevice->address; + + // Removes all registrations for connection. + BTA_GATTC_CancelOpen(gatt_if, rendererDevice->address, false); + rendererDevice->bg_conn = false; + + if (rendererDevice->conn_id) { + GattOpsQueue::Clean(rendererDevice->conn_id); + BTA_GATTC_Close(rendererDevice->conn_id); + } else { + // cancel pending direct connect + BTA_GATTC_CancelOpen(gatt_if, rendererDevice->address, true); + PostDisconnected(rendererDevice); + } + } + + void OnGattConnected(tGATT_STATUS status, uint16_t conn_id, + tGATT_IF client_if, RawAddress address, + tBTA_TRANSPORT transport, uint16_t mtu) { + RendererDevice* rendererDevice = rendererDevices.FindByAddress(address); + LOG(INFO) << __func__ << ": address=" << address << ", conn_id=" << conn_id; + + if (!rendererDevice) { + LOG(WARNING) << "Closing connection to non volume renderer device, address=" + << address; + BTA_GATTC_Close(conn_id); + return; + } + + if (status != GATT_SUCCESS) { + if (rendererDevice->bg_conn) { + // whitelist connection failed, that's ok. + LOG(INFO) << "bg conn failed, return immediately"; + return; + } + + LOG(INFO) << "Failed to connect to volume renderer device"; + rendererDevices.Remove(address); + callbacks->OnConnectionState(ConnectionState::DISCONNECTED, address); + return; + } + + if (rendererDevice->bg_conn) { + LOG(INFO) << __func__ << ": backgound connection from: address=" << address; + } + + rendererDevice->bg_conn = false; + rendererDevice->conn_id = conn_id; + + /* verify bond */ + uint8_t sec_flag = 0; + BTM_GetSecurityFlagsByTransport(address, &sec_flag, BT_TRANSPORT_LE); + + LOG(INFO) << __func__ << ": sec_flag =" << loghex(sec_flag); + if (sec_flag & BTM_SEC_FLAG_ENCRYPTED) { + /* if link has been encrypted */ + OnEncryptionComplete(address, true); + return; + } + + if (sec_flag & BTM_SEC_FLAG_LKEY_KNOWN) { + /* if bonded and link not encrypted */ + sec_flag = BTM_BLE_SEC_ENCRYPT; + BTM_SetEncryption(address, BTA_TRANSPORT_LE, vcp_controller_encryption_callback, nullptr, + sec_flag); + return; + } + + /* otherwise let it go through */ + OnEncryptionComplete(address, true); + } + + void OnGattDisconnected(tGATT_STATUS status, uint16_t conn_id, + tGATT_IF client_if, RawAddress remote_bda, + tBTA_GATT_REASON reason) { + RendererDevice* rendererDevice = rendererDevices.FindByConnId(conn_id); + + if (!rendererDevice) { + LOG(WARNING) << "Skipping unknown device disconnect, conn_id=" + << loghex(conn_id); + return; + } + LOG(INFO) << __func__ << ": conn_id=" << loghex(conn_id) + << ", reason=" << loghex(reason) << ", remote_bda=" << remote_bda; + + PostDisconnected(rendererDevice); + } + + void PostDisconnected(RendererDevice* rendererDevice) { + LOG(INFO) << __func__ << " " << rendererDevice->address; + rendererDevice->state = BTA_VCP_DISCONNECTED; + + if(rendererDevice->vcs.volume_state_handle != 0xFFFF) { + BTIF_TRACE_WARNING("%s: Deregister notifications", __func__); + BTA_GATTC_DeregisterForNotifications(gatt_if, + rendererDevice->address, + rendererDevice->vcs.volume_state_handle); + } + if(rendererDevice->vcs.volume_flags_handle != 0xFFFF) { + BTIF_TRACE_WARNING("%s: Deregister notifications", __func__); + BTA_GATTC_DeregisterForNotifications(gatt_if, + rendererDevice->address, + rendererDevice->vcs.volume_flags_handle); + } + + if (rendererDevice->conn_id) { + GattOpsQueue::Clean(rendererDevice->conn_id); + rendererDevice->conn_id = 0; + } + + callbacks->OnConnectionState(ConnectionState::DISCONNECTED, rendererDevice->address); + rendererDevices.Remove(rendererDevice->address); + } + + void OnEncryptionComplete(const RawAddress& address, bool success) { + RendererDevice* rendererDevice = rendererDevices.FindByAddress(address); + LOG(INFO) << __func__ << " " << address; + + if (!rendererDevice) { + LOG(WARNING) << "Skipping unknown device" << address; + return; + } + + if (!success) { + LOG(ERROR) << "encryption failed"; + BTA_GATTC_Close(rendererDevice->conn_id); + return; + } + + BTA_GATTC_ServiceSearchRequest(rendererDevice->conn_id, &VCS_UUID); + } + + void OnServiceSearchComplete(uint16_t conn_id, tGATT_STATUS status) { + RendererDevice* rendererDevice = rendererDevices.FindByConnId(conn_id); + LOG(INFO) << __func__; + + if (!rendererDevice) { + LOG(WARNING) << "Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + + if (status != GATT_SUCCESS) { + /* close connection and report service discovery complete with error */ + LOG(ERROR) << "Service discovery failed"; + VcpGattClose(rendererDevice); + return; + } + + const std::vector* services = BTA_GATTC_GetServices(conn_id); + + const gatt::Service* service = nullptr; + if (services) { + for (const gatt::Service& tmp : *services) { + if (tmp.uuid == Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER)) { + /* + LOG(INFO) << "Found UUID_SERVCLASS_GATT_SERVER, handle=" + << loghex(tmp.handle); + const gatt::Service* service_changed_service = &tmp; + FindServerChangedCCCHandle(conn_id, service_changed_service); + */ + } else if (tmp.uuid == VCS_UUID) { + LOG(INFO) << "Found Volume Control service, handle=" << loghex(tmp.handle); + service = &tmp; + } + } + } else { + LOG(ERROR) << "no services found for conn_id: " << conn_id; + return; + } + + if (!service) { + LOG(ERROR) << "No VCS found"; + VcpGattClose(rendererDevice); + return; + } + + for (const gatt::Characteristic& charac : service->characteristics) { + if (charac.uuid == VCS_VOLUME_STATE_UUID) { + rendererDevice->vcs.volume_state_handle = charac.value_handle; + + rendererDevice->vcs.volume_state_ccc_handle = + FindCccHandle(conn_id, charac.value_handle); + if (!rendererDevice->vcs.volume_state_ccc_handle) { + LOG(ERROR) << __func__ << ": cannot find volume state CCC descriptor"; + continue; + } + + LOG(INFO) << __func__ + << ": vcs volume_state_handle=" << loghex(charac.value_handle) + << ", ccc=" << loghex(rendererDevice->vcs.volume_state_ccc_handle); + } else if (charac.uuid == VCS_VOLUME_FLAGS_UUID) { + rendererDevice->vcs.volume_flags_handle = charac.value_handle; + + rendererDevice->vcs.volume_flags_ccc_handle = + FindCccHandle(conn_id, charac.value_handle); + if (!rendererDevice->vcs.volume_flags_ccc_handle) { + LOG(ERROR) << __func__ << ": cannot find volume flags CCC descriptor"; + continue; + } + + LOG(INFO) << __func__ + << ": vcs volume_flags_handle=" << loghex(charac.value_handle) + << ", ccc=" << loghex(rendererDevice->vcs.volume_flags_ccc_handle); + } else if (charac.uuid == VCS_VOLUME_CONTROL_POINT_UUID) { + // store volume control point! + rendererDevice->vcs.volume_control_point_handle = charac.value_handle; + } else { + LOG(WARNING) << "Unknown characteristic found:" << charac.uuid; + } + } + + LOG(WARNING) << "reading vcs volume_state_handle"; + GattOpsQueue::ReadCharacteristic(gatt_if, + conn_id, rendererDevice->vcs.volume_state_handle, + VcpControllerImpl::OnVolumeStateReadStatic, nullptr); + + } + + void OnServiceChangeEvent(const RawAddress& address) { + RendererDevice* rendererDevice = rendererDevices.FindByAddress(address); + if (!rendererDevice) { + VLOG(2) << "Skipping unknown device" << address; + return; + } + LOG(INFO) << __func__ << ": address=" << address; + rendererDevice->service_changed_rcvd = true; + GattOpsQueue::Clean(rendererDevice->conn_id); + } + + void OnServiceDiscDoneEvent(const RawAddress& address) { + RendererDevice* rendererDevice = rendererDevices.FindByAddress(address); + if (!rendererDevice) { + VLOG(2) << "Skipping unknown device" << address; + return; + } + if (rendererDevice->service_changed_rcvd) { + BTA_GATTC_ServiceSearchRequest(rendererDevice->conn_id, &VCS_UUID); + } + } + + void OnVolumeStateRead(uint16_t client_id, uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, uint8_t* value, void* data) { + RendererDevice* rendererDevice = rendererDevices.FindByConnId(conn_id); + + if (!rendererDevice) { + LOG(WARNING) << "Skipping unknown read event, conn_id=" << loghex(conn_id); + return; + } + + LOG(INFO) << __func__ << " " << rendererDevice->address << ", status: " << loghex(status) + << ", renderer device state: " << loghex(rendererDevice->state); + + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Error reading Volume State for device" << rendererDevice->address; + } else { + uint8_t* p = value; + uint8_t volume_setting; + STREAM_TO_UINT8(volume_setting, p); + rendererDevice->vcs.volume_state.volume_setting = volume_setting; + + uint8_t mute; + STREAM_TO_UINT8(mute, p); + rendererDevice->vcs.volume_state.mute = mute; + + uint8_t change_counter; + STREAM_TO_UINT8(change_counter, p); + rendererDevice->vcs.volume_state.change_counter = change_counter; + } + + HandleVCSEvent(rendererDevice, VCS_VOLUME_STATE_READ_CMPL_EVT, status); + } + + void OnVolumeFlagsRead(uint16_t client_id, uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, uint8_t* value, void* data) { + RendererDevice* rendererDevice = rendererDevices.FindByConnId(conn_id); + + if (!rendererDevice) { + LOG(WARNING) << "Skipping unknown read event, conn_id=" << loghex(conn_id); + return; + } + + LOG(INFO) << __func__ << " " << rendererDevice->address << ", status: " << loghex(status); + + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Error reading Volume Flags for device" << rendererDevice->address; + } else { + uint8_t* p = value; + uint8_t volume_flags; + STREAM_TO_UINT8(volume_flags, p); + rendererDevice->vcs.volume_flags = volume_flags; + } + + HandleVCSEvent(rendererDevice, VCS_VOLUME_FLAGS_READ_CMPL_EVT, status); + } + + void OnVolumeStateCCCWrite(uint16_t client_id, uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data) { + RendererDevice* rendererDevice = rendererDevices.FindByConnId(conn_id); + + if (!rendererDevice) { + LOG(WARNING) << "Skipping unknown read event, conn_id=" << loghex(conn_id); + return; + } + + LOG(INFO) << __func__ << " " << rendererDevice->address << ", status: " << loghex(status); + + HandleVCSEvent(rendererDevice, VCS_VOLUME_STATE_CCC_WRITE_CMPL_EVT, status); + } + + void OnVolumeFlagsCCCWrite(uint16_t client_id, uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data) { + RendererDevice* rendererDevice = rendererDevices.FindByConnId(conn_id); + + if (!rendererDevice) { + LOG(WARNING) << "Skipping unknown read event, conn_id=" << loghex(conn_id); + return; + } + + LOG(INFO) << __func__ << " " << rendererDevice->address << ", status: " << loghex(status); + + HandleVCSEvent(rendererDevice, VCS_VOLUME_FLAGS_CCC_WRITE_CMPL_EVT, status); + } + + void OnSetAbsVolume(uint16_t client_id, uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data) { + RendererDevice* rendererDevice = rendererDevices.FindByConnId(conn_id); + + if (!rendererDevice) { + LOG(WARNING) << "Skipping unknown read event, conn_id=" << loghex(conn_id); + return; + } + + LOG(INFO) << __func__ << " " << rendererDevice->address << ", status: " << loghex(status); + + if (status != GATT_SUCCESS) { + // Check for VCS Invalid Change Counter error, it may + // conflict with GATT_NO_RESOURCES error. + if (status == VCS_INVALID_CHANGE_COUNTER || + status == VCS_OPCODE_NOT_SUPPORTED) { + LOG(ERROR) << __func__ << ": Error code: " << status + << " device: " << rendererDevice->address + << " Read Volume State to update change counter"; + + rendererDevice->vcs.retry_cmd |= VCS_RETRY_SET_ABS_VOL; + GattOpsQueue::ReadCharacteristic(gatt_if, + conn_id, rendererDevice->vcs.volume_state_handle, + VcpControllerImpl::OnVolumeStateReadStatic, nullptr); + } else { + LOG(ERROR) << __func__ << ": Other errors, not retry"; + } + } else { + rendererDevice->vcs.retry_cmd &= ~VCS_RETRY_SET_ABS_VOL; + LOG(INFO) << "Set abs volume success " << rendererDevice->address; + } + } + + void OnMute(uint16_t client_id, uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data) { + RendererDevice* rendererDevice = rendererDevices.FindByConnId(conn_id); + + if (!rendererDevice) { + LOG(WARNING) << "Skipping unknown read event, conn_id=" << loghex(conn_id); + return; + } + + LOG(INFO) << __func__ << " " << rendererDevice->address << ", status: " << loghex(status); + + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Volume State Write failed" << rendererDevice->address + << "Read Volume State"; + + rendererDevice->vcs.retry_cmd |= VCS_RETRY_SET_MUTE_STATE; + GattOpsQueue::ReadCharacteristic(gatt_if, + conn_id, rendererDevice->vcs.volume_state_handle, + VcpControllerImpl::OnVolumeStateReadStatic, nullptr); + } else { + LOG(INFO) << "Mute success" << rendererDevice->address; + } + + } + + void OnUnmute(uint16_t client_id, uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data) { + RendererDevice* rendererDevice = rendererDevices.FindByConnId(conn_id); + + if (!rendererDevice) { + LOG(WARNING) << "Skipping unknown read event, conn_id=" << loghex(conn_id); + return; + } + + LOG(INFO) << __func__ << " " << rendererDevice->address << ", status: " << loghex(status); + + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Volume State Write failed" << rendererDevice->address + << "Read Volume State"; + + rendererDevice->vcs.retry_cmd |= VCS_RETRY_SET_MUTE_STATE; + GattOpsQueue::ReadCharacteristic(gatt_if, + conn_id, rendererDevice->vcs.volume_state_handle, + VcpControllerImpl::OnVolumeStateReadStatic, nullptr); + } else { + LOG(INFO) << "Unmute success" << rendererDevice->address; + } + } + + void RetryVolumeControlOp(RendererDevice* rendererDevice) { + LOG(INFO) << __func__ << " " << rendererDevice->address; + + if (rendererDevice->vcs.retry_cmd & VCS_RETRY_SET_ABS_VOL) { + rendererDevice->vcs.retry_cmd &= ~VCS_RETRY_SET_ABS_VOL; + SetAbsVolume(rendererDevice->address, rendererDevice->vcs.pending_volume_setting); + } + + if (rendererDevice->vcs.retry_cmd & VCS_RETRY_SET_MUTE_STATE) { + rendererDevice->vcs.retry_cmd &= ~VCS_RETRY_SET_MUTE_STATE; + if (rendererDevice->vcs.pending_mute_setting == VCS_MUTE_STATE) { + Mute(rendererDevice->address); + } else { + Unmute(rendererDevice->address); + } + } + } + + static void OnVolumeStateReadStatic(uint16_t client_id, uint16_t conn_id, + tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + if (instance) + instance->OnVolumeStateRead(client_id, conn_id, status, handle, len, value, + data); + } + + static void OnVolumeFlagsReadStatic(uint16_t client_id, uint16_t conn_id, + tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + if (instance) + instance->OnVolumeFlagsRead(client_id, conn_id, status, handle, len, value, + data); + } + + static void OnVolumeStateCCCWriteStatic(uint16_t client_id, uint16_t conn_id, + tGATT_STATUS status, uint16_t handle, void* data) { + if (instance) + instance->OnVolumeStateCCCWrite(client_id, conn_id, status, handle, data); + } + + static void OnVolumeFlagsCCCWriteStatic(uint16_t client_id, uint16_t conn_id, + tGATT_STATUS status, uint16_t handle, void* data) { + if (instance) + instance->OnVolumeFlagsCCCWrite(client_id, conn_id, status, handle, data); + } + + static void OnSetAbsVolumeStatic(uint16_t client_id, uint16_t conn_id, + tGATT_STATUS status, uint16_t handle, void* data) { + if (instance) + instance->OnSetAbsVolume(client_id, conn_id, status, handle, data); + } + + static void OnMuteStatic(uint16_t client_id, uint16_t conn_id, + tGATT_STATUS status, uint16_t handle, void* data) { + if (instance) + instance->OnMute(client_id, conn_id, status, handle, data); + } + + static void OnUnmuteStatic(uint16_t client_id, uint16_t conn_id, + tGATT_STATUS status, uint16_t handle, void* data) { + if (instance) + instance->OnUnmute(client_id, conn_id, status, handle, data); + } + + + void OnNotificationEvent(uint16_t conn_id, uint16_t handle, uint16_t len, + uint8_t* value) { + RendererDevice* device = rendererDevices.FindByConnId(conn_id); + + if (!device) { + LOG(INFO) << __func__ + << ": Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + + if (handle == device->vcs.volume_state_handle) { + if ( len != sizeof(device->vcs.volume_state)) { + LOG(ERROR) << __func__ << ": Data Length mismatch, len=" << len + << ", expecting " << sizeof(device->vcs.volume_state); + return; + } + + LOG(INFO) << __func__ << " " << device->address << " volume state notification"; + memcpy(&device->vcs.volume_state, value, len); + callbacks->OnVolumeStateChange(device->vcs.volume_state.volume_setting, + device->vcs.volume_state.mute, device->address); + } else if (handle == device->vcs.volume_flags_handle) { + if ( len != sizeof(device->vcs.volume_flags)) { + LOG(ERROR) << __func__ << ": Data Length mismatch, len=" << len + << ", expecting " << sizeof(device->vcs.volume_flags); + return; + } + + LOG(INFO) << __func__ << " " << device->address << " volume flags notification"; + memcpy(&device->vcs.volume_flags, value, len); + callbacks->OnVolumeFlagsChange(device->vcs.volume_flags, device->address); + } else { + LOG(INFO) << __func__ << ": Mismatched handle, " + << loghex(device->vcs.volume_state_handle) + << " or " << loghex(device->vcs.volume_flags_handle) + << "!=" << loghex(handle); + return; + } + } + + void OnCongestionEvent(uint16_t conn_id, bool congested) { + RendererDevice* device = rendererDevices.FindByConnId(conn_id); + if (!device) { + LOG(INFO) << __func__ + << ": Skipping unknown device, conn_id=" << loghex(conn_id); + return; + } + + LOG(WARNING) << __func__ << ": conn_id:" << loghex(conn_id) + << ", congested: " << congested; + GattOpsQueue::CongestionCallback(conn_id, congested); + } + + void HandleVCSEvent(RendererDevice* rendererDevice, uint32_t event, tGATT_STATUS status) { + LOG(INFO) << __func__ << " event = " << vcp_controller_handle_vcs_evt_str(event); + + if (status != GATT_SUCCESS) { + if (rendererDevice->state == BTA_VCP_CONNECTING) { + LOG(ERROR) << __func__ << ": Error status while VCP connecting, Close GATT for device: " + << rendererDevice->address; + VcpGattClose(rendererDevice); + return; + } else if (rendererDevice->state == BTA_VCP_CONNECTED) { + LOG(ERROR) << __func__ << ": Error status while VCP is connected for device: " + << rendererDevice->address; + if (rendererDevice->vcs.retry_cmd != 0) { + rendererDevice->vcs.retry_cmd = 0; + } + return; + } else { + LOG(ERROR) << __func__ << ": Error status in disconnected or disconnecting " + << "Igore handle VCS Event for device: " << rendererDevice->address; + return; + } + } + + switch (event) { + case VCS_VOLUME_STATE_READ_CMPL_EVT: { + if (rendererDevice->state == BTA_VCP_CONNECTING) { + LOG(WARNING) << "Setup VCP connection, reading vcs volume_flags_handle"; + GattOpsQueue::ReadCharacteristic(gatt_if, + rendererDevice->conn_id, rendererDevice->vcs.volume_flags_handle, + VcpControllerImpl::OnVolumeFlagsReadStatic, nullptr); + break; + } else if (rendererDevice->state == BTA_VCP_CONNECTED) { + if (rendererDevice->vcs.retry_cmd != 0) { + RetryVolumeControlOp(rendererDevice); + } + } + break; + } + + case VCS_VOLUME_FLAGS_READ_CMPL_EVT: { + if (rendererDevice->state == BTA_VCP_CONNECTING) { + /* Register and enable the Volume State Notification */ + tGATT_STATUS register_status; + register_status = BTA_GATTC_RegisterForNotifications( + gatt_if, rendererDevice->address, rendererDevice->vcs.volume_state_handle); + if (register_status != GATT_SUCCESS) { + LOG(ERROR) << __func__ + << ": BTA_GATTC_RegisterForNotifications failed, status=" + << loghex(register_status); + VcpGattClose(rendererDevice); + return; + } + + std::vector value(2); + uint8_t* ptr = value.data(); + UINT16_TO_STREAM(ptr, GATT_CHAR_CLIENT_CONFIG_NOTIFICATION); + GattOpsQueue::WriteDescriptor(gatt_if, + rendererDevice->conn_id, rendererDevice->vcs.volume_state_ccc_handle, + std::move(value), GATT_WRITE, VcpControllerImpl::OnVolumeStateCCCWriteStatic, + nullptr); + } + break; + } + + case VCS_VOLUME_STATE_CCC_WRITE_CMPL_EVT: { + if (rendererDevice->state == BTA_VCP_CONNECTING) { + /* Register and enable the Volume State Notification */ + tGATT_STATUS register_status; + register_status = BTA_GATTC_RegisterForNotifications( + gatt_if, rendererDevice->address, rendererDevice->vcs.volume_flags_handle); + if (register_status != GATT_SUCCESS) { + LOG(ERROR) << __func__ + << ": BTA_GATTC_RegisterForNotifications failed, status=" + << loghex(register_status); + VcpGattClose(rendererDevice); + return; + } + + std::vector value(2); + uint8_t* ptr = value.data(); + UINT16_TO_STREAM(ptr, GATT_CHAR_CLIENT_CONFIG_NOTIFICATION); + GattOpsQueue::WriteDescriptor(gatt_if, + rendererDevice->conn_id, rendererDevice->vcs.volume_flags_ccc_handle, + std::move(value), GATT_WRITE, VcpControllerImpl::OnVolumeFlagsCCCWriteStatic, + nullptr); + } + break; + } + + case VCS_VOLUME_FLAGS_CCC_WRITE_CMPL_EVT: { + if (rendererDevice->state == BTA_VCP_CONNECTING) { + LOG(INFO) << __func__ << ": VCP Connection Setup complete"; + rendererDevice->state = BTA_VCP_CONNECTED; + callbacks->OnConnectionState(ConnectionState::CONNECTED, rendererDevice->address); + callbacks->OnVolumeFlagsChange(rendererDevice->vcs.volume_flags, + rendererDevice->address); + callbacks->OnVolumeStateChange(rendererDevice->vcs.volume_state.volume_setting, + rendererDevice->vcs.volume_state.mute, rendererDevice->address); + break; + } + break; + } + + default: + LOG(INFO) << __func__ << ": unexpected VCS event"; + break; + } + } + + // Find the handle for the client characteristics configuration of a given + // characteristics + uint16_t FindCccHandle(uint16_t conn_id, uint16_t char_handle) { + const gatt::Characteristic* p_char = + BTA_GATTC_GetCharacteristic(conn_id, char_handle); + LOG(INFO) << __func__ << " " << ", conn_id: " << conn_id << ", char_handle: " << char_handle; + + if (!p_char) { + LOG(WARNING) << __func__ << ": No such characteristic: " << char_handle; + return 0; + } + + for (const gatt::Descriptor& desc : p_char->descriptors) { + if (desc.uuid == Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)) + return desc.handle; + } + return 0; + } + + void CleanUp() { + LOG(INFO) << __func__; + BTA_GATTC_AppDeregister(gatt_if); + for (RendererDevice& device : rendererDevices.devices) { + PostDisconnected(&device); + } + + rendererDevices.devices.clear(); + } +}; + +void vcp_controller_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { + LOG(INFO) << __func__ << " event = " << vcp_controller_gatt_callback_evt_str(event); + + if (p_data == nullptr) return; + + switch (event) { + case BTA_GATTC_DEREG_EVT: + break; + + case BTA_GATTC_OPEN_EVT: { + if (!instance) return; + tBTA_GATTC_OPEN& o = p_data->open; + instance->OnGattConnected(o.status, o.conn_id, o.client_if, o.remote_bda, + o.transport, o.mtu); + break; + } + + case BTA_GATTC_CLOSE_EVT: { + if (!instance) return; + tBTA_GATTC_CLOSE& c = p_data->close; + instance->OnGattDisconnected(c.status, c.conn_id, c.client_if, + c.remote_bda, c.reason); + } break; + + case BTA_GATTC_SEARCH_CMPL_EVT: + if (!instance) return; + instance->OnServiceSearchComplete(p_data->search_cmpl.conn_id, + p_data->search_cmpl.status); + break; + + case BTA_GATTC_NOTIF_EVT: + if (!instance) return; + if (!p_data->notify.is_notify || p_data->notify.len > GATT_MAX_ATTR_LEN) { + LOG(ERROR) << __func__ << ": rejected BTA_GATTC_NOTIF_EVT. is_notify=" + << p_data->notify.is_notify + << ", len=" << p_data->notify.len; + break; + } + instance->OnNotificationEvent(p_data->notify.conn_id, + p_data->notify.handle, p_data->notify.len, + p_data->notify.value); + break; + + case BTA_GATTC_ENC_CMPL_CB_EVT: + if (!instance) return; + instance->OnEncryptionComplete(p_data->enc_cmpl.remote_bda, true); + break; + + case BTA_GATTC_SRVC_CHG_EVT: + if (!instance) return; + instance->OnServiceChangeEvent(p_data->remote_bda); + break; + + case BTA_GATTC_SRVC_DISC_DONE_EVT: + if (!instance) return; + instance->OnServiceDiscDoneEvent(p_data->remote_bda); + break; + + case BTA_GATTC_CONGEST_EVT: + if (!instance) return; + instance->OnCongestionEvent(p_data->congest.conn_id, + p_data->congest.congested); + break; + + case BTA_GATTC_SEARCH_RES_EVT: + case BTA_GATTC_CANCEL_OPEN_EVT: + case BTA_GATTC_CONN_UPDATE_EVT: + + default: + break; + } +} + +void vcp_controller_encryption_callback(const RawAddress* address, + UNUSED_ATTR tGATT_TRANSPORT transport, + UNUSED_ATTR void* data, tBTM_STATUS status) { + if (instance) { + instance->OnEncryptionComplete(*address, + status == BTM_SUCCESS ? true : false); + } +} + +void VcpController::Initialize( + bluetooth::vcp_controller::VcpControllerCallbacks* callbacks) { + LOG(INFO) << __func__ ; + + if (instance) { + LOG(ERROR) << "Already initialized!"; + } + + instance = new VcpControllerImpl(callbacks); +} + +bool VcpController::IsVcpControllerRunning() { return instance; } + +VcpController* VcpController::Get() { + CHECK(instance); + return instance; +}; + +int VcpController::GetDeviceCount() { + if (!instance) { + LOG(INFO) << __func__ << ": Not initialized yet"; + return 0; + } + + return (instance->GetDeviceCount()); +} + +void VcpController::CleanUp() { + VcpControllerImpl* ptr = instance; + instance = nullptr; + + ptr->CleanUp(); + + delete ptr; +}; + +/******************************************************************************* + * Debugging functions + ******************************************************************************/ +#define CASE_RETURN_STR(const) \ + case const: \ + return #const; + +const char* vcp_controller_gatt_callback_evt_str(uint8_t event) { + switch (event) { + CASE_RETURN_STR(BTA_GATTC_DEREG_EVT) + CASE_RETURN_STR(BTA_GATTC_OPEN_EVT) + CASE_RETURN_STR(BTA_GATTC_CLOSE_EVT) + CASE_RETURN_STR(BTA_GATTC_SEARCH_CMPL_EVT) + CASE_RETURN_STR(BTA_GATTC_NOTIF_EVT) + CASE_RETURN_STR(BTA_GATTC_ENC_CMPL_CB_EVT) + CASE_RETURN_STR(BTA_GATTC_SEARCH_RES_EVT) + CASE_RETURN_STR(BTA_GATTC_CANCEL_OPEN_EVT) + CASE_RETURN_STR(BTA_GATTC_SRVC_CHG_EVT) + CASE_RETURN_STR(BTA_GATTC_CONN_UPDATE_EVT) + CASE_RETURN_STR(BTA_GATTC_SRVC_DISC_DONE_EVT) + CASE_RETURN_STR(BTA_GATTC_CONGEST_EVT) + default: + return (char*)"Unknown GATT Callback Event"; + } +} + +const char* vcp_controller_handle_vcs_evt_str(uint8_t event) { + switch (event) { + CASE_RETURN_STR(VCS_VOLUME_STATE_READ_CMPL_EVT) + CASE_RETURN_STR(VCS_VOLUME_FLAGS_READ_CMPL_EVT) + CASE_RETURN_STR(VCS_VOLUME_STATE_CCC_WRITE_CMPL_EVT) + CASE_RETURN_STR(VCS_VOLUME_FLAGS_CCC_WRITE_CMPL_EVT) + default: + return (char*)"Unknown handling VCS Event"; + } +} + diff --git a/le_audio/system/bt/btif/Android.bp b/le_audio/system/bt/btif/Android.bp new file mode 100644 index 00000000000..7b4b2ace9a3 --- /dev/null +++ b/le_audio/system/bt/btif/Android.bp @@ -0,0 +1,80 @@ +cc_defaults { + name: "fluoride_btif_defaults_qti_adva", + defaults: ["fluoride_defaults_qti"], + include_dirs: [ + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/bta/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/bta/ag", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/btcore/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/hci/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/internal_include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/stack/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/stack/btm", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/udrv/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/vnd/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/utils/include", + "vendor/qcom/opensource/commonsys/bluetooth_ext/system_bt_ext", + "vendor/qcom/opensource/commonsys/bluetooth_ext/vhal/include", + "vendor/qcom/opensource/commonsys/bluetooth_ext/system_bt_ext/bta/include", + "vendor/qcom/opensource/commonsys/bluetooth_ext/system_bt_ext/btif/include", + "vendor/qcom/opensource/commonsys-intf/bluetooth/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/device/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/btif/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/bta/include", + "vendor/qcom/opensource/commonsys/packages/modules/Bluetooth/system/bta/sys", + "packages/modules/Bluetooth/system/common", + "vendor/qcom/opensource/commonsys/bluetooth_lea/packages/modules/Bluetooth/system/btif/include", + "vendor/qcom/opensource/commonsys/bluetooth_lea/packages/modules/Bluetooth/system/bta/include", + "vendor/qcom/opensource/commonsys/bluetooth_lea/packages/modules/Bluetooth/system", + "vendor/qcom/opensource/commonsys/bluetooth_lea/vhal/include", + "external/libxml2/include", + ], + shared_libs: [ + "libcutils", + "vendor.qti.hardware.bluetooth_audio@2.0", + "vendor.qti.hardware.bluetooth_audio@2.1", + "libcrypto", + "libxml2", + ], + header_libs: ["libbluetooth_headers"], + cflags: [ + "-DBUILDCFG", + "-DADV_AUDIO_FEATURE=1", + ], + required: ["leaudio_configs.xml"], +} + +// BTA static library for target +// ======================================================== +cc_library_static { + name: "libbt-btif_qti_adva", + defaults: ["fluoride_btif_defaults_qti_adva"], + enabled: false, + srcs: [ + "src/bluetooth_adv_audio.cc", + "src/btif_bap_broadcast.cc", + "src/btif_csip.cc", + "src/btif_acm.cc", + "src/btif_pacs_client.cc", + "src/btif_ascs_client.cc", + "src/btif_bap_config.cc", + "src/btif_bap_uclient.cc", + "src/btif_bap_codec_utils.cc", + "src/btif_vmcp.cc", + "src/btif_apm.cc", + "src/btif_vcp_controller.cc", + "src/btif_dm_adv_audio.cc", + "src/btif_acm_source.cc", + "src/btif_mcp.cc", + "src/btif_cc.cc" + ], +} + +// Bluetooth le audio configs xml +// ======================================================== +prebuilt_etc { + name: "leaudio_configs.xml", + src: "leaudio_configs.xml", + sub_dir: "bluetooth", + system_ext_specific: true, +} diff --git a/le_audio/system/bt/btif/include/bluetooth_adv_audio.h b/le_audio/system/bt/btif/include/bluetooth_adv_audio.h new file mode 100644 index 00000000000..22395e88c39 --- /dev/null +++ b/le_audio/system/bt/btif/include/bluetooth_adv_audio.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ +/******************************************************************************* + * + * Filename: bluetooth_adv_audio.h + * + * Description: Main API header file for LEA interfacing + * + ******************************************************************************/ + +#pragma once + +/******************************************************************************* + * TInterface APIs + ******************************************************************************/ + +const void* get_adv_audio_profile_interface(const char* profile_id); +void init_adv_audio_interfaces(); diff --git a/le_audio/system/bt/btif/include/btif_acm.h b/le_audio/system/bt/btif/include/btif_acm.h new file mode 100644 index 00000000000..e0722e78baf --- /dev/null +++ b/le_audio/system/bt/btif/include/btif_acm.h @@ -0,0 +1,236 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +#ifndef BTIF_ACM_H +#define BTIF_ACM_H + +#include + +//#include "bta_acm_api.h" +#include "btif_common.h" +#include "bta_bap_uclient_api.h" +#include "bta_pacs_client_api.h" +#include "bta_ascs_client_api.h" + +typedef uint8_t tBTA_ACM_HNDL; +typedef uint8_t tBTIF_ACM_STATUS; + +#define BTA_ACM_MAX_EVT 26 +#define BTA_ACM_NUM_STRS 6 +#define BTA_ACM_NUM_CIGS 239 +//starting setid from 16 onwards as 16 is inavlid +#define BTA_ACM_MIN_NUM_SETID 17 +#define BTA_ACM_MAX_NUM_SETID 255 +#define CONTEXT_TYPE_UNKNOWN 0 +#define CONTEXT_TYPE_MUSIC 1 +#define CONTEXT_TYPE_VOICE 2 +#define CONTEXT_TYPE_MUSIC_VOICE 3 + +#define BTA_ACM_DISCONNECT_EVT 0 +#define BTA_ACM_CONNECT_EVT 1 +#define BTA_ACM_START_EVT 2 +#define BTA_ACM_STOP_EVT 3 +#define BTA_ACM_RECONFIG_EVT 4 +#define BTA_ACM_CONFIG_EVT 5 +#define BTA_ACM_CONN_UPDATE_TIMEOUT_EVT 6 + +#define BTA_ACM_INITIATOR_SERVICE_ID 0xFF +#define ACM_UUID 0xFFFF +#define ACM_TSEP_SNK 1 + +#define SRC 0 +#define SNK 1 + +constexpr uint8_t STREAM_STATE_DISCONNECTED = 0x00; +constexpr uint8_t STREAM_STATE_CONNECTING = 0x01; +constexpr uint8_t STREAM_STATE_CONNECTED = 0x02; +constexpr uint8_t STREAM_STATE_STARTING = 0x03; +constexpr uint8_t STREAM_STATE_STREAMING = 0x04; +constexpr uint8_t STREAM_STATE_STOPPING = 0x05; +constexpr uint8_t STREAM_STATE_DISCONNECTING = 0x06; +constexpr uint8_t STREAM_STATE_RECONFIGURING = 0x07; + +using bluetooth::bap::ucast::StreamStateInfo; +using bluetooth::bap::ucast::StreamConfigInfo; + +using bluetooth::bap::ucast::StreamConnect; +using bluetooth::bap::ucast::StreamType; +using bluetooth::bap::ucast::StreamReconfig; +using bluetooth::bap::ucast::StreamDiscReason; +using bluetooth::bap::ucast::StreamState; +using bluetooth::bap::ucast::QosConfig; + +using bluetooth::bap::pacs::CodecConfig; + +typedef struct { + RawAddress bd_addr; + int contextType; + int profileType; +}tBTIF_ACM_CONN_DISC; + +typedef struct { + RawAddress bd_addr; +}tBTA_ACM_CONN_UPDATE_TIMEOUT_INFO; + +typedef struct { + RawAddress bd_addr; + StreamType stream_type; + CodecConfig codec_config; + uint32_t audio_location; + QosConfig qos_config; + std::vector codecs_selectable; +}tBTA_ACM_CONFIG_INFO; + +typedef struct { + RawAddress bd_addr; + StreamType stream_type; + StreamState stream_state; + StreamDiscReason reason; +}tBTA_ACM_STATE_INFO; + +typedef struct { + RawAddress bd_addr; + bool is_direct; + StreamStateInfo streams_info; +}tBTIF_ACM_CONNECT; + +typedef struct { + RawAddress bd_addr; + StreamStateInfo streams_info; +}tBTIF_ACM_DISCONNECT; + +typedef struct { + RawAddress bd_addr; + StreamStateInfo streams_info; +}tBTIF_ACM_START; + +typedef struct { + RawAddress bd_addr; + StreamStateInfo streams_info; +}tBTIF_ACM_STOP; + +typedef struct { + RawAddress bd_addr; + StreamReconfig streams_info; +}tBTIF_ACM_RECONFIG; + +typedef union { + tBTIF_ACM_CONN_DISC acm_conn_disc; + tBTA_ACM_STATE_INFO state_info; + tBTA_ACM_CONFIG_INFO config_info; + tBTIF_ACM_CONNECT acm_connect; + tBTIF_ACM_DISCONNECT acm_disconnect; + tBTIF_ACM_START acm_start; + tBTIF_ACM_STOP acm_stop; + tBTIF_ACM_RECONFIG acm_reconfig; +}tBTIF_ACM; + +typedef enum { + /* Reuse BTA_ACM_XXX_EVT - No need to redefine them here */ + BTIF_ACM_CONNECT_REQ_EVT = BTA_ACM_MAX_EVT, + BTIF_ACM_DISCONNECT_REQ_EVT, + BTIF_ACM_START_STREAM_REQ_EVT, + BTIF_ACM_STOP_STREAM_REQ_EVT, + BTIF_ACM_SUSPEND_STREAM_REQ_EVT, + BTIF_ACM_RECONFIG_REQ_EVT, +} btif_acm_sm_event_t; + +typedef enum { + BTA_CSIP_NEW_SET_FOUND_EVT = 1, + BTA_CSIP_SET_MEMBER_FOUND_EVT, + BTA_CSIP_CONN_STATE_CHG_EVT, + BTA_CSIP_LOCK_STATUS_CHANGED_EVT, + BTA_CSIP_LOCK_AVAILABLE_EVT, + BTA_CSIP_SET_SIZE_CHANGED, + BTA_CSIP_SET_SIRK_CHANGED, +} btif_csip_sm_event_t; + +/** + * When the local device is ACM source, get the address of the active peer. + */ +RawAddress btif_acm_source_active_peer(void); + +/** + * When the local device is ACM sink, get the address of the active peer. + */ +RawAddress btif_acm_sink_active_peer(void); + +/** + * Start streaming. + */ +void btif_acm_stream_start(void); + +/** + * Stop streaming. + * + * @param peer_address the peer address or RawAddress::kEmpty to stop all peers + */ +void btif_acm_stream_stop(void); + +/** + * Suspend streaming. + */ +void btif_acm_stream_suspend(void); + +/** + * Start offload streaming. + */ +void btif_acm_stream_start_offload(void); + +bool btif_acm_check_if_requested_devices_stopped(void); + +/** + * Get the Stream Endpoint Type of the Active peer. + * + * @return the stream endpoint type: either AVDT_TSEP_SRC or AVDT_TSEP_SNK + */ +uint8_t btif_acm_get_peer_sep(void); + +/** + + * Report ACM Source Codec State for a peer. + * + * @param peer_address the address of the peer to report + * @param codec_config the codec config to report + * @param codecs_local_capabilities the codecs local capabilities to report + * @param codecs_selectable_capabilities the codecs selectable capabilities + * to report + */ +void btif_acm_report_source_codec_state( + const RawAddress& peer_address, + const CodecConfig& codec_config, + const std::vector& codecs_local_capabilities, + const std::vector& + codecs_selectable_capabilities, int contextType); + +/** + * Initialize / shut down the ACM Initiator service. + * + * @param enable true to enable the ACM Source service, false to disable it + * @return BT_STATUS_SUCCESS on success, BT_STATUS_FAIL otherwise + */ +bt_status_t btif_acm_initiator_execute_service(bool enable); + +/** + * Dump debug-related information for the BTIF ACM module. + * + * @param fd the file descriptor to use for writing the ASCII formatted + * information + */ +void btif_debug_acm_dump(int fd); + +bool btif_acm_is_active(); +uint16_t btif_acm_get_sample_rate(); +uint8_t btif_acm_get_ch_mode(); +uint32_t btif_acm_get_bitrate(); +uint32_t btif_acm_get_octets(uint32_t bit_rate); +uint16_t btif_acm_get_framelength(); +uint8_t btif_acm_get_ch_count(); +uint16_t btif_acm_get_current_active_profile(); +bool btif_acm_is_codec_type_lc3q(); +uint8_t btif_acm_lc3q_ver(); +bool btif_acm_is_call_active(void); + +#endif /* BTIF_ACM_H */ diff --git a/le_audio/system/bt/btif/include/btif_acm_source.h b/le_audio/system/bt/btif/include/btif_acm_source.h new file mode 100644 index 00000000000..47d4530b09d --- /dev/null +++ b/le_audio/system/bt/btif/include/btif_acm_source.h @@ -0,0 +1,21 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + ******************************************************************************/ + +#if AHIM_ENABLED +void btif_register_cb(); +void btif_acm_process_request(tA2DP_CTRL_CMD cmd); +void btif_acm_handle_event(uint16_t event, char* p_param); +bool btif_acm_source_start_session(const RawAddress& peer_address); +bool btif_acm_source_end_session(const RawAddress& peer_address); +bool btif_acm_source_restart_session(const RawAddress& old_peer_address, + const RawAddress& new_peer_address); +void btif_acm_source_command_ack(tA2DP_CTRL_CMD cmd, tA2DP_CTRL_ACK status); +void btif_acm_source_on_stopped(tA2DP_CTRL_ACK status); +void btif_acm_source_on_suspended(tA2DP_CTRL_ACK status); +bool btif_acm_on_started(tA2DP_CTRL_ACK status); +bt_status_t btif_acm_source_setup_codec(); +bool btif_acm_update_sink_latency_change(uint16_t sink_latency); + +#endif diff --git a/le_audio/system/bt/btif/include/btif_bap_broadcast.h b/le_audio/system/bt/btif/include/btif_bap_broadcast.h new file mode 100644 index 00000000000..c2192861a57 --- /dev/null +++ b/le_audio/system/bt/btif/include/btif_bap_broadcast.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ +/******************************************************************************* + * + * Filename: btif_bap_broadcast.h + * + * Description: Main API header file for all BTIF BAP Broadcast functions + * accessed from internal stack. + * + ******************************************************************************/ + +#ifndef BTIF_BAP_BROADCAST_H +#define BTIF_BAP_BROADCAST_H + +#include "bta_av_api.h" +#include "btif_common.h" +#include "btif_sm.h" + + +/******************************************************************************* + * Type definitions for callback functions + ******************************************************************************/ + +typedef enum { + /* Reuse BTA_AV_XXX_EVT - No need to redefine them here */ + BTIF_BAP_BROADCAST_ENABLE_EVT, + BTIF_BAP_BROADCAST_DISABLE_EVT, + BTIF_BAP_BROADCAST_START_STREAM_REQ_EVT, + BTIF_BAP_BROADCAST_STOP_STREAM_REQ_EVT, + BTIF_BAP_BROADCAST_SUSPEND_STREAM_REQ_EVT, + BTIF_BAP_BROADCAST_SOURCE_CONFIG_REQ_EVT, + BTIF_BAP_BROADCAST_CLEANUP_REQ_EVT, + BTIF_BAP_BROADCAST_SET_ACTIVE_REQ_EVT, + BTIF_BAP_BROADCAST_REMOVE_ACTIVE_REQ_EVT, + BTIF_BAP_BROADCAST_SETUP_ISO_DATAPATH_EVT, + BTIF_BAP_BROADCAST_REMOVE_ISO_DATAPATH_EVT, + BTIF_BAP_BROADCAST_GENERATE_ENC_KEY_EVT, + BTIF_BAP_BROADCAST_BISES_SETUP_EVT, + BTIF_BAP_BROADCAST_BISES_REMOVE_EVT, + BTIF_BAP_BROADCAST_BIG_SETUP_EVT, + BTIF_BAP_BROADCAST_BIG_REMOVED_EVT, + BTIF_BAP_BROADCAST_SETUP_NEXT_BIS_EVENT, + BTIF_BAP_BROADCAST_PROCESS_HIDL_REQ_EVT, +} btif_bap_broadcast_sm_event_t; + +enum { + BTBAP_CODEC_CHANNEL_MODE_JOINT_STEREO = 0x01 << 2, + BTBAP_CODEC_CHANNEL_MODE_DUAL_MONO = 0x1 << 3 +}; +/******************************************************************************* + * BTIF AV API + ******************************************************************************/ +bool btif_bap_broadcast_is_active(); + +uint16_t btif_bap_broadcast_get_sample_rate(); +uint8_t btif_bap_broadcast_get_ch_mode(); +uint16_t btif_bap_broadcast_get_framelength(); +uint32_t btif_bap_broadcast_get_mtu(uint32_t bitrate); +uint32_t btif_bap_broadcast_get_bitrate(); +uint8_t btif_bap_broadcast_get_ch_count(); +bool btif_bap_broadcast_is_simulcast_enabled(); +/******************************************************************************* + * + * Function btif_dispatch_sm_event + * + * Description Send event to AV statemachine + * + * Returns None + * + ******************************************************************************/ + +/* used to pass events to AV statemachine from other tasks */ +void btif_bap_ba_dispatch_sm_event(btif_bap_broadcast_sm_event_t event, void *p_data, int len); + + +#endif /* BTIF_BAP_BROADCAST_H */ + diff --git a/le_audio/system/bt/btif/include/btif_bap_codec_utils.h b/le_audio/system/bt/btif/include/btif_bap_codec_utils.h new file mode 100644 index 00000000000..7b30cd023d8 --- /dev/null +++ b/le_audio/system/bt/btif/include/btif_bap_codec_utils.h @@ -0,0 +1,76 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + *****************************************************************************/ + +#pragma once + +#include +#include + +#include +#include "bt_types.h" + +using bluetooth::bap::pacs::CodecConfig; + +bool UpdateCapaSupFrameDurations(CodecConfig *config , uint8_t sup_frame); + +bool UpdateCapaMaxSupLc3Frames(CodecConfig *config, + uint8_t max_sup_lc3_frames); + + +bool UpdateCapaPreferredContexts(CodecConfig *config, uint16_t contexts); + + +bool UpdateCapaSupOctsPerFrame(CodecConfig *config, + uint32_t octs_per_frame); + +bool UpdateCapaVendorMetaDataLc3QPref(CodecConfig *config, bool lc3q_pref); + +bool UpdateCapaVendorMetaDataLc3QVer(CodecConfig *config, uint8_t lc3q_ver); + +uint8_t GetCapaSupFrameDurations(CodecConfig *config); + +uint8_t GetCapaMaxSupLc3Frames(CodecConfig *config); + +uint16_t GetCapaPreferredContexts(CodecConfig *config); + +uint32_t GetCapaSupOctsPerFrame(CodecConfig *config); + +bool GetCapaVendorMetaDataLc3QPref(CodecConfig *config); + +uint8_t GetCapaVendorMetaDataLc3QVer(CodecConfig *config); + +// configurations +bool UpdateFrameDuration(CodecConfig *config , uint8_t frame_dur); + +bool UpdateLc3BlocksPerSdu(CodecConfig *config, + uint8_t lc3_blocks_per_sdu) ; + +bool UpdateOctsPerFrame(CodecConfig *config , uint16_t octs_per_frame); + +bool UpdateLc3QPreference(CodecConfig *config , bool lc3q_pref); + +bool UpdateVendorMetaDataLc3QPref(CodecConfig *config, bool lc3q_pref); + +bool UpdateVendorMetaDataLc3QVer(CodecConfig *config, uint8_t lc3q_ver); + +bool UpdatePreferredAudioContext(CodecConfig *config , + uint16_t pref_audio_context); + +uint8_t GetFrameDuration(CodecConfig *config); + +uint8_t GetLc3BlocksPerSdu(CodecConfig *config); + +uint16_t GetOctsPerFrame(CodecConfig *config); + +uint8_t GetLc3QPreference(CodecConfig *config); + +uint8_t GetVendorMetaDataLc3QPref(CodecConfig *config); + +uint8_t GetVendorMetaDataLc3QVer(CodecConfig *config); + +uint16_t GetPreferredAudioContext(CodecConfig *config); + +bool IsCodecConfigEqual(CodecConfig *src_config, CodecConfig *dst_config); + + diff --git a/le_audio/system/bt/btif/include/btif_bap_config.h b/le_audio/system/bt/btif/include/btif_bap_config.h new file mode 100644 index 00000000000..a83b8e9bb5c --- /dev/null +++ b/le_audio/system/bt/btif/include/btif_bap_config.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/****************************************************************************** + * + * Copyright (C) 2014 Google, Inc. + * + * 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. + * + ******************************************************************************/ + +#pragma once + +#include +#include + +#include +#include "bt_types.h" + +using bluetooth::bap::pacs::CodecConfig; +using bluetooth::bap::pacs::CodecDirection; +using bluetooth::bap::pacs::CodecIndex; + +typedef enum { + REC_TYPE_CAPABILITY = 0x01, + REC_TYPE_CONFIGURATION +} btif_bap_record_type_t; + +const char BTIF_BAP_CONFIG_MODULE[] = "btif_bap_config_module"; + +typedef struct btif_bap_config_section_iter_t btif_bap_config_section_iter_t; + +bool btif_bap_add_record(const RawAddress& bd_addr, + btif_bap_record_type_t rec_type, + uint16_t context_type, + CodecDirection direction, + CodecConfig *record); + +bool btif_bap_remove_record(const RawAddress& bd_addr, + btif_bap_record_type_t rec_type, + uint16_t context_type, + CodecDirection direction, + CodecConfig *record); + +bool btif_bap_remove_record_by_context(const RawAddress& bd_addr, + btif_bap_record_type_t rec_type, + uint16_t context_type, + CodecDirection direction); + +bool btif_bap_remove_all_records(const RawAddress& bd_addr); + +bool btif_bap_get_records(const RawAddress& bd_addr, + btif_bap_record_type_t rec_type, + uint16_t context_type, + CodecDirection direction, + std::vector *pac_records); + +bool btif_bap_add_audio_loc(const RawAddress& bd_addr, + CodecDirection direction, uint32_t audio_loc); + +bool btif_bap_rem_audio_loc(const RawAddress& bd_addr, + CodecDirection direction); + +bool btif_bap_add_supp_contexts(const RawAddress& bd_addr, + uint32_t supp_contexts); + +bool btif_bap_get_supp_contexts(const RawAddress& bd_addr, + uint32_t *supp_contexts); + +bool btif_bap_rem_supp_contexts(const RawAddress& bd_addr); + +bool btif_bap_config_clear(void); diff --git a/le_audio/system/bt/btif/include/btif_dm_adv_audio.h b/le_audio/system/bt/btif/include/btif_dm_adv_audio.h new file mode 100644 index 00000000000..ffc437897b8 --- /dev/null +++ b/le_audio/system/bt/btif/include/btif_dm_adv_audio.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ +#ifndef BTIF_DM_ADV_AUDIO_H +#define BTIF_DM_ADV_AUDIO_H + +extern std::unordered_map adv_audio_device_db; +extern tBTA_TRANSPORT btif_dm_get_adv_audio_transport(const RawAddress& bd_addr); +extern void btif_dm_lea_search_services_evt(uint16_t event, char* p_param); +extern void btif_register_uuid_srvc_disc(bluetooth::Uuid uuid); +extern bool check_adv_audio_cod(uint32_t cod); +extern bool is_remote_support_adv_audio(const RawAddress p_addr); +extern void bte_dm_adv_audio_search_services_evt(tBTA_DM_SEARCH_EVT event, + tBTA_DM_SEARCH* p_data); +extern void btif_dm_release_action_uuid(RawAddress bd_addr); + +#endif + diff --git a/le_audio/system/bt/btif/include/btif_vmcp.h b/le_audio/system/bt/btif/include/btif_vmcp.h new file mode 100644 index 00000000000..cdfbbe742e2 --- /dev/null +++ b/le_audio/system/bt/btif/include/btif_vmcp.h @@ -0,0 +1,133 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + ******************************************************************************/ + +#include +#include "raw_address.h" +#include "hardware/bt_pacs_client.h" + +using bluetooth::bap::pacs::CodecSampleRate; +using bluetooth::bap::pacs::CodecIndex; +using bluetooth::bap::pacs::CodecFrameDuration; +using bluetooth::bap::pacs::CodecConfig; + +#define BAP 0x01 +#define GCP 0x02 +#define WMCP 0x04 +#define VMCP 0x08 +#define BAP_CALL 0x10 +#define GCP_TX_RX 0x20 + +#define EB_CONFIG 1 +#define STEREO_HS_CONFIG_1 2 +#define STEREO_HS_CONFIG_2 3 + +#define VOICE_CONTEXT 1 +#define MEDIA_CONTEXT 2 +#define MEDIA_LL_CONTEXT 3 +#define MEDIA_HR_CONTEXT 4 + +#define SAMPLE_RATE_8000 8000 +#define SAMPLE_RATE_16000 16000 +#define SAMPLE_RATE_24000 24000 +#define SAMPLE_RATE_32000 32000 +#define SAMPLE_RATE_44100 44100 +#define SAMPLE_RATE_48000 48000 + + +#define FRM_DURATION_7_5_MS 7.5 +#define FRM_DURATION_10_MS 10 + +#define OCT_PER_CODEC_FRM_26 26 +#define OCT_PER_CODEC_FRM_30 30 +#define OCT_PER_CODEC_FRM_60 60 +#define OCT_PER_CODEC_FRM_75 75 +#define OCT_PER_CODEC_FRM_80 80 +#define OCT_PER_CODEC_FRM_90 90 +#define OCT_PER_CODEC_FRM_98 98 +#define OCT_PER_CODEC_FRM_100 100 +#define OCT_PER_CODEC_FRM_117 117 +#define OCT_PER_CODEC_FRM_120 120 +#define OCT_PER_CODEC_FRM_130 130 +#define OCT_PER_CODEC_FRM_150 150 +#define OCT_PER_CODEC_FRM_155 155 + +#define UNFRAMED 0 +#define FRAMED 1 + +#define RETRANS_NO_2 2 +#define RETRANS_NO_5 5 +#define RETRANS_NO_7 7 +#define RETRANS_NO_11 11 +#define RETRANS_NO_13 13 +#define RETRANS_NO_23 23 +#define RETRANS_NO_29 29 +#define RETRANS_NO_35 35 +#define RETRANS_NO_41 41 +#define RETRANS_NO_47 47 +#define RETRANS_NO_53 53 + +#define MAX_TRANS_LAT_MS_8 8 +#define MAX_TRANS_LAT_MS_10 10 +#define MAX_TRANS_LAT_MS_15 15 +#define MAX_TRANS_LAT_MS_20 20 +#define MAX_TRANS_LAT_MS_24 24 +#define MAX_TRANS_LAT_MS_25 25 +#define MAX_TRANS_LAT_MS_31 31 +#define MAX_TRANS_LAT_MS_33 33 +#define MAX_TRANS_LAT_MS_45 45 +#define MAX_TRANS_LAT_MS_54 54 +#define MAX_TRANS_LAT_MS_60 60 +#define MAX_TRANS_LAT_MS_71 71 +#define MAX_TRANS_LAT_MS_95 95 + +#define PRES_DELAY_20_MS 20 +#define PRES_DELAY_25_MS 25 +#define PRES_DELAY_40_MS 40 + +#define LEAUDIO_CONFIG_PATH "/system_ext/etc/bluetooth/leaudio_configs.xml" +using namespace std; + +// for storing codec config as it is read from xml +struct codec_config +{ + uint32_t freq_in_hz; + float frame_dur_msecs; + uint8_t oct_per_codec_frm; + uint8_t mandatory; +}; + +// for storing QoS settings as it is read from xml +struct qos_config +{ + uint32_t freq_in_hz; + uint32_t sdu_int_micro_secs; + uint8_t framing; + uint8_t max_sdu_size; + uint8_t retrans_num; + uint8_t max_trans_lat; + uint32_t presentation_delay; + uint8_t mandatory; +}; + + +// QoS configuration in the structure needed by ACM +struct QoSConfig +{ + CodecSampleRate sample_rate; + uint32_t sdu_int_micro_secs; + uint8_t framing; + uint8_t max_sdu_size; + uint8_t retrans_num; + uint8_t max_trans_lat; + uint32_t presentation_delay; + uint8_t mandatory; +}; +void btif_vmcp_init(); + +vector get_all_codec_configs(uint8_t profile, uint8_t context); +vector get_preferred_codec_configs(uint8_t profile, uint8_t context); + +vector get_all_qos_params(uint8_t profile, uint8_t context); +vector get_qos_params_for_codec(uint8_t profile, uint8_t context, CodecSampleRate freq, uint8_t frame_dur, uint8_t octets); diff --git a/le_audio/system/bt/btif/leaudio_configs.xml b/le_audio/system/bt/btif/leaudio_configs.xml new file mode 100644 index 00000000000..905b739ee27 --- /dev/null +++ b/le_audio/system/bt/btif/leaudio_configs.xml @@ -0,0 +1,713 @@ + + + + + + + + 8000 + 7500 + 26 + 0 + + + 8000 + 10000 + 30 + 0 + + + 32000 + 7500 + 60 + 0 + + + 32000 + 10000 + 80 + 1 + + + + + + 48000 + 7500 + 75 + 0 + + + 48000 + 10000 + 100 + 1 + + + 48000 + 7500 + 90 + 0 + + + 48000 + 10000 + 120 + 0 + + + 48000 + 7500 + 117 + 0 + + + 48000 + 10000 + 155 + 1 + + + + + + 8000 + 7500 + 0 + 26 + 2 + 8 + 20000 + 0 + + + 8000 + 10000 + 0 + 30 + 2 + 10 + 20000 + 0 + + + 32000 + 7500 + 0 + 60 + 2 + 8 + 20000 + 1 + + + 32000 + 10000 + 0 + 80 + 2 + 10 + 20000 + 1 + + + + + 48000 + 7500 + 0 + 75 + 5 + 15 + 20000 + 0 + + + 48000 + 10000 + 0 + 100 + 5 + 20 + 20000 + 1 + + + 48000 + 7500 + 0 + 90 + 5 + 15 + 20000 + 0 + + + 48000 + 10000 + 0 + 120 + 5 + 20 + 20000 + 0 + + + 48000 + 7500 + 0 + 117 + 5 + 15 + 20000 + 0 + + + 48000 + 10000 + 0 + 155 + 5 + 20 + 20000 + 0 + + + + + 48000 + 7500 + 0 + 75 + 23 + 45 + 20000 + 0 + + + 48000 + 10000 + 0 + 100 + 23 + 60 + 20000 + 1 + + + 48000 + 7500 + 0 + 90 + 23 + 45 + 20000 + 0 + + + 48000 + 10000 + 0 + 120 + 23 + 60 + 20000 + 0 + + + 48000 + 7500 + 0 + 117 + 23 + 45 + 20000 + 0 + + + 48000 + 10000 + 0 + 155 + 23 + 60 + 20000 + 0 + + + + + + + + 32000 + 10000 + 80 + 0 + + + + + 48000 + 10000 + 100 + 0 + + + + + 32000 + 10000 + 0 + 80 + 2 + 10 + 40000 + 0 + + + + + 8000 + 7500 + 0 + 26 + 2 + 8 + 40000 + 0 + + + 8000 + 10000 + 0 + 30 + 2 + 10 + 40000 + 0 + + + 16000 + 7500 + 0 + 30 + 2 + 8 + 40000 + 0 + + + 16000 + 10000 + 0 + 40 + 2 + 10 + 40000 + 0 + + + 24000 + 7500 + 0 + 45 + 2 + 8 + 40000 + 0 + + + 24000 + 10000 + 0 + 60 + 2 + 10 + 40000 + 0 + + + 48000 + 7500 + 0 + 75 + 5 + 15 + 40000 + 0 + + + 48000 + 10000 + 0 + 100 + 15 + 70 + 40000 + 0 + + + 48000 + 7500 + 0 + 90 + 5 + 15 + 40000 + 0 + + + 48000 + 10000 + 0 + 120 + 5 + 20 + 40000 + 0 + + + 32000 + 7500 + 0 + 60 + 2 + 8 + 40000 + 0 + + + 32000 + 10000 + 0 + 80 + 2 + 10 + 40000 + 0 + + + 48000 + 7500 + 0 + 117 + 5 + 15 + 40000 + 0 + + + 48000 + 10000 + 0 + 155 + 13 + 100 + 40000 + 0 + + + + + 8000 + 7500 + 0 + 26 + 41 + 45 + 40000 + 0 + + + 8000 + 10000 + 0 + 30 + 53 + 60 + 40000 + 0 + + + 16000 + 7500 + 0 + 30 + 41 + 45 + 40000 + 0 + + + 16000 + 10000 + 0 + 40 + 47 + 60 + 40000 + 1 + + + 24000 + 7500 + 0 + 45 + 35 + 45 + 40000 + 0 + + + 24000 + 10000 + 0 + 60 + 41 + 60 + 40000 + 0 + + + 32000 + 7500 + 0 + 60 + 29 + 45 + 40000 + 0 + + + 32000 + 10000 + 0 + 80 + 35 + 60 + 40000 + 0 + + + 48000 + 7500 + 0 + 75 + 23 + 45 + 40000 + 0 + + + 48000 + 10000 + 0 + 100 + 23 + 60 + 40000 + 0 + + + 48000 + 7500 + 0 + 90 + 23 + 45 + 40000 + 0 + + + 48000 + 10000 + 0 + 120 + 23 + 60 + 40000 + 0 + + + 48000 + 7500 + 0 + 117 + 23 + 45 + 40000 + 0 + + + 48000 + 10000 + 0 + 155 + 13 + 100 + 40000 + 0 + + + + + + + + 16000 + 7500 + 60 + 1 + + + 16000 + 7500 + 60 + 0 + + + 16000 + 7500 + 30 + 1 + + + + + + 48000 + 7500 + 75 + 0 + + + + + + 16000 + 15000 + 0 + 60 + 2 + 8 + 25000 + 1 + + + 16000 + 15000 + 0 + 60 + 7 + 25 + 25000 + 0 + + + 16000 + 7500 + 0 + 30 + 11 + 33 + 25000 + 1 + + + + + + 48000 + 7500 + 0 + 75 + 5 + 12 + 25000 + 0 + + + + + + + + 48000 + 10000 + 100 + 0 + + + + + 16000 + 10000 + 0 + 40 + 13 + 95 + 40000 + 0 + + + 16000 + 7500 + 0 + 30 + 13 + 75 + 40000 + 0 + + + 32000 + 10000 + 0 + 80 + 13 + 95 + 40000 + 0 + + + 32000 + 7500 + 0 + 60 + 13 + 75 + 40000 + 0 + + + 48000 + 10000 + 0 + 100 + 11 + 40 + 25000 + 0 + + + 48000 + 7500 + 0 + 75 + 13 + 75 + 40000 + 0 + + + + + + diff --git a/le_audio/system/bt/btif/src/bluetooth_adv_audio.cc b/le_audio/system/bt/btif/src/bluetooth_adv_audio.cc new file mode 100644 index 00000000000..ad1b1accff3 --- /dev/null +++ b/le_audio/system/bt/btif/src/bluetooth_adv_audio.cc @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/****************************************************************************** + * + * Copyright (C) 2009-2012 Broadcom Corporation + * + * 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. + * + ******************************************************************************/ + +/******************************************************************************* + * + * Filename: bluetooth_adv_audio.cc + * + * Description: Bluetooth LEA HAL implementation + * + ******************************************************************************/ + +#define LOG_TAG "bt_btif_adv_audio" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "osi/include/log.h" +#include "btif_bap_config.h" +#include "bta_csip_api.h" +#include "stack_interface.h" +#include "btcore/include/module.h" +#include "btcore/include/osi_module.h" +#include + +/******************************************************************************* + * Externs + ******************************************************************************/ + +/* list all extended interfaces here */ +using bluetooth::bap::pacs::PacsClientInterface; +using bluetooth::bap::ascs::AscsClientInterface; +using bluetooth::bap::ucast::UcastClientInterface; +using bluetooth::vcp_controller::VcpControllerInterface; +using bluetooth::mcp_server::McpServerInterface; +using bluetooth::call_control::CallControllerInterface; +extern PacsClientInterface *btif_pacs_client_get_interface(); +extern AscsClientInterface *btif_ascs_client_get_interface(); +extern UcastClientInterface *btif_bap_uclient_get_interface(); +extern bt_apm_interface_t *btif_apm_get_interface(); +extern btacm_initiator_interface_t* btif_acm_initiator_get_interface(); +extern btbap_broadcast_interface_t * btif_bap_broadcast_get_interface(); +/* Coordinated set identification profile - client */ +extern btcsip_interface_t* btif_csip_get_interface(); +/*Vcp Controller*/ +extern VcpControllerInterface* btif_vcp_get_controller_interface(); +/*Mcp server*/ +extern McpServerInterface* btif_mcp_server_get_interface(); +extern CallControllerInterface* btif_cc_server_get_interface(); + +/******************************************************************************* + * Functions + ******************************************************************************/ + +static bool is_profile(const char* p1, const char* p2) { + CHECK(p1); + CHECK(p2); + return strlen(p1) == strlen(p2) && strncmp(p1, p2, strlen(p2)) == 0; +} + +/***************************************************************************** + * + * BLUETOOTH LEA HAL INTERFACE FUNCTIONS + * + ****************************************************************************/ + +StackCallbacks *stack_callbacks; + +const void* get_adv_audio_profile_interface(const char* profile_id) { + LOG_INFO(LOG_TAG, "%s: id = %s", __func__, profile_id); + + if (is_profile(profile_id, BT_PROFILE_PACS_CLIENT_ID)) { + return btif_pacs_client_get_interface(); + } + + if (is_profile(profile_id, BT_APM_MODULE_ID)) { + return btif_apm_get_interface(); + } + + if (is_profile(profile_id, BT_PROFILE_ACM_ID)) { + return btif_acm_initiator_get_interface(); + } + + if (is_profile(profile_id, BT_PROFILE_BAP_BROADCAST_ID)) + return btif_bap_broadcast_get_interface(); + + if (is_profile(profile_id, BT_PROFILE_CSIP_CLIENT_ID)) { + return btif_csip_get_interface(); + } + + if (is_profile(profile_id, BT_PROFILE_VOLUME_CONTROL_ID)) { + return btif_vcp_get_controller_interface(); + } + + if (is_profile(profile_id, BT_PROFILE_MCP_ID)) { + return btif_mcp_server_get_interface(); + } + + if (is_profile(profile_id, BT_PROFILE_CC_ID)) { + return btif_cc_server_get_interface(); + } + + if (is_profile(profile_id, BT_PROFILE_ASCS_CLIENT_ID)) { + return btif_ascs_client_get_interface(); + } + + if (is_profile(profile_id, BT_PROFILE_BAP_UCLIENT_ID)) { + return bluetooth::bap::ucast::btif_bap_uclient_get_interface(); + } + return NULL; +} + +class StackCallbacksImpl : public StackCallbacks { + public: + ~StackCallbacksImpl() = default; + void OnDevUnPaired(const RawAddress& address) override { + BTA_CsipRemoveUnpairedSetMember(address); + btif_bap_remove_all_records(address); + } + + void OnConfigCleared(void) override { + btif_bap_config_clear(); + } + + void OnStackState(StackState state) { + switch(state) { + case StackState::INITIALIZING: + module_init(get_module(BTIF_BAP_CONFIG_MODULE)); + break; + case StackState::TURNING_ON: + module_start_up(get_module(BTIF_BAP_CONFIG_MODULE)); + break; + case StackState::TURNING_OFF: + module_shut_down(get_module(BTIF_BAP_CONFIG_MODULE)); + break; + case StackState::CLEAND_UP: + module_clean_up(get_module(BTIF_BAP_CONFIG_MODULE)); + break; + default: + break; + } + } +}; + +void init_adv_audio_interfaces() { + stack_callbacks = new StackCallbacksImpl; + StackInterface::Initialize(stack_callbacks); +} diff --git a/le_audio/system/bt/btif/src/btif_acm.cc b/le_audio/system/bt/btif/src/btif_acm.cc new file mode 100644 index 00000000000..5378515365b --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_acm.cc @@ -0,0 +1,6658 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +#define LOG_TAG "btif_acm" +#include "btif_acm.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "bta_closure_api.h" +#include "btif_storage.h" +#include +#include +#include "audio_hal_interface/a2dp_encoding.h" +#include "bt_common.h" +#include "bt_utils.h" +#include "bta/include/bta_api.h" +#include "btif/include/btif_a2dp_source.h" +#include "btif_common.h" +#include +#include "audio_a2dp_hw/include/audio_a2dp_hw.h" +#include "btif_av_co.h" +#include "btif_util.h" +#include "btu.h" +#include "common/state_machine.h" +#include "osi/include/allocator.h" +#include "osi/include/osi.h" +#include "osi/include/properties.h" +#include "btif/include/btif_bap_config.h" +#include "bta_bap_uclient_api.h" +#include "btif_bap_codec_utils.h" +#include "bta/include/bta_csip_api.h" +#include +#include "osi/include/thread.h" +#include +#include "bta_api.h" +#include +#include +#include "btif/include/btif_vmcp.h" +#include "btif/include/btif_acm_source.h" +#include "l2c_api.h" +#include "bt_types.h" +#include "btm_int.h" +#include + +/***************************************************************************** + * Constants & Macros + *****************************************************************************/ +#define LE_AUDIO_MASK 0x00000300 +#define LE_AUDIO_NOT_AVAILABLE 0x00000100 +#define LE_AUDIO_AVAILABLE_NOT_LICENSED 0x00000200 //LC3 +#define LE_AUDIO_AVAILABLE_LICENSED 0x00000300 //LC3Q +#define LE_AUDIO_CS_3_1ST_BYTE_INDEX 0x00 +#define LE_AUDIO_CS_3_2ND_BYTE_INDEX 0x01 +#define LE_AUDIO_CS_3_3RD_BYTE_INDEX 0x02 +#define LE_AUDIO_CS_3_4TH_BYTE_INDEX 0x03 +#define LE_AUDIO_CS_3_5TH_BYTE_INDEX 0x04 +#define LE_AUDIO_CS_3_7TH_BYTE_INDEX 0x06 +#define LE_AUDIO_CS_3_8TH_BYTE_INDEX 0x07 + +static RawAddress active_bda = {}; +static constexpr int kDefaultMaxConnectedAudioDevices = 5; +CodecConfig current_active_config; +static CodecConfig current_media_config; +static CodecConfig current_voice_config; +static CodecConfig current_recording_config; +uint16_t current_active_profile_type = 0; +uint16_t current_active_context_type; + +using bluetooth::bap::ucast::UcastClientInterface; +using bluetooth::bap::ucast::UcastClientCallbacks; +using bluetooth::bap::ucast::UcastClient; +using bluetooth::bap::ucast::StreamState; +using bluetooth::bap::ucast::StreamConnect; +using bluetooth::bap::ucast::StreamType; + +using bluetooth::bap::pacs::CodecIndex; +using bluetooth::bap::pacs::CodecPriority; +using bluetooth::bap::pacs::CodecSampleRate; +using bluetooth::bap::pacs::CodecBPS; +using bluetooth::bap::pacs::CodecChannelMode; +using bluetooth::bap::pacs::CodecFrameDuration; +using bluetooth::bap::ucast::CodecQosConfig; +using bluetooth::bap::ucast::StreamStateInfo; +using bluetooth::bap::ucast::StreamConfigInfo; +using bluetooth::bap::ucast::StreamReconfig; +using bluetooth::bap::ucast::CISConfig; +using bluetooth::bap::pacs::CodecDirection; +using bluetooth::bap::ucast::CONTENT_TYPE_MEDIA; +using bluetooth::bap::ucast::CONTENT_TYPE_CONVERSATIONAL; +using bluetooth::bap::ucast::CONTENT_TYPE_LIVE; +using bluetooth::bap::ucast::CONTENT_TYPE_UNSPECIFIED; +using bluetooth::bap::ucast::CONTENT_TYPE_INSTRUCTIONAL; +using bluetooth::bap::ucast::CONTENT_TYPE_NOTIFICATIONS; +using bluetooth::bap::ucast::CONTENT_TYPE_ALERT; +using bluetooth::bap::ucast::CONTENT_TYPE_MAN_MACHINE; +using bluetooth::bap::ucast::CONTENT_TYPE_EMERGENCY; +using bluetooth::bap::ucast::CONTENT_TYPE_RINGTONE; +using bluetooth::bap::ucast::CONTENT_TYPE_SOUND_EFFECTS; +using bluetooth::bap::ucast::CONTENT_TYPE_GAME; + +using bluetooth::bap::ucast::ASE_DIRECTION_SRC; +using bluetooth::bap::ucast::ASE_DIRECTION_SINK; +using bluetooth::bap::ucast::ASCSConfig; +using bluetooth::bap::ucast::LE_2M_PHY; +using bluetooth::bap::ucast::LE_QHS_PHY; + +using base::Bind; +using base::Unretained; +using base::IgnoreResult; +using bluetooth::Uuid; +extern void do_in_bta_thread(const base::Location& from_here, + const base::Closure& task); + +bool reconfig_acm_initiator(const RawAddress& peer_address, int profileType); + +static void btif_acm_initiator_dispatch_sm_event(const RawAddress& peer_address, + btif_acm_sm_event_t event); +void btif_acm_update_lc3q_params(int64_t* cs3, tBTIF_ACM* p_acm_data); + +uint16_t btif_acm_bap_to_acm_context(uint16_t bap_context); + +std::mutex acm_session_wait_mutex_; +std::condition_variable acm_session_wait_cv; +bool acm_session_wait; + + +/***************************************************************************** + * Local type definitions + *****************************************************************************/ + +class BtifCsipEvent { + public: + BtifCsipEvent(uint32_t event, const void* p_data, size_t data_length); + BtifCsipEvent(const BtifCsipEvent& other); + BtifCsipEvent() = delete; + ~BtifCsipEvent(); + BtifCsipEvent& operator=(const BtifCsipEvent& other); + + uint32_t Event() const { return event_; } + void* Data() const { return data_; } + size_t DataLength() const { return data_length_; } + std::string ToString() const; + static std::string EventName(uint32_t event); + + private: + void DeepCopy(uint32_t event, const void* p_data, size_t data_length); + void DeepFree(); + + uint32_t event_; + void* data_; + size_t data_length_; +}; + +class BtifAcmEvent { + public: + BtifAcmEvent(uint32_t event, const void* p_data, size_t data_length); + BtifAcmEvent(const BtifAcmEvent& other); + BtifAcmEvent() = delete; + ~BtifAcmEvent(); + BtifAcmEvent& operator=(const BtifAcmEvent& other); + + uint32_t Event() const { return event_; } + void* Data() const { return data_; } + size_t DataLength() const { return data_length_; } + std::string ToString() const; + static std::string EventName(uint32_t event); + + private: + void DeepCopy(uint32_t event, const void* p_data, size_t data_length); + void DeepFree(); + + uint32_t event_; + void* data_; + size_t data_length_; +}; + +class BtifAcmPeer; + +class BtifAcmStateMachine : public bluetooth::common::StateMachine { + public: + enum { + kStateIdle, // ACM state disconnected + kStateOpening, // ACM state connecting + kStateOpened, // ACM state connected + kStateStarted, // ACM state streaming + kStateReconfiguring, // ACM state reconfiguring + kStateClosing, // ACM state disconnecting + }; + + class StateIdle : public State { + public: + StateIdle(BtifAcmStateMachine& sm) + : State(sm, kStateIdle), peer_(sm.Peer()) {} + void OnEnter() override; + void OnExit() override; + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + BtifAcmPeer& peer_; + }; + + class StateOpening : public State { + public: + StateOpening(BtifAcmStateMachine& sm) + : State(sm, kStateOpening), peer_(sm.Peer()) {} + void OnEnter() override; + void OnExit() override; + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + BtifAcmPeer& peer_; + }; + + class StateOpened : public State { + public: + StateOpened(BtifAcmStateMachine& sm) + : State(sm, kStateOpened), peer_(sm.Peer()) {} + void OnEnter() override; + void OnExit() override; + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + BtifAcmPeer& peer_; + }; + + class StateStarted : public State { + public: + StateStarted(BtifAcmStateMachine& sm) + : State(sm, kStateStarted), peer_(sm.Peer()) {} + void OnEnter() override; + void OnExit() override; + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + BtifAcmPeer& peer_; + }; + + class StateReconfiguring : public State { + public: + StateReconfiguring(BtifAcmStateMachine& sm) + : State(sm, kStateReconfiguring), peer_(sm.Peer()) {} + void OnEnter() override; + void OnExit() override; + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + BtifAcmPeer& peer_; + }; + + class StateClosing : public State { + public: + StateClosing(BtifAcmStateMachine& sm) + : State(sm, kStateClosing), peer_(sm.Peer()) {} + void OnEnter() override; + void OnExit() override; + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + BtifAcmPeer& peer_; + }; + + BtifAcmStateMachine(BtifAcmPeer& btif_acm_peer) : peer_(btif_acm_peer) { + state_idle_ = new StateIdle(*this); + state_opening_ = new StateOpening(*this); + state_opened_ = new StateOpened(*this); + state_started_ = new StateStarted(*this); + state_reconfiguring_ = new StateReconfiguring(*this); + state_closing_ = new StateClosing(*this); + + AddState(state_idle_); + AddState(state_opening_); + AddState(state_opened_); + AddState(state_started_); + AddState(state_reconfiguring_); + AddState(state_closing_); + SetInitialState(state_idle_); + } + + BtifAcmPeer& Peer() { return peer_; } + + private: + BtifAcmPeer& peer_; + StateIdle* state_idle_; + StateOpening* state_opening_; + StateOpened* state_opened_; + StateStarted* state_started_; + StateReconfiguring* state_reconfiguring_; + StateClosing* state_closing_; +}; + +class BtifAcmPeer { + public: + enum { + kFlagPendingLocalSuspend = 0x01, + kFlagPendingReconfigure = 0x02, + kFlagPendingStart = 0x04, + kFlagPendingStop = 0x08, + kFLagPendingStartAfterReconfig = 0x10, + }; + + enum { + kFlagAggresiveMode = 0x01, + kFlagRelaxedMode = 0x02, + }; + + static constexpr uint64_t kTimeoutLockReleaseMs = 5 * 1000; + + BtifAcmPeer(const RawAddress& peer_address, uint8_t peer_sep, + uint8_t set_id, uint8_t cig_id, uint8_t cis_id); + ~BtifAcmPeer(); + + bt_status_t Init(); + void Cleanup(); + + /** + * Check whether the peer can be deleted. + * + * @return true if the pair can be deleted, otherwise false + */ + bool CanBeDeleted() const; + + bool IsPeerActiveForMusic() const { + return (SetId() == MusicActiveSetId()); + } + bool IsPeerActiveForVoice() const { + return (SetId() == VoiceActiveSetId()); + } + + bool IsAcceptor() const { return (peer_sep_ == ACM_TSEP_SNK); } + + const RawAddress& MusicActivePeerAddress() const; + const RawAddress& VoiceActivePeerAddress() const; + uint8_t MusicActiveSetId() const; + uint8_t VoiceActiveSetId() const; + + const RawAddress& PeerAddress() const { return peer_address_; } + + void SetContextType(uint16_t contextType) { context_type_ = context_type_ | contextType; } + uint16_t GetContextType() { return context_type_; } + void ResetContextType(uint16_t contextType) { context_type_ &= ~contextType; } + + + void SetProfileType(uint16_t profileType) { profile_type_ = profile_type_ | profileType; } + uint16_t GetProfileType() {return profile_type_;} + void ResetProfileType(uint16_t profileType) { profile_type_ &= ~profileType; } + + + void SetRcfgProfileType(uint16_t profileType) { rcfg_profile_type_ = profileType; } + uint16_t GetRcfgProfileType() {return rcfg_profile_type_;} + + void SetPrefContextType(uint16_t preferredContext) {preferred_context_ = preferredContext;}; + uint16_t GetPrefContextType() {return preferred_context_;} + + void SetStreamContextType(uint16_t contextType) { stream_context_type_ = contextType; } + uint16_t GetStreamContextType() { return stream_context_type_; } + + void SetPeerVoiceRxState(StreamState state) {voice_rx_state = state;} + StreamState GetPeerVoiceRxState() {return voice_rx_state;} + + void SetPeerVoiceTxState(StreamState state) {voice_tx_state = state;} + StreamState GetPeerVoiceTxState() {return voice_tx_state;} + + void SetPeerMusicTxState(StreamState state) {music_tx_state = state;} + StreamState GetPeerMusicTxState() {return music_tx_state;} + + void SetPeerMusicRxState(StreamState state) {music_rx_state = state;} + StreamState GetPeerMusicRxState() {return music_rx_state;} + + void SetPeerLatency(uint16_t peerLatency) { peer_latency_ = peerLatency; } + uint16_t GetPeerLatency() {return peer_latency_;} + + void SetIsStereoHsType(bool stereoHsType) { is_stereohs_type_= stereoHsType; } + bool IsStereoHsType() {return is_stereohs_type_;} + + void set_peer_media_codec_config(CodecConfig &codec_config) { + peer_media_codec_config = codec_config; + } + CodecConfig get_peer_media_codec_config() {return peer_media_codec_config;} + + void set_peer_media_qos_config(QosConfig &qos_config) {peer_media_qos_config = qos_config;} + QosConfig get_peer_media_qos_config() {return peer_media_qos_config;} + + void set_peer_media_codec_qos_config(CodecQosConfig &codec_qos_config) { + peer_media_codec_qos_config = codec_qos_config;} + CodecQosConfig get_peer_media_codec_qos_config() {return peer_media_codec_qos_config;} + + void set_peer_voice_rx_codec_config(CodecConfig &codec_config) { + peer_voice_rx_codec_config = codec_config; + } + CodecConfig get_peer_voice_rx_codec_config() {return peer_voice_rx_codec_config;} + + void set_peer_voice_rx_qos_config(QosConfig &qos_config) {peer_voice_rx_qos_config = qos_config;} + QosConfig get_peer_voice_rx_qos_config() {return peer_voice_rx_qos_config;} + + void set_peer_voice_rx_codec_qos_config(CodecQosConfig &codec_qos_config) { + peer_voice_rx_codec_qos_config = codec_qos_config;} + CodecQosConfig get_peer_voice_rx_codec_qos_config() {return peer_voice_rx_codec_qos_config;} + + void set_peer_voice_tx_codec_config(CodecConfig &codec_config) { + peer_voice_tx_codec_config = codec_config; + } + CodecConfig get_peer_voice_tx_codec_config() {return peer_voice_tx_codec_config;} + + void set_peer_voice_tx_qos_config(QosConfig &qos_config) {peer_voice_tx_qos_config = qos_config;} + QosConfig get_peer_voice_tx_qos_config() {return peer_voice_tx_qos_config;} + + void set_peer_voice_tx_codec_qos_config(CodecQosConfig &codec_qos_config) { + peer_voice_tx_codec_qos_config = codec_qos_config;} + CodecQosConfig get_peer_voice_tx_codec_qos_config() {return peer_voice_tx_codec_qos_config;} + + uint8_t SetId() const { return set_id_; } + uint8_t CigId() const { return cig_id_; } + uint8_t CisId() const { return cis_id_; } + + BtifAcmStateMachine& StateMachine() { return state_machine_; } + const BtifAcmStateMachine& StateMachine() const { return state_machine_; } + + bool IsConnected() const; + bool IsStreaming() const; + + bool CheckConnUpdateMode(uint8_t mode) const { + return (conn_mode_ == mode); + } + + void SetConnUpdateMode(uint8_t mode) { + if(conn_mode_ == mode) return; + if(mode == kFlagAggresiveMode) { + BTIF_TRACE_DEBUG("%s: push aggressive intervals", __func__); + L2CA_UpdateBleConnParams(peer_address_, 16, 32, 0, 1000); + } else if(mode == kFlagRelaxedMode) { + BTIF_TRACE_DEBUG("%s: push relaxed intervals", __func__); + L2CA_UpdateBleConnParams(peer_address_, 40, 56, 0, 1000); + } + conn_mode_ = mode; + } + + void ClearConnUpdateMode() { conn_mode_ = 0; } + + bool CheckFlags(uint8_t flags_mask) const { + return ((flags_ & flags_mask) != 0); + } + + /** + * Set only the flags as specified by the flags mask. + * + * @param flags_mask the flags to set + */ + void SetFlags(uint8_t flags_mask) { flags_ |= flags_mask; } + + /** + * Clear only the flags as specified by the flags mask. + * + * @param flags_mask the flags to clear + */ + void ClearFlags(uint8_t flags_mask) { flags_ &= ~flags_mask; } + + /** + * Clear all the flags. + */ + void ClearAllFlags() { flags_ = 0; } + + /** + * Get string for the flags set. + */ + std::string FlagsToString() const; + + private: + const RawAddress peer_address_; + const uint8_t peer_sep_;// SEP type of peer device + uint8_t set_id_, cig_id_, cis_id_; + BtifAcmStateMachine state_machine_; + uint8_t flags_; + uint8_t conn_mode_; + bool is_stereohs_type_ = false; + StreamState voice_rx_state, voice_tx_state, music_tx_state, music_rx_state; + uint16_t peer_latency_; + uint16_t context_type_ = 0; + uint16_t profile_type_ = 0; + uint16_t rcfg_profile_type_ = 0; + uint16_t preferred_context_ = 0; + uint16_t stream_context_type_ = 0; + CodecConfig peer_media_codec_config, peer_voice_rx_codec_config, peer_voice_tx_codec_config; + QosConfig peer_media_qos_config, peer_voice_rx_qos_config, peer_voice_tx_qos_config; + CodecQosConfig peer_media_codec_qos_config, peer_voice_rx_codec_qos_config, peer_voice_tx_codec_qos_config; +}; + +static void btif_acm_check_and_cancel_lock_release_timer(uint8_t setId); +bool btif_acm_request_csip_unlock(uint8_t setId); +void btif_acm_process_request(tA2DP_CTRL_CMD cmd); + +void btif_acm_source_on_stopped(); +void btif_acm_source_on_suspended(); +void btif_acm_on_idle(void); +bool btif_acm_check_if_requested_devices_stopped(); + +void btif_acm_source_cleanup(void); + +bt_status_t btif_acm_source_setup_codec(); +uint16_t btif_acm_get_active_device_latency(); + +class BtifAcmInitiator { + public: + static constexpr uint8_t kCigIdMin = 0; + static constexpr uint8_t kCigIdMax = BTA_ACM_NUM_CIGS; + static constexpr uint8_t kPeerMinSetId = BTA_ACM_MIN_NUM_SETID; + static constexpr uint8_t kPeerMaxSetId = BTA_ACM_MAX_NUM_SETID; + + enum { + kFlagStatusUnknown = 0x0, + kFlagStatusUnlocked = 0x1, + kFlagStatusPendingLock = 0x2, + kFlagStatusSubsetLocked = 0x4, + kFlagStatusLocked = 0x8, + kFlagStatusPendingUnlock = 0x10, + }; + + // acm group procedure timer + static constexpr uint64_t kTimeoutAcmGroupProcedureMs = 10 * 1000; + static constexpr uint64_t kTimeoutConnIntervalMs = 5 * 1000; + + BtifAcmInitiator() + : callbacks_(nullptr), + enabled_(false), + max_connected_peers_(kDefaultMaxConnectedAudioDevices), + music_active_setid_(INVALID_SET_ID), + voice_active_setid_(INVALID_SET_ID), + csip_app_id_(0), + is_csip_reg_(false), + lock_flags_(0), + music_set_lock_release_timer_(nullptr), + voice_set_lock_release_timer_(nullptr), + acm_group_procedure_timer_(nullptr), + acm_conn_interval_timer_(nullptr){} + ~BtifAcmInitiator(); + + bt_status_t Init( + btacm_initiator_callbacks_t* callbacks, int max_connected_audio_devices, + const std::vector& codec_priorities); + void Cleanup(); + bool IsSetIdle(uint8_t setId) const; + + btacm_initiator_callbacks_t* Callbacks() { return callbacks_; } + bool Enabled() const { return enabled_; } + + BtifAcmPeer* FindPeer(const RawAddress& peer_address); + uint8_t FindPeerSetId(const RawAddress& peer_address); + uint8_t FindPeerBySetId(uint8_t set_id); + uint8_t FindPeerCigId(uint8_t set_id); + uint8_t FindPeerByCigId(uint8_t cig_id); + uint8_t FindPeerByCisId(uint8_t cig_id, uint8_t cis_id); + BtifAcmPeer* FindOrCreatePeer(const RawAddress& peer_address); + BtifAcmPeer* FindMusicActivePeer(); + + /** + * Check whether a connection to a peer is allowed. + * The check considers the maximum number of connected peers. + * + * @param peer_address the peer address to connect to + * @return true if connection is allowed, otherwise false + */ + bool AllowedToConnect(const RawAddress& peer_address) const; + bool IsAcmIdle() const; + + bool IsOtherSetPeersIdle(const RawAddress& peer_address, uint8_t setId) const; + + alarm_t* MusicSetLockReleaseTimer() { return music_set_lock_release_timer_; } + alarm_t* VoiceSetLockReleaseTimer() { return voice_set_lock_release_timer_; } + alarm_t* AcmGroupProcedureTimer() { return acm_group_procedure_timer_; } + alarm_t* AcmConnIntervalTimer() { return acm_conn_interval_timer_; } + + /** + * Delete a peer. + * + * @param peer_address of the peer to be deleted + * @return true on success, false on failure + */ + bool DeletePeer(const RawAddress& peer_address); + + /** + * Delete all peers that are in Idle state and can be deleted. + */ + void DeleteIdlePeers(); + + /** + * Get the Music active peer. + * + * @return the music active peer + */ + const RawAddress& MusicActivePeer() const { return music_active_peer_; } + + /** + * Get the Voice active peer. + * + * @return the voice active peer + */ + const RawAddress& VoiceActivePeer() const { return voice_active_peer_; } + + uint8_t MusicActiveCSetId() const { return music_active_setid_; } + uint8_t VoiceActiveCSetId() const { return voice_active_setid_; } + + void SetCsipAppId(uint8_t csip_app_id) { csip_app_id_ = csip_app_id; } + uint8_t GetCsipAppId() const { return csip_app_id_; } + + void SetCsipRegistration(bool is_csip_reg) { is_csip_reg_ = is_csip_reg; } + bool IsCsipRegistered() const { return is_csip_reg_;} + + void SetMusicActiveGroupStarted(bool flag) { is_music_active_set_started_ = flag; } + bool IsMusicActiveGroupStarted () { return is_music_active_set_started_; } + + bool IsConnUpdateEnabled() const { + return (is_conn_update_enabled_ == true); + } + + void SetOrUpdateGroupLockStatus(uint8_t set_id, int lock_status) { + std::map::iterator p = set_lock_status_.find(set_id); + if (p == set_lock_status_.end()) { + set_lock_status_.insert(std::make_pair(set_id, lock_status)); + } else { + set_lock_status_.erase(set_id); + set_lock_status_.insert(std::make_pair(set_id, lock_status)); + } + } + + int GetGroupLockStatus(uint8_t set_id) { + auto it = set_lock_status_.find(set_id); + if (it != set_lock_status_.end()) return it->second; + return kFlagStatusUnknown; + } + + bool CheckLockFlags(uint8_t bitlockflags_mask) const { + return ((lock_flags_ & bitlockflags_mask) != 0); + } + + /** + * Set only the flags as specified by the bitlockflags_mask. + * + * @param bitlockflags_mask the lock flags to set + */ + void SetLockFlags(uint8_t bitlockflags_mask) { lock_flags_ |= bitlockflags_mask;} + + /** + * Clear only the flags as specified by the bitlockflags_mask. + * + * @param bitlockflags_mask the lock flags to clear + */ + void ClearLockFlags(uint8_t bitlockflags_mask) { lock_flags_ &= ~bitlockflags_mask;} + + /** + * Clear all lock flags. + */ + void ClearAllLockFlags() { lock_flags_ = 0;} + + /** + * Get a string for lock flags. + */ + std::string LockFlagsToString() const; + + bool SetAcmActivePeer(const RawAddress& peer_address, uint16_t contextType, uint16_t profileType, + std::promise peer_ready_promise) { + LOG(INFO) << __PRETTY_FUNCTION__ << ": peer: " << peer_address + << " music_active_peer_: " << music_active_peer_ << " voice_active_peer_: " << voice_active_peer_; + uint16_t sink_latency; + active_bda = peer_address;// for stereo LEA active_bda = peer_address + BtifAcmPeer* peer = FindPeer(peer_address); + BTIF_TRACE_DEBUG("%s address byte BDA:%02x", __func__,active_bda.address[5]); + if (contextType == CONTEXT_TYPE_MUSIC) { + if (music_active_peer_ == active_bda) { + //Same active device, profileType may have changed. + if ((peer != nullptr) && (current_active_profile_type != 0) && (current_active_profile_type != profileType)) { + BTIF_TRACE_DEBUG("%s current_active_profile_type %d, profileType %d peer->GetProfileType() %d", + __func__, current_active_profile_type, profileType, peer->GetProfileType()); + if ((peer->GetProfileType() & profileType) == 0) { + std::unique_lock guard(acm_session_wait_mutex_); + acm_session_wait = false; + if (reconfig_acm_initiator(peer_address, profileType)) { + acm_session_wait_cv.wait_for(guard, std::chrono::milliseconds(3000), []{return acm_session_wait;}); + BTIF_TRACE_EVENT("%s: done with signal",__func__); + } + } else { + current_active_profile_type = profileType; + if (current_active_profile_type != WMCP) + current_active_config = current_media_config; + else + current_active_config = current_recording_config; + if (!btif_acm_source_restart_session(music_active_peer_, active_bda)) { + // cannot set promise but need to be handled within restart_session + return false; + } + if (current_active_profile_type == WMCP) { + sink_latency = btif_acm_get_active_device_latency(); + BTIF_TRACE_EVENT("%s: sink_latency = %dms", __func__, sink_latency); + if ((sink_latency > 0) && !btif_acm_update_sink_latency_change(sink_latency * 10)) { + BTIF_TRACE_ERROR("%s: unable to update latency", __func__); + } + } + } + peer_ready_promise.set_value(); + return true; + } else { + peer_ready_promise.set_value(); + return true; + } + } + + if (active_bda.IsEmpty()) { + BTIF_TRACE_EVENT("%s: set address is empty, shutdown the Acm initiator", + __func__); + btif_acm_check_and_cancel_lock_release_timer(music_active_setid_); + if ((GetGroupLockStatus(music_active_setid_) == BtifAcmInitiator::kFlagStatusLocked) || + (GetGroupLockStatus(music_active_setid_) == BtifAcmInitiator::kFlagStatusSubsetLocked)) { + if (!btif_acm_request_csip_unlock(music_active_setid_)) { + BTIF_TRACE_ERROR("%s: error unlocking", __func__); + } + } + btif_acm_source_end_session(music_active_peer_); + music_active_peer_ = active_bda; + current_active_profile_type = 0; + memset(¤t_active_config, 0, sizeof(current_active_config)); + peer_ready_promise.set_value(); + return true; + } + + btif_acm_check_and_cancel_lock_release_timer(music_active_setid_); + if ((GetGroupLockStatus(music_active_setid_) == BtifAcmInitiator::kFlagStatusLocked) || + (GetGroupLockStatus(music_active_setid_) == BtifAcmInitiator::kFlagStatusSubsetLocked)) { + if (!btif_acm_request_csip_unlock(music_active_setid_)) { + BTIF_TRACE_ERROR("%s: error unlocking", __func__); + } + } + + /*check if previous active device is streaming, then STOP it first*/ + if (!music_active_peer_.IsEmpty()) { + int setid = music_active_setid_; + if (setid < INVALID_SET_ID) { + tBTA_CSIP_CSET cset_info; + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(setid); + if (cset_info.size != 0) { + std::vector::iterator itr; + BTIF_TRACE_DEBUG("%s: size of set members %d", __func__, (cset_info.set_members).size()); + if ((cset_info.set_members).size() > 0) { + for (itr =(cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + BtifAcmPeer* grp_peer = FindPeer(*itr); + if (grp_peer != nullptr && grp_peer->IsStreaming()) { + BTIF_TRACE_DEBUG("%s: peer is streaming %s ", __func__, grp_peer->PeerAddress().ToString().c_str()); + btif_acm_initiator_dispatch_sm_event(*itr, BTIF_ACM_STOP_STREAM_REQ_EVT); + } + } + } + } + } else { + BTIF_TRACE_DEBUG("%s: music_active_peer_ is twm device ", __func__); + BtifAcmPeer* twm_peer = FindPeer(music_active_peer_); + if (twm_peer != nullptr && twm_peer->IsStreaming()) { + BTIF_TRACE_DEBUG("%s: music_active_peer_ %s is streaming, send stop ", __func__, twm_peer->PeerAddress().ToString().c_str()); + btif_acm_initiator_dispatch_sm_event(music_active_peer_, BTIF_ACM_STOP_STREAM_REQ_EVT); + } + } + } + + if ((peer != nullptr) && ((peer->GetProfileType() & profileType) == 0)) { + BTIF_TRACE_DEBUG("%s peer.GetProfileType() %d, profileType %d", __func__, peer->GetProfileType(), profileType); + std::unique_lock guard(acm_session_wait_mutex_); + acm_session_wait = false; + if (reconfig_acm_initiator(peer_address, profileType)) { + acm_session_wait_cv.wait_for(guard, std::chrono::milliseconds(3000), []{return acm_session_wait;}); + BTIF_TRACE_EVENT("%s: done with signal",__func__); + } + } else { + current_active_profile_type = profileType; + if (current_active_profile_type != WMCP) + current_active_config = current_media_config; + else + current_active_config = current_recording_config; + if (!btif_acm_source_restart_session(music_active_peer_, active_bda)) { + // cannot set promise but need to be handled within restart_session + return false; + } + } + music_active_peer_ = active_bda; + if (active_bda.address[0] == 0x9E && active_bda.address[1] == 0x8B && active_bda.address[2] == 0x00) { + BTIF_TRACE_DEBUG("%s: get set ID from group BD address ", __func__); + music_active_setid_ = active_bda.address[5]; + } else { + BTIF_TRACE_DEBUG("%s: get set ID from peer data ", __func__); + if (peer != nullptr) + music_active_setid_ = peer->SetId(); + } + + if (current_active_profile_type == WMCP) { + sink_latency = btif_acm_get_active_device_latency(); + BTIF_TRACE_EVENT("%s: sink_latency = %dms", __func__, sink_latency); + if ((sink_latency > 0) && !btif_acm_update_sink_latency_change(sink_latency * 10)) { + BTIF_TRACE_ERROR("%s: unable to update latency", __func__); + } + } + peer_ready_promise.set_value(); + return true; + } else if (contextType == CONTEXT_TYPE_VOICE) { + if (voice_active_peer_ == active_bda) { + peer_ready_promise.set_value(); + return true; + } + if (active_bda.IsEmpty()) { + BTIF_TRACE_EVENT("%s: peer address is empty, shutdown the acm initiator", + __func__); + voice_active_peer_ = active_bda; + peer_ready_promise.set_value(); + return true; + } + + /*check if previous active device is streaming, then STOP it first*/ + if (!voice_active_peer_.IsEmpty()) { + int setid = voice_active_setid_; + if (setid < INVALID_SET_ID) { + tBTA_CSIP_CSET cset_info; + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(setid); + if (cset_info.size != 0) { + std::vector::iterator itr; + BTIF_TRACE_DEBUG("%s: size of set members %d", __func__, (cset_info.set_members).size()); + if ((cset_info.set_members).size() > 0) { + for (itr =(cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + BtifAcmPeer* grp_peer = FindPeer(*itr); + if (grp_peer != nullptr && grp_peer->IsStreaming()) { + BTIF_TRACE_DEBUG("%s: voice peer is streaming %s ", __func__, grp_peer->PeerAddress().ToString().c_str()); + btif_acm_initiator_dispatch_sm_event(*itr, BTIF_ACM_STOP_STREAM_REQ_EVT); + } + } + } + } + } else { + BTIF_TRACE_DEBUG("%s: voice_active_peer_ is twm device ", __func__); + BtifAcmPeer* twm_peer = FindPeer(voice_active_peer_); + if (twm_peer != nullptr && twm_peer->IsStreaming()) { + BTIF_TRACE_DEBUG("%s: voice_active_peer_ %s is streaming, send stop ", __func__, twm_peer->PeerAddress().ToString().c_str()); + btif_acm_initiator_dispatch_sm_event(voice_active_peer_, BTIF_ACM_STOP_STREAM_REQ_EVT); + } + } + } + + voice_active_peer_ = active_bda; + if (active_bda.address[0] == 0x9E && active_bda.address[1] == 0x8B && active_bda.address[2] == 0x00) { + BTIF_TRACE_DEBUG("%s: get set ID from group BD address ", __func__); + voice_active_setid_ = active_bda.address[5]; + } else { + BTIF_TRACE_DEBUG("%s: get set ID from peer data ", __func__); + if (peer != nullptr) + voice_active_setid_ = peer->SetId(); + } + peer_ready_promise.set_value(); + return true; + } else { + peer_ready_promise.set_value(); + return true; + } + } + + void btif_acm_initiator_encoder_user_config_update_req( + const RawAddress& peer_addr, + const std::vector& codec_user_preferences, + std::promise peer_ready_promise); + + + void UpdateCodecConfig( + const RawAddress& peer_address, + const std::vector& codec_preferences, + int contextType, + int profileType, + std::promise peer_ready_promise) { + // Restart the session if the codec for the active peer is updated + if (!peer_address.IsEmpty() && music_active_peer_ == peer_address) { + btif_acm_source_end_session(music_active_peer_); + } + + btif_acm_initiator_encoder_user_config_update_req( + peer_address, codec_preferences, std::move(peer_ready_promise)); + } + + const std::map& Peers() const { return peers_; } + // const std::map& SetPeers() const { return set_peers_; } + + std::vector locked_devices; + private: + void CleanupAllPeers(); + + btacm_initiator_callbacks_t* callbacks_; + bool enabled_; + int max_connected_peers_; + + RawAddress music_active_peer_; + RawAddress voice_active_peer_; + uint8_t music_active_setid_; + uint8_t voice_active_setid_; + uint8_t music_active_set_locked_dev_count_; + uint8_t voice_active_set_locked_dev_count_; + bool is_music_active_set_started_; + bool is_voice_active_set_started_; + bool is_conn_update_enabled_; + + uint8_t csip_app_id_; + bool is_csip_reg_; + uint8_t lock_flags_; + + alarm_t* music_set_lock_release_timer_; + alarm_t* voice_set_lock_release_timer_; + alarm_t* acm_group_procedure_timer_; + alarm_t* acm_conn_interval_timer_; + + + std::map peers_; + std::map addr_setid_pair; + std::map set_cig_pair;//setid and cig id pair + std::map > cig_cis_pair;//cig id and cis id pair + std::map set_lock_status_; +}; + + +/***************************************************************************** + * Static variables + *****************************************************************************/ +static BtifAcmInitiator btif_acm_initiator; +std::vector unicast_codecs_capabilities; +static CodecConfig acm_local_capability = + {CodecIndex::CODEC_INDEX_SOURCE_LC3, + CodecPriority::CODEC_PRIORITY_DEFAULT, + CodecSampleRate::CODEC_SAMPLE_RATE_48000, + CodecBPS::CODEC_BITS_PER_SAMPLE_24, + CodecChannelMode::CODEC_CHANNEL_MODE_STEREO, 0, 0, 0, 0}; +static CodecConfig default_config; +static bool mandatory_codec_selected = false; +static bt_status_t disconnect_acm_initiator(const RawAddress& peer_address, + uint16_t contextType); + +static bt_status_t start_stream_acm_initiator(const RawAddress& peer_address, + uint16_t contextType); +static bt_status_t stop_stream_acm_initiator(const RawAddress& peer_address, + uint16_t contextType); + +static void btif_acm_handle_csip_status_locked(std::vector addr, uint8_t setId); + +static void btif_acm_handle_evt(uint16_t event, char* p_param); +static void btif_report_connection_state(const RawAddress& peer_address, + btacm_connection_state_t state, uint16_t contextType); +static void btif_report_audio_state(const RawAddress& peer_address, + btacm_audio_state_t state, uint16_t contextType); + +static void btif_acm_check_and_start_lock_release_timer(uint8_t setId); + +static void btif_acm_initiator_lock_release_timer_timeout(void* data); + +static void btif_acm_check_and_start_group_procedure_timer(uint8_t setId); +static void btif_acm_check_and_start_conn_Interval_timer(BtifAcmPeer* peer); +static void btif_acm_initiator_conn_Interval_timer_timeout(void *data); +static void btif_acm_check_and_cancel_conn_Interval_timer(); + + +static void btif_acm_check_and_cancel_group_procedure_timer(uint8_t setId); +static void btif_acm_initiator_group_procedure_timer_timeout(void *data); +static void SelectCodecQosConfig(const RawAddress& bd_addr, int profile_type, + int context_type, int direction, int config_type); +bool compare_codec_config_(CodecConfig &first, CodecConfig &second); +void print_codec_parameters(CodecConfig config); +void print_qos_parameters(QosConfig qos_config); +void select_best_codec_config(const RawAddress& bd_addr, uint16_t context_type, + uint8_t profile_type, CodecConfig *codec_config, int dir, int config_type); +static UcastClientInterface* sUcastClientInterface = nullptr; + +/***************************************************************************** + * Local helper functions + *****************************************************************************/ + +const char* dump_acm_sm_event_name(btif_acm_sm_event_t event) { + switch ((int)event) { + CASE_RETURN_STR(BTA_ACM_DISCONNECT_EVT) + CASE_RETURN_STR(BTA_ACM_CONNECT_EVT) + CASE_RETURN_STR(BTA_ACM_START_EVT) + CASE_RETURN_STR(BTA_ACM_STOP_EVT) + CASE_RETURN_STR(BTA_ACM_RECONFIG_EVT) + CASE_RETURN_STR(BTA_ACM_CONFIG_EVT) + CASE_RETURN_STR(BTIF_ACM_CONNECT_REQ_EVT) + CASE_RETURN_STR(BTIF_ACM_DISCONNECT_REQ_EVT) + CASE_RETURN_STR(BTIF_ACM_START_STREAM_REQ_EVT) + CASE_RETURN_STR(BTIF_ACM_STOP_STREAM_REQ_EVT) + CASE_RETURN_STR(BTIF_ACM_SUSPEND_STREAM_REQ_EVT) + CASE_RETURN_STR(BTIF_ACM_RECONFIG_REQ_EVT) + CASE_RETURN_STR(BTA_ACM_CONN_UPDATE_TIMEOUT_EVT) + default: + return "UNKNOWN_EVENT"; + } +} + +const char* dump_csip_event_name(btif_csip_sm_event_t event) { + switch ((int)event) { + CASE_RETURN_STR(BTA_CSIP_NEW_SET_FOUND_EVT) + CASE_RETURN_STR(BTA_CSIP_SET_MEMBER_FOUND_EVT) + CASE_RETURN_STR(BTA_CSIP_CONN_STATE_CHG_EVT) + CASE_RETURN_STR(BTA_CSIP_LOCK_STATUS_CHANGED_EVT) + CASE_RETURN_STR(BTA_CSIP_LOCK_AVAILABLE_EVT) + CASE_RETURN_STR(BTA_CSIP_SET_SIZE_CHANGED) + CASE_RETURN_STR(BTA_CSIP_SET_SIRK_CHANGED) + default: + return "UNKNOWN_EVENT"; + } +} + +void btif_acm_signal_session_ready() { + std::unique_lock guard(acm_session_wait_mutex_); + if(!acm_session_wait) { + acm_session_wait = true; + acm_session_wait_cv.notify_all(); + } else { + BTIF_TRACE_WARNING("%s: already signalled ",__func__); + } +} + +void fetch_media_tx_codec_qos_config(const RawAddress& bd_addr, int profile_type, StreamConnect *conn_media) { + BTIF_TRACE_DEBUG("%s: Peer %s , profile_type: %d", __func__, bd_addr.ToString().c_str(), profile_type); + CodecQosConfig conf; + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(bd_addr); + if (peer == nullptr) { + BTIF_TRACE_WARNING("%s: peer is NULL", __func__); + return; + } + if (peer->IsStereoHsType()) { + //Stereo HS config 1 + SelectCodecQosConfig(peer->PeerAddress(), profile_type, MEDIA_CONTEXT, SNK, STEREO_HS_CONFIG_1); + conf = peer->get_peer_media_codec_qos_config(); + print_codec_parameters(conf.codec_config); + print_qos_parameters(conf.qos_config); + conn_media->codec_qos_config_pair.push_back(conf); + } else { + //EB config + SelectCodecQosConfig(peer->PeerAddress(), profile_type, MEDIA_CONTEXT, SNK, EB_CONFIG); + conf = peer->get_peer_media_codec_qos_config(); + print_codec_parameters(conf.codec_config); + print_qos_parameters(conf.qos_config); + conn_media->codec_qos_config_pair.push_back(conf); + } + conn_media->stream_type.type = CONTENT_TYPE_MEDIA; + conn_media->stream_type.audio_context = CONTENT_TYPE_MEDIA; + conn_media->stream_type.direction = ASE_DIRECTION_SINK; +} + +void fetch_media_rx_codec_qos_config(const RawAddress& bd_addr, int profile_type, StreamConnect *conn_media) { + BTIF_TRACE_DEBUG("%s: Peer %s , profile_type: %d", __func__, bd_addr.ToString().c_str(), profile_type); + CodecQosConfig conf; + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(bd_addr); + if (peer == nullptr) { + BTIF_TRACE_WARNING("%s: peer is NULL", __func__); + return; + } + if (peer->IsStereoHsType()) { + //Stereo HS config 1 + SelectCodecQosConfig(peer->PeerAddress(), WMCP, MEDIA_CONTEXT, SRC, STEREO_HS_CONFIG_1); + conf = peer->get_peer_media_codec_qos_config(); + print_codec_parameters(conf.codec_config); + print_qos_parameters(conf.qos_config); + conn_media->codec_qos_config_pair.push_back(conf); + } else { + //EB config + SelectCodecQosConfig(peer->PeerAddress(), WMCP, MEDIA_CONTEXT, SRC, EB_CONFIG); + conf = peer->get_peer_media_codec_qos_config(); + print_codec_parameters(conf.codec_config); + print_qos_parameters(conf.qos_config); + conn_media->codec_qos_config_pair.push_back(conf); + } + conn_media->stream_type.type = CONTENT_TYPE_MEDIA; + conn_media->stream_type.audio_context = CONTENT_TYPE_LIVE; //Live audio context + conn_media->stream_type.direction = ASE_DIRECTION_SRC; +} + +void fetch_voice_rx_codec_qos_config(const RawAddress& bd_addr, int profile_type, StreamConnect *conn_voice) { + BTIF_TRACE_DEBUG("%s: Peer %s , profile_type: %d", __func__, bd_addr.ToString().c_str(), profile_type); + CodecQosConfig conf; + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(bd_addr); + if (peer == nullptr) { + BTIF_TRACE_WARNING("%s: peer is NULL", __func__); + return; + } + if (peer->IsStereoHsType()) { + //Stereo HS config 1 + SelectCodecQosConfig(peer->PeerAddress(), BAP, VOICE_CONTEXT, SRC, STEREO_HS_CONFIG_1); + conf = peer->get_peer_voice_rx_codec_qos_config(); + print_codec_parameters(conf.codec_config); + print_qos_parameters(conf.qos_config); + conn_voice->codec_qos_config_pair.push_back(conf); + } else { + // EB config + SelectCodecQosConfig(peer->PeerAddress(), BAP, VOICE_CONTEXT, SRC, EB_CONFIG); + conf = peer->get_peer_voice_rx_codec_qos_config(); + print_codec_parameters(conf.codec_config); + print_qos_parameters(conf.qos_config); + conn_voice->codec_qos_config_pair.push_back(conf); + } + conn_voice->stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_voice->stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + conn_voice->stream_type.direction = ASE_DIRECTION_SRC; +} + +void fetch_voice_tx_codec_qos_config(const RawAddress& bd_addr, int profile_type, StreamConnect *conn_voice) { + BTIF_TRACE_DEBUG("%s: Peer %s , profile_type: %d", __func__, bd_addr.ToString().c_str(), profile_type); + CodecQosConfig conf; + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(bd_addr); + if (peer == nullptr) { + BTIF_TRACE_WARNING("%s: peer is NULL", __func__); + return; + } + if (peer->IsStereoHsType()) { + //Stereo HS config 1 + SelectCodecQosConfig(peer->PeerAddress(), BAP, VOICE_CONTEXT, SNK, STEREO_HS_CONFIG_1); + conf = peer->get_peer_voice_tx_codec_qos_config(); + print_codec_parameters(conf.codec_config); + print_qos_parameters(conf.qos_config); + conn_voice->codec_qos_config_pair.push_back(conf); + } else { + // EB config + SelectCodecQosConfig(peer->PeerAddress(), BAP, VOICE_CONTEXT, SNK, EB_CONFIG); + conf = peer->get_peer_voice_tx_codec_qos_config(); + print_codec_parameters(conf.codec_config); + print_qos_parameters(conf.qos_config); + conn_voice->codec_qos_config_pair.push_back(conf); + } + conn_voice->stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_voice->stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + conn_voice->stream_type.direction = ASE_DIRECTION_SINK; +} + +BtifAcmEvent::BtifAcmEvent(uint32_t event, const void* p_data, size_t data_length) + : event_(event), data_(nullptr), data_length_(0) { + DeepCopy(event, p_data, data_length); +} + +BtifAcmEvent::BtifAcmEvent(const BtifAcmEvent& other) + : event_(0), data_(nullptr), data_length_(0) { + *this = other; +} + +BtifAcmEvent& BtifAcmEvent::operator=(const BtifAcmEvent& other) { + DeepFree(); + DeepCopy(other.Event(), other.Data(), other.DataLength()); + return *this; +} + +BtifAcmEvent::~BtifAcmEvent() { DeepFree(); } + +std::string BtifAcmEvent::ToString() const { + return BtifAcmEvent::EventName(event_); +} + +std::string BtifAcmEvent::EventName(uint32_t event) { + std::string name = dump_acm_sm_event_name((btif_acm_sm_event_t)event); + std::stringstream ss_value; + ss_value << "(0x" << std::hex << event << ")"; + return name + ss_value.str(); +} + +void BtifAcmEvent::DeepCopy(uint32_t event, const void* p_data, + size_t data_length) { + event_ = event; + data_length_ = data_length; + if (data_length == 0) { + data_ = nullptr; + } else { + data_ = osi_malloc(data_length_); + memcpy(data_, p_data, data_length); + } +} + +void BtifAcmEvent::DeepFree() { + osi_free_and_reset((void**)&data_); + data_length_ = 0; +} + +BtifCsipEvent::BtifCsipEvent(uint32_t event, const void* p_data, size_t data_length) + : event_(event), data_(nullptr), data_length_(0) { + DeepCopy(event, p_data, data_length); +} + +BtifCsipEvent::BtifCsipEvent(const BtifCsipEvent& other) + : event_(0), data_(nullptr), data_length_(0) { + *this = other; +} + +BtifCsipEvent& BtifCsipEvent::operator=(const BtifCsipEvent& other) { + DeepFree(); + DeepCopy(other.Event(), other.Data(), other.DataLength()); + return *this; +} + +BtifCsipEvent::~BtifCsipEvent() { DeepFree(); } + +std::string BtifCsipEvent::ToString() const { + return BtifCsipEvent::EventName(event_); +} + +std::string BtifCsipEvent::EventName(uint32_t event) { + std::string name = dump_csip_event_name((btif_csip_sm_event_t)event); + std::stringstream ss_value; + ss_value << "(0x" << std::hex << event << ")"; + return name + ss_value.str(); +} + +void BtifCsipEvent::DeepCopy(uint32_t event, const void* p_data, + size_t data_length) { + event_ = event; + data_length_ = data_length; + if (data_length == 0) { + data_ = nullptr; + } else { + data_ = osi_malloc(data_length_); + memcpy(data_, p_data, data_length); + } +} + +void BtifCsipEvent::DeepFree() { + osi_free_and_reset((void**)&data_); + data_length_ = 0; +} + +BtifAcmPeer::BtifAcmPeer(const RawAddress& peer_address, uint8_t peer_sep, + uint8_t set_id, uint8_t cig_id, uint8_t cis_id) + : peer_address_(peer_address), + peer_sep_(peer_sep), + set_id_(set_id), + cig_id_(cig_id), + cis_id_(cis_id), + state_machine_(*this), + flags_(0) {} + +BtifAcmPeer::~BtifAcmPeer() { /*alarm_free(av_open_on_rc_timer_);*/ } + +std::string BtifAcmPeer::FlagsToString() const { + std::string result; + + if (flags_ & BtifAcmPeer::kFlagPendingLocalSuspend) { + if (!result.empty()) result += "|"; + result += "LOCAL_SUSPEND_PENDING"; + } + if (flags_ & BtifAcmPeer::kFlagPendingReconfigure) { + if (!result.empty()) result += "|"; + result += "PENDING_RECONFIGURE"; + } + if (flags_ & BtifAcmPeer::kFlagPendingStart) { + if (!result.empty()) result += "|"; + result += "PENDING_START"; + } + if (flags_ & BtifAcmPeer::kFlagPendingStop) { + if (!result.empty()) result += "|"; + result += "PENDING_STOP"; + } + if (flags_ & BtifAcmPeer::kFLagPendingStartAfterReconfig) { + if (!result.empty()) result += "|"; + result += "PENDING_START_AFTER_RECONFIG"; + } + if (result.empty()) result = "None"; + + return base::StringPrintf("0x%x(%s)", flags_, result.c_str()); +} + +bt_status_t BtifAcmPeer::Init() { + state_machine_.Start(); + return BT_STATUS_SUCCESS; +} + +void BtifAcmPeer::Cleanup() { + state_machine_.Quit(); +} + +bool BtifAcmPeer::CanBeDeleted() const { + return ( + (state_machine_.StateId() == BtifAcmStateMachine::kStateIdle) && + (state_machine_.PreviousStateId() != BtifAcmStateMachine::kStateInvalid)); +} + +const RawAddress& BtifAcmPeer::MusicActivePeerAddress() const { + return btif_acm_initiator.MusicActivePeer(); +} +const RawAddress& BtifAcmPeer::VoiceActivePeerAddress() const { + return btif_acm_initiator.VoiceActivePeer(); +} +uint8_t BtifAcmPeer::MusicActiveSetId() const { + return btif_acm_initiator.MusicActiveCSetId(); +} +uint8_t BtifAcmPeer::VoiceActiveSetId() const { + return btif_acm_initiator.VoiceActiveCSetId(); +} + +bool BtifAcmPeer::IsConnected() const { + int state = state_machine_.StateId(); + return ((state == BtifAcmStateMachine::kStateOpened) || + (state == BtifAcmStateMachine::kStateStarted)); +} + +bool BtifAcmPeer::IsStreaming() const { + int state = state_machine_.StateId(); + return (state == BtifAcmStateMachine::kStateStarted); +} + +BtifAcmInitiator::~BtifAcmInitiator() { + CleanupAllPeers(); +} + +void init_local_capabilities() { + unicast_codecs_capabilities.push_back(acm_local_capability); +} + +void BtifAcmInitiator::Cleanup() { + LOG_INFO(LOG_TAG, "%s", __PRETTY_FUNCTION__); + if (!enabled_) return; + std::promise peer_ready_promise; + btif_disable_service(BTA_ACM_INITIATOR_SERVICE_ID); // ACM deregistration required? + CleanupAllPeers(); + alarm_free(music_set_lock_release_timer_); + music_set_lock_release_timer_ = nullptr; + alarm_free(music_set_lock_release_timer_); + music_set_lock_release_timer_ = nullptr; + alarm_free(acm_group_procedure_timer_); + acm_group_procedure_timer_ = nullptr; + alarm_free(acm_conn_interval_timer_); + acm_conn_interval_timer_ = nullptr; + callbacks_ = nullptr; + enabled_ = false; + if (sUcastClientInterface != nullptr) { + sUcastClientInterface->Cleanup(); + sUcastClientInterface = nullptr; + } +} + +BtifAcmPeer* BtifAcmInitiator::FindPeer(const RawAddress& peer_address) { + auto it = peers_.find(peer_address); + if (it != peers_.end()) return it->second; + return nullptr; +} + +uint8_t BtifAcmInitiator:: FindPeerSetId(const RawAddress& peer_address) { + auto it = addr_setid_pair.find(peer_address); + if (it != addr_setid_pair.end()) return it->second; + return 0xff; +} + +uint8_t BtifAcmInitiator:: FindPeerBySetId(uint8_t setid) { + for (auto it : addr_setid_pair) { + if (it.second == setid) { + return setid; + } + } + return 0xff; +} + +uint8_t BtifAcmInitiator:: FindPeerCigId(uint8_t setid) { + auto it = set_cig_pair.find(setid); + if (it != set_cig_pair.end()) return it->second; + return 0xff; +} + +uint8_t BtifAcmInitiator:: FindPeerByCigId(uint8_t cigid) { + for (auto it : set_cig_pair) { + if (it.second == cigid) { + return cigid; + } + } + return 0xff; +} + +uint8_t BtifAcmInitiator:: FindPeerByCisId(uint8_t cigid, uint8_t cisid) { + for (auto itr = cig_cis_pair.begin(); itr != cig_cis_pair.end(); itr++) { + for (auto ptr = itr->second.begin(); ptr != itr->second.end(); ptr++) { + if (ptr->first == cigid) { + if (ptr->second == cisid) { + return cisid; + } + } + } + } + return 0xff; +} + +BtifAcmPeer* BtifAcmInitiator::FindOrCreatePeer(const RawAddress& peer_address) { + BTIF_TRACE_DEBUG("%s: peer_address=%s ", __PRETTY_FUNCTION__, + peer_address.ToString().c_str()); + + BtifAcmPeer* peer = FindPeer(peer_address); + if (peer != nullptr) return peer; + + uint8_t SetId, CigId, CisId; + //get the set id from CSIP. + //TODO: need UUID ? + Uuid uuid = Uuid::kEmpty; + LOG_INFO(LOG_TAG, "%s ACM UUID = %s", __func__, uuid.ToString().c_str()); + SetId = BTA_CsipGetDeviceSetId(peer_address, uuid); + BTIF_TRACE_EVENT("%s: set id from csip : %d", __func__, SetId); + if (SetId == INVALID_SET_ID) { + SetId = FindPeerSetId(peer_address); + // Find next available SET ID to use + if (SetId == 0xff) { + for (SetId = kPeerMinSetId; SetId < kPeerMaxSetId; SetId++) { + if (FindPeerBySetId(SetId) == 0xff) break; + } + } + } + if (SetId == kPeerMaxSetId) { + BTIF_TRACE_ERROR( + "%s: Cannot create peer for peer_address=%s : " + "cannot allocate unique SET ID", + __PRETTY_FUNCTION__, peer_address.ToString().c_str()); + return nullptr; + } + addr_setid_pair.insert(std::make_pair(peer_address, SetId)); + + //Find next available CIG ID to use + CigId = FindPeerCigId(SetId); + if (CigId == 0xff) { + for (CigId = kCigIdMin; CigId < kCigIdMax; ) { + if (FindPeerByCigId(CigId) == 0xff) break; + CigId += 4; + } + } + if (CigId == kCigIdMax) { + BTIF_TRACE_ERROR( + "%s: cannot allocate unique CIG ID to = %s ", + __func__, peer_address.ToString().c_str()); + return nullptr; + } + set_cig_pair.insert(std::make_pair(SetId, CigId)); + + //Find next available CIS ID to use + for (CisId = kCigIdMin; CisId < kCigIdMax; CisId++) { + if (FindPeerByCisId(CigId, CisId) == 0xff) break; + } + if (CisId == kCigIdMax) { + BTIF_TRACE_ERROR( + "%s: cannot allocate unique CIS ID to = %s ", + __func__, peer_address.ToString().c_str()); + return nullptr; + } + cig_cis_pair.insert(std::make_pair(peer_address, map())); + cig_cis_pair[peer_address].insert(std::make_pair(CigId, CisId)); + + LOG_INFO(LOG_TAG, + "%s: Create peer: peer_address=%s, set_id=%d, cig_id=%d, cis_id=%d", + __PRETTY_FUNCTION__, peer_address.ToString().c_str(), SetId, CigId, CisId); + peer = new BtifAcmPeer(peer_address, ACM_TSEP_SNK, SetId, CigId, CisId); + peer->SetPeerVoiceTxState(StreamState::DISCONNECTED); + peer->SetPeerVoiceRxState(StreamState::DISCONNECTED); + peer->SetPeerMusicTxState(StreamState::DISCONNECTED); + peer->SetPeerMusicRxState(StreamState::DISCONNECTED); + if (SetId >= kPeerMinSetId && SetId < kPeerMaxSetId) { + LOG_INFO(LOG_TAG, + "%s: Created peer is TWM device",__PRETTY_FUNCTION__); + peer->SetIsStereoHsType(true); + } + peers_.insert(std::make_pair(peer_address, peer)); + peer->Init(); + return peer; +} + +BtifAcmPeer* BtifAcmInitiator::FindMusicActivePeer() { + for (auto it : peers_) { + BtifAcmPeer* peer = it.second; + if (peer->IsPeerActiveForMusic()) { + return peer; + } + } + return nullptr; +} + +bool BtifAcmInitiator::AllowedToConnect(const RawAddress& peer_address) const { + int connected = 0; + + // Count peers that are in the process of connecting or already connected + for (auto it : peers_) { + const BtifAcmPeer* peer = it.second; + switch (peer->StateMachine().StateId()) { + case BtifAcmStateMachine::kStateOpening: + case BtifAcmStateMachine::kStateOpened: + case BtifAcmStateMachine::kStateStarted: + case BtifAcmStateMachine::kStateReconfiguring: + if (peer->PeerAddress() == peer_address) { + return true; // Already connected or accounted for + } + connected++; + break; + default: + break; + } + } + return (connected < max_connected_peers_); +} + +bool BtifAcmInitiator::IsAcmIdle() const { + int connected = 0; + + // Count peers that are in the process of connecting or already connected + for (auto it : peers_) { + const BtifAcmPeer* peer = it.second; + switch (peer->StateMachine().StateId()) { + case BtifAcmStateMachine::kStateOpening: + case BtifAcmStateMachine::kStateOpened: + case BtifAcmStateMachine::kStateStarted: + case BtifAcmStateMachine::kStateReconfiguring: + case BtifAcmStateMachine::kStateClosing: + connected++; + break; + default: + break; + } + } + return (connected == 0); +} + +bool BtifAcmInitiator::IsSetIdle(uint8_t setId) const { + int connected = 0; + tBTA_CSIP_CSET cset_info = BTA_CsipGetCoordinatedSet(setId); + std::vector::iterator itr; + if ((cset_info.set_members).size() > 0) { + for (itr = (cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(*itr); + switch (peer->StateMachine().StateId()) { + case BtifAcmStateMachine::kStateOpening: + case BtifAcmStateMachine::kStateOpened: + case BtifAcmStateMachine::kStateStarted: + case BtifAcmStateMachine::kStateReconfiguring: + case BtifAcmStateMachine::kStateClosing: + connected++; + break; + default: + break; + } + } + } + return (connected == 0); +} + +bool BtifAcmInitiator::IsOtherSetPeersIdle(const RawAddress& peer_address, uint8_t setId) const { + int connected = 0; + tBTA_CSIP_CSET cset_info = BTA_CsipGetCoordinatedSet(setId); + std::vector::iterator itr; + if ((cset_info.set_members).size() > 0) { + for (itr = (cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + if (*itr == peer_address) continue; + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(*itr); + if (peer == nullptr) continue; + switch (peer->StateMachine().StateId()) { + case BtifAcmStateMachine::kStateOpening: + case BtifAcmStateMachine::kStateOpened: + case BtifAcmStateMachine::kStateStarted: + case BtifAcmStateMachine::kStateReconfiguring: + case BtifAcmStateMachine::kStateClosing: + connected++; + break; + default: + break; + } + } + } + return (connected == 0); +} + +bool BtifAcmInitiator::DeletePeer(const RawAddress& peer_address) { + auto it = peers_.find(peer_address); + if (it == peers_.end()) return false; + BtifAcmPeer* peer = it->second; + for (auto itr = addr_setid_pair.begin(); itr != addr_setid_pair.end(); ++itr) { + if (itr->second == peer->SetId()) { + addr_setid_pair.erase(itr); + break; + } + } + for (auto itr = set_cig_pair.begin(); itr != set_cig_pair.end(); ++itr) { + if (itr->second == peer->SetId()) { + set_cig_pair.erase(itr); + break; + } + } + bool found = false; + for (auto itr = cig_cis_pair.begin(); itr != cig_cis_pair.end(); itr++) { + for (auto ptr = itr->second.begin(); ptr != itr->second.end(); ptr++) { + if (ptr->first == peer->CigId()) { + if (ptr->second == peer->CisId()) { + cig_cis_pair.erase(itr); + found = true; + break; + } + } + } + if (found) + break; + } + peer->Cleanup(); + peers_.erase(it); + delete peer; + return true; +} + +void BtifAcmInitiator::DeleteIdlePeers() { + for (auto it = peers_.begin(); it != peers_.end();) { + BtifAcmPeer* peer = it->second; + auto prev_it = it++; + if (!peer->CanBeDeleted()) continue; + LOG_INFO(LOG_TAG, "%s: Deleting idle peer: %s ", __func__, + peer->PeerAddress().ToString().c_str()); + for (auto itr = addr_setid_pair.begin(); itr != addr_setid_pair.end(); ++itr) { + if (itr->second == peer->SetId()) { + addr_setid_pair.erase(itr); + break; + } + } + for (auto itr = set_cig_pair.begin(); itr != set_cig_pair.end(); ++itr) { + if (itr->second == peer->SetId()) { + set_cig_pair.erase(itr); + break; + } + } + bool found = false; + for (auto itr = cig_cis_pair.begin(); itr != cig_cis_pair.end(); itr++) { + for (auto ptr = itr->second.begin(); ptr != itr->second.end(); ptr++) { + if (ptr->first == peer->CigId()) { + if (ptr->second == peer->CisId()) { + cig_cis_pair.erase(itr); + found = true; + break; + } + } + } + if (found) + break; + } + peer->Cleanup(); + peers_.erase(prev_it); + delete peer; + } +} + +void BtifAcmInitiator::CleanupAllPeers() { + while (!peers_.empty()) { + auto it = peers_.begin(); + BtifAcmPeer* peer = it->second; + for (auto itr = addr_setid_pair.begin(); itr != addr_setid_pair.end(); ++itr) { + if (itr->second == peer->SetId()) { + addr_setid_pair.erase(itr); + break; + } + } + for (auto itr = set_cig_pair.begin(); itr != set_cig_pair.end(); ++itr) { + if (itr->second == peer->SetId()) { + set_cig_pair.erase(itr); + break; + } + } + bool found = false; + for (auto itr = cig_cis_pair.begin(); itr != cig_cis_pair.end(); itr++) { + for (auto ptr = itr->second.begin(); ptr != itr->second.end(); ptr++) { + if (ptr->first == peer->CigId()) { + if (ptr->second == peer->CisId()) { + cig_cis_pair.erase(itr); + found = true; + break; + } + } + } + if (found) + break; + } + peer->Cleanup(); + peers_.erase(it); + delete peer; + } +} + +class UcastClientCallbacksImpl : public UcastClientCallbacks { + public: + ~UcastClientCallbacksImpl() = default; + void OnStreamState(const RawAddress& address, + std::vector streams_state_info) override { + LOG(INFO) << __func__; + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(address); + if (peer == nullptr) { + BTIF_TRACE_DEBUG("%s: Peer is NULL", __PRETTY_FUNCTION__); + } + for (auto it = streams_state_info.begin(); it != streams_state_info.end(); ++it) { + LOG(WARNING) << __func__ << ": address: " << address; + LOG(WARNING) << __func__ << ": stream type: " + << GetStreamType(it->stream_type.type); + LOG(WARNING) << __func__ << ": stream context: " + << GetStreamType(it->stream_type.audio_context); + LOG(WARNING) << __func__ << ": stream dir: " + << GetStreamDirection(it->stream_type.direction); + LOG(WARNING) << __func__ << ": stream state: " + << GetStreamState(static_cast (it->stream_state)); + switch (it->stream_state) { + case StreamState::DISCONNECTED: + case StreamState::DISCONNECTING: { + tBTA_ACM_STATE_INFO data = {.bd_addr = address, .stream_type = it->stream_type, + .stream_state = it->stream_state, .reason = it->reason}; + btif_acm_handle_evt(BTA_ACM_DISCONNECT_EVT, (char*)&data); + } break; + + case StreamState::CONNECTING: + case StreamState::CONNECTED: { + tBTA_ACM_STATE_INFO data = {.bd_addr = address, .stream_type = it->stream_type, + .stream_state = it->stream_state, .reason = it->reason}; + btif_acm_handle_evt(BTA_ACM_CONNECT_EVT, (char*)&data); + } break; + + case StreamState::STARTING: + case StreamState::STREAMING: { + tBTA_ACM_STATE_INFO data = {.bd_addr = address, .stream_type = it->stream_type, + .stream_state = it->stream_state, .reason = it->reason}; + btif_acm_handle_evt(BTA_ACM_START_EVT, (char*)&data); + } break; + + case StreamState::STOPPING: { + tBTA_ACM_STATE_INFO data = {.bd_addr = address, .stream_type = it->stream_type, + .stream_state = it->stream_state, .reason = it->reason}; + btif_acm_handle_evt(BTA_ACM_STOP_EVT, (char*)&data); + } break; + + case StreamState::RECONFIGURING: { + tBTA_ACM_STATE_INFO data = {.bd_addr = address, .stream_type = it->stream_type, + .stream_state = it->stream_state, .reason = it->reason}; + btif_acm_handle_evt(BTA_ACM_RECONFIG_EVT, (char*)&data); + } break; + default: + break; + } + } + } + + void OnStreamConfig(const RawAddress& address, + std::vector streams_config_info) override { + LOG(INFO) << __func__; + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(address); + if (peer == nullptr) { + BTIF_TRACE_DEBUG("%s: Peer is NULL", __PRETTY_FUNCTION__); + } + for (auto it = streams_config_info.begin(); it != streams_config_info.end(); ++it) { + tBTA_ACM_CONFIG_INFO data = {.bd_addr = address, .stream_type = it->stream_type, + .codec_config = it->codec_config, .audio_location = it->audio_location, + .qos_config = it->qos_config, .codecs_selectable = it->codecs_selectable}; + btif_acm_handle_evt(BTA_ACM_CONFIG_EVT, (char*)&data); + } + } + + void OnStreamAvailable(const RawAddress& bd_addr, uint16_t src_audio_contexts, + uint16_t sink_audio_contexts) override { + LOG(INFO) << __func__; + //Need to use during START of src and sink audio context + BTIF_TRACE_DEBUG("%s: Peer %s, src_audio_context: 0x%x, sink_audio_contexts: 0x%x", + __func__, + bd_addr.ToString().c_str(), src_audio_contexts, sink_audio_contexts); + } + + const char* GetStreamType(uint16_t stream_type) { + switch (stream_type) { + CASE_RETURN_STR(CONTENT_TYPE_UNSPECIFIED) + CASE_RETURN_STR(CONTENT_TYPE_CONVERSATIONAL) + CASE_RETURN_STR(CONTENT_TYPE_MEDIA) + CASE_RETURN_STR(CONTENT_TYPE_INSTRUCTIONAL) + CASE_RETURN_STR(CONTENT_TYPE_NOTIFICATIONS) + CASE_RETURN_STR(CONTENT_TYPE_ALERT) + CASE_RETURN_STR(CONTENT_TYPE_MAN_MACHINE) + CASE_RETURN_STR(CONTENT_TYPE_EMERGENCY) + CASE_RETURN_STR(CONTENT_TYPE_RINGTONE) + CASE_RETURN_STR(CONTENT_TYPE_SOUND_EFFECTS) + CASE_RETURN_STR(CONTENT_TYPE_LIVE) + CASE_RETURN_STR(CONTENT_TYPE_GAME) + default: + return "Unknown StreamType"; + } + } + + const char* GetStreamDirection(uint8_t event) { + switch (event) { + CASE_RETURN_STR(ASE_DIRECTION_SINK) + CASE_RETURN_STR(ASE_DIRECTION_SRC) + default: + return "Unknown StreamDirection"; + } + } + + const char* GetStreamState(uint8_t event) { + switch (event) { + CASE_RETURN_STR(STREAM_STATE_DISCONNECTED) + CASE_RETURN_STR(STREAM_STATE_CONNECTING) + CASE_RETURN_STR(STREAM_STATE_CONNECTED) + CASE_RETURN_STR(STREAM_STATE_STARTING) + CASE_RETURN_STR(STREAM_STATE_STREAMING) + CASE_RETURN_STR(STREAM_STATE_STOPPING) + CASE_RETURN_STR(STREAM_STATE_DISCONNECTING) + CASE_RETURN_STR(STREAM_STATE_RECONFIGURING) + default: + return "Unknown StreamState"; + } + } +}; + +static UcastClientCallbacksImpl sUcastClientCallbacks; + +bt_status_t BtifAcmInitiator::Init( + btacm_initiator_callbacks_t* callbacks, int max_connected_acceptors, + const std::vector& codec_priorities) { + LOG_INFO(LOG_TAG, "%s: max_connected_acceptors=%d", __PRETTY_FUNCTION__, + max_connected_acceptors); + if (enabled_) return BT_STATUS_SUCCESS; + CleanupAllPeers(); + max_connected_peers_ = max_connected_acceptors; + alarm_free(music_set_lock_release_timer_); + alarm_free(voice_set_lock_release_timer_); + alarm_free(acm_group_procedure_timer_); + alarm_free(acm_conn_interval_timer_); + music_set_lock_release_timer_ = alarm_new("btif_acm_initiator.music_set_lock_release_timer"); + voice_set_lock_release_timer_ = alarm_new("btif_acm_initiator.voice_set_lock_release_timer"); + acm_group_procedure_timer_ = alarm_new("btif_acm_initiator.acm_group_procedure_timer"); + acm_conn_interval_timer_ = alarm_new("btif_acm_initiator.acm_conn_interval_timer"); + + callbacks_ = callbacks; + //init local capabilties + init_local_capabilities(); + + // register ACM with AHIM + btif_register_cb(); + + btif_vmcp_init(); + bt_status_t status1 = btif_acm_initiator_execute_service(true); + if (status1 == BT_STATUS_SUCCESS) { + BTIF_TRACE_EVENT("%s: status success", __func__); + } + if (sUcastClientInterface != nullptr) { + LOG_INFO(LOG_TAG, "%s Cleaning up BAP client Interface before initializing...", + __PRETTY_FUNCTION__); + sUcastClientInterface->Cleanup(); + sUcastClientInterface = nullptr; + } + sUcastClientInterface = bluetooth::bap::ucast::btif_bap_uclient_get_interface(); + + + if (sUcastClientInterface == nullptr) { + LOG_ERROR(LOG_TAG, "%s Failed to get BAP Interface", __PRETTY_FUNCTION__); + return BT_STATUS_FAIL; + } + char value[PROPERTY_VALUE_MAX]; + if(property_get("persist.vendor.service.bt.bap.conn_update", value, "false") + && !strcmp(value, "true")) { + is_conn_update_enabled_ = true; + } else { + is_conn_update_enabled_ = false; + } + sUcastClientInterface->Init(&sUcastClientCallbacks); + enabled_ = true; + return BT_STATUS_SUCCESS; +} + +void BtifAcmStateMachine::StateIdle::OnEnter() { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str()); + if(btif_acm_initiator.IsConnUpdateEnabled()) { + if ((peer_.StateMachine().PreviousStateId() == BtifAcmStateMachine::kStateOpened) || + (peer_.StateMachine().PreviousStateId() == BtifAcmStateMachine::kStateStarted)) + { + if (alarm_is_scheduled(btif_acm_initiator.AcmConnIntervalTimer())) { + btif_acm_check_and_cancel_conn_Interval_timer(); + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagRelaxedMode); + } else { + LOG_ERROR(LOG_TAG, "%s Already in relaxed intervals", __PRETTY_FUNCTION__); + } + } else if (peer_.StateMachine().PreviousStateId() != BtifAcmStateMachine::kStateInvalid) { + if (alarm_is_scheduled(btif_acm_initiator.AcmConnIntervalTimer())) { + btif_acm_check_and_cancel_conn_Interval_timer(); + } + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagRelaxedMode); + } + } + peer_.ClearConnUpdateMode(); + peer_.ClearAllFlags(); + peer_.SetProfileType(0); + peer_.SetRcfgProfileType(0); + memset(¤t_media_config, 0, sizeof(current_media_config)); + + // Delete peers that are re-entering the Idle state + if (peer_.IsAcceptor()) { + do_in_bta_thread(FROM_HERE, base::Bind(&BtifAcmInitiator::DeleteIdlePeers, + base::Unretained(&btif_acm_initiator))); + } +} + +void BtifAcmStateMachine::StateIdle::OnExit() { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str()); +} + +bool BtifAcmStateMachine::StateIdle::ProcessEvent(uint32_t event, void* p_data) { + BTIF_TRACE_DEBUG("%s: Peer %s : event=%s flags=%s music_active_peer=%s voice_active_peer=%s", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str(), + logbool(peer_.IsPeerActiveForMusic()).c_str(), + logbool(peer_.IsPeerActiveForVoice()).c_str()); + + switch (event) { + case BTIF_ACM_STOP_STREAM_REQ_EVT: + case BTIF_ACM_SUSPEND_STREAM_REQ_EVT: + break; +#if 0 + case BTIF_ACM_DISCONNECT_REQ_EVT: { + tBTIF_ACM_CONN_DISC* p_bta_data = (tBTIF_ACM_CONN_DISC*)p_data; + std::vector disconnect_streams; + if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC) { + StreamType type_1; + if (peer_.GetProfileType() & (BAP|GCP)) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_1); + } + if (peer_.GetProfileType() & WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + disconnect_streams.push_back(type_1); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC_VOICE) { + StreamType type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + StreamType type_3 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_2); + disconnect_streams.push_back(type_3); + StreamType type_1; + if (peer_.GetProfileType() & (BAP|GCP)) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_1); + } + if (peer_.GetProfileType() & WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + disconnect_streams.push_back(type_1); + } + } + LOG(WARNING) << __func__ << " size of disconnect_streams " << disconnect_streams.size(); + if (!sUcastClientInterface) break; + sUcastClientInterface->Disconnect(peer_.PeerAddress(), disconnect_streams); + + // Re-enter Idle so the peer can be deleted + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } + break; +#endif + + case BTIF_ACM_CONNECT_REQ_EVT: { + tBTIF_ACM_CONN_DISC* p_bta_data = (tBTIF_ACM_CONN_DISC*)p_data; + bool can_connect = true; + // Check whether connection is allowed + if (peer_.IsAcceptor()) { + //There is no char in current spec. Should we check VMCP role here? + // shall we assume VMCP role would have been checked in apps and no need to check here? + can_connect = btif_acm_initiator.AllowedToConnect(peer_.PeerAddress()); + if (!can_connect) disconnect_acm_initiator(peer_.PeerAddress(), p_bta_data->contextType); + } + if (!can_connect) { + BTIF_TRACE_ERROR( + "%s: Cannot connect to peer %s: too many connected " + "peers", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str()); + break; + } + std::vector streams; + if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC) { + StreamConnect conn_media; + if (peer_.GetProfileType() & (BAP|GCP)) { + //keeping media tx as BAP/GCP config + memset(&conn_media, 0, sizeof(conn_media)); + fetch_media_tx_codec_qos_config(peer_.PeerAddress(), peer_.GetProfileType() & (BAP|GCP), &conn_media); + streams.push_back(conn_media); +#if 0 + if (false) {//enable when GCP support is available + SelectCodecQosConfig(peer_.PeerAddress(), (peer_.GetProfileType() & ~WMCP), VOICE_CONTEXT, SRC, EB_CONFIG); + StreamConnect conn_voice; + CodecQosConfig config; + conn_voice.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_voice.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + config = peer_.get_peer_voice_rx_codec_qos_config(); + print_codec_parameters(config.codec_config); + print_qos_parameters(config.qos_config); + conn_voice.stream_type.direction = ASE_DIRECTION_SRC; + conn_voice.codec_qos_config_pair.push_back(config); + streams.push_back(conn_voice); + } +#endif + } + if (peer_.GetProfileType() & WMCP) { + //keeping media rx as WMCP config + memset(&conn_media, 0, sizeof(conn_media)); + fetch_media_rx_codec_qos_config(peer_.PeerAddress(), WMCP, &conn_media); + streams.push_back(conn_media); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC_VOICE) { + StreamConnect conn_media, conn_voice; + //keeping voice tx as BAP config + memset(&conn_voice, 0, sizeof(conn_voice)); + fetch_voice_tx_codec_qos_config(peer_.PeerAddress(), BAP, &conn_voice); + streams.push_back(conn_voice); + //keeping voice rx as BAP config + memset(&conn_voice, 0, sizeof(conn_voice)); + fetch_voice_rx_codec_qos_config(peer_.PeerAddress(), BAP, &conn_voice); + streams.push_back(conn_voice); + if (peer_.GetProfileType() & (BAP|GCP)) { + //keeping media tx as BAP/GCP config + memset(&conn_media, 0, sizeof(conn_media)); + fetch_media_tx_codec_qos_config(peer_.PeerAddress(), peer_.GetProfileType() & (BAP|GCP), &conn_media); + streams.push_back(conn_media); + } + if (peer_.GetProfileType() & WMCP) { + //keeping media rx as WMCP config + memset(&conn_media, 0, sizeof(conn_media)); + fetch_media_rx_codec_qos_config(peer_.PeerAddress(), WMCP, &conn_media); + streams.push_back(conn_media); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_VOICE) { + StreamConnect conn_voice; + //keeping voice tx as BAP config + memset(&conn_voice, 0, sizeof(conn_voice)); + fetch_voice_tx_codec_qos_config(peer_.PeerAddress(), BAP, &conn_voice); + streams.push_back(conn_voice); + //keeping voice rx as BAP config + memset(&conn_voice, 0, sizeof(conn_voice)); + fetch_voice_rx_codec_qos_config(peer_.PeerAddress(), BAP, &conn_voice); + streams.push_back(conn_voice); + } + LOG(WARNING) << __func__ << " size of streams " << streams.size(); + if (!sUcastClientInterface) break; + // intiate background connection + std::vector address; + address.push_back(peer_.PeerAddress()); + sUcastClientInterface->Connect(address, false, streams); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpening); + } break; +#if 0 + case BTA_ACM_DISCONNECT_EVT: { + tBTIF_ACM* p_acm = (tBTIF_ACM*)p_data; + int context_type = p_acm->state_info.stream_type.type; + if (p_acm->state_info.stream_state == StreamState::DISCONNECTED) { + if (context_type == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + BTIF_TRACE_DEBUG("%s: received Media Rx disconnected state from BAP, set state & ignore", __func__); + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + } + } else if (p_acm->state_info.stream_state == StreamState::DISCONNECTING) { + if (context_type == CONTENT_TYPE_MEDIA) { + BTIF_TRACE_DEBUG("%s: received Media disconnecting state from BAP, ignore", __func__); + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + } + } break; +#endif + + case BTA_ACM_CONN_UPDATE_TIMEOUT_EVT: + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagRelaxedMode); + break; + + default: + BTIF_TRACE_WARNING("%s: Peer %s : Unhandled event=%s", + __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str()); + return false; + } + + return true; +} + +void BtifAcmStateMachine::StateOpening::OnEnter() { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str()); + + if(btif_acm_initiator.IsConnUpdateEnabled()) { + //Cancel the timer if start streamng comes before + // 5 seconds while moving the interval to relaxed mode. + if (alarm_is_scheduled(btif_acm_initiator.AcmConnIntervalTimer())) { + btif_acm_check_and_cancel_conn_Interval_timer(); + } + else { + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagAggresiveMode); + } + } + +} + +void BtifAcmStateMachine::StateOpening::OnExit() { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str()); +} + +bool BtifAcmStateMachine::StateOpening::ProcessEvent(uint32_t event, void* p_data) { + BTIF_TRACE_DEBUG("%s: Peer %s : event=%s flags=%s music_active_peer=%s voice_active_peer=%s", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str(), + logbool(peer_.IsPeerActiveForMusic()).c_str(), + logbool(peer_.IsPeerActiveForVoice()).c_str()); + + switch (event) { + case BTIF_ACM_STOP_STREAM_REQ_EVT: + case BTIF_ACM_SUSPEND_STREAM_REQ_EVT: + break; // Ignore + + case BTA_ACM_CONNECT_EVT: { + tBTIF_ACM* p_bta_data = (tBTIF_ACM*)p_data; + btacm_connection_state_t state; + uint8_t status = (uint8_t)p_bta_data->state_info.stream_state; + uint16_t contextType = p_bta_data->state_info.stream_type.type; + + LOG_INFO( + LOG_TAG, "%s: Peer %s : event=%s flags=%s status=%d contextType=%d", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), peer_.FlagsToString().c_str(), + status, contextType); + if (contextType == CONTENT_TYPE_MEDIA) { + if (p_bta_data->state_info.stream_state == StreamState::CONNECTED) { + state = BTACM_CONNECTION_STATE_CONNECTED; + // Report the connection state to the application + btif_report_connection_state(peer_.PeerAddress(), state, CONTEXT_TYPE_MUSIC); + if (p_bta_data->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerMusicTxState(p_bta_data->state_info.stream_state); + BTIF_TRACE_DEBUG("%s: received connected state from BAP for mediaTx, move in opened state", __func__); + } else if (p_bta_data->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerMusicRxState(p_bta_data->state_info.stream_state); + BTIF_TRACE_DEBUG("%s: received connected state from BAP for mediaRx, move in opened state", __func__); + } + // Change state to OPENED + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } else if (p_bta_data->state_info.stream_state == StreamState::CONNECTING) { + BTIF_TRACE_DEBUG("%s: received connecting state from BAP for MEDIA Tx or Rx, ignore", __func__); + if (p_bta_data->state_info.stream_type.direction == ASE_DIRECTION_SINK) + peer_.SetPeerMusicTxState(p_bta_data->state_info.stream_state); + else if (p_bta_data->state_info.stream_type.direction == ASE_DIRECTION_SRC) + peer_.SetPeerMusicRxState(p_bta_data->state_info.stream_state); + } + } else if (contextType == CONTENT_TYPE_CONVERSATIONAL) { + if (p_bta_data->state_info.stream_state == StreamState::CONNECTED) { + state = BTACM_CONNECTION_STATE_CONNECTED; + if (p_bta_data->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_bta_data->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::CONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), state, CONTEXT_TYPE_VOICE); + BTIF_TRACE_DEBUG("%s: received connected state from BAP for voice Tx, move in opened state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } + } else if (p_bta_data->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_bta_data->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::CONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), state, CONTEXT_TYPE_VOICE); + BTIF_TRACE_DEBUG("%s: received connected state from BAP for voice Rx, move in opened state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } + } + } else if (p_bta_data->state_info.stream_state == StreamState::CONNECTING) { + BTIF_TRACE_DEBUG("%s: received connecting state from BAP for CONVERSATIONAL Tx or Rx, ignore", __func__); + if (p_bta_data->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_bta_data->state_info.stream_state); + } else if (p_bta_data->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_bta_data->state_info.stream_state); + } + } + } + } break; + + case BTA_ACM_DISCONNECT_EVT: { + tBTIF_ACM* p_acm = (tBTIF_ACM*)p_data; + int context_type = p_acm->state_info.stream_type.type; + if (p_acm->state_info.stream_state == StreamState::DISCONNECTED) { + if (context_type == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_MUSIC); + } + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED && + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnected state from BAP" + " when Voice Tx+Rx & Media Rx/Tx was disconnected, move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnected state from BAP" + " when either Voice Tx or Rx or Media Rx/Tx is connecting, remain in opening state", __func__); + } + } else if (context_type == CONTENT_TYPE_CONVERSATIONAL) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Tx," + " voice Rx, music Tx+Rx are disconnected move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else if (peer_.GetPeerMusicTxState() == StreamState::CONNECTING || + peer_.GetPeerMusicRxState() == StreamState::CONNECTING) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Tx," + " voice Rx is disconnected but either music Tx or Rx still connecting," + " remain in opening state", __func__); + } + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Tx, music Tx+Rx are disconnected move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else if (peer_.GetPeerMusicTxState() == StreamState::CONNECTING || + peer_.GetPeerMusicRxState() == StreamState::CONNECTING) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Tx is disconnected but music Tx or Rx still connecting," + " remain in opening state", __func__); + } + } + } + } + } else if (p_acm->state_info.stream_state == StreamState::DISCONNECTING) { + if (context_type == CONTENT_TYPE_MEDIA) { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for MEDIA Tx or Rx, ignore", __func__); + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_MUSIC); + } else if (context_type == CONTENT_TYPE_CONVERSATIONAL) { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for CONVERSATIONAL Tx or Rx, ignore", __func__); + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + } + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_VOICE); + } + } + } + break; + + case BTIF_ACM_DISCONNECT_REQ_EVT:{ + tBTIF_ACM_CONN_DISC* p_bta_data = (tBTIF_ACM_CONN_DISC*)p_data; + std::vector disconnect_streams; + if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC) { + StreamType type_1; + if (p_bta_data->profileType & (BAP|GCP)) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_1); + } + if (p_bta_data->profileType & WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + disconnect_streams.push_back(type_1); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC_VOICE) { + StreamType type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + StreamType type_3 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_3); + disconnect_streams.push_back(type_2); + StreamType type_1; + if (p_bta_data->profileType & (BAP|GCP)) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_1); + } + if (p_bta_data->profileType & WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + disconnect_streams.push_back(type_1); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_VOICE) { + StreamType type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + StreamType type_3 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_3); + disconnect_streams.push_back(type_2); + } + LOG(WARNING) << __func__ << " size of disconnect_streams " << disconnect_streams.size(); + if (!sUcastClientInterface) break; + sUcastClientInterface->Disconnect(peer_.PeerAddress(), disconnect_streams); + + if ((p_bta_data->contextType == CONTEXT_TYPE_MUSIC) && ((peer_.GetPeerVoiceRxState() == StreamState::CONNECTING) || + (peer_.GetPeerVoiceTxState() == StreamState::CONNECTING))) { + LOG(WARNING) << __func__ << " voice connecting remain in opening "; + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_MUSIC); + } else if ((p_bta_data->contextType == CONTEXT_TYPE_VOICE) && (peer_.GetPeerMusicTxState() == StreamState::CONNECTING || + (peer_.GetPeerMusicRxState() == StreamState::CONNECTING))) { + LOG(WARNING) << __func__ << " Music connecting remain in opening "; + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + } else { + LOG(WARNING) << __func__ << " Move in idle state "; + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_MUSIC_VOICE); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } + } + break; + + case BTIF_ACM_CONNECT_REQ_EVT: { + BTIF_TRACE_WARNING( + "%s: Peer %s : event=%s : device is already connecting, " + "ignore Connect request", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str()); + } break; + + case BTA_ACM_CONFIG_EVT: { + tBTIF_ACM* p_acm_data = (tBTIF_ACM*)p_data; + uint16_t contextType = p_acm_data->state_info.stream_type.type; + uint16_t peer_latency_ms = 0; + uint32_t presen_delay = 0; + bool is_update_require = false; + if (contextType == CONTENT_TYPE_MEDIA) { + if (p_acm_data->state_info.stream_type.audio_context == CONTENT_TYPE_MEDIA) { + BTIF_TRACE_DEBUG("%s: compare with current media config", __PRETTY_FUNCTION__); + is_update_require = compare_codec_config_(current_media_config, p_acm_data->config_info.codec_config); + } else if (p_acm_data->state_info.stream_type.audio_context == CONTENT_TYPE_LIVE) { + BTIF_TRACE_DEBUG("%s: cache current_recording_config", __PRETTY_FUNCTION__); + current_recording_config = p_acm_data->config_info.codec_config; + } + if (mandatory_codec_selected) { + BTIF_TRACE_DEBUG("%s: Mandatory codec selected, do not store config", __PRETTY_FUNCTION__); + } else { + BTIF_TRACE_DEBUG("%s: store configuration", __PRETTY_FUNCTION__); + } + //Cache the peer latency in WMCP case + if (p_acm_data->state_info.stream_type.audio_context == CONTENT_TYPE_LIVE) { + BTIF_TRACE_DEBUG("%s: presentation delay[0] = %x", __func__, + p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[0]); + BTIF_TRACE_DEBUG("%s: presentation delay[1] = %x", __func__, + p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[1]); + BTIF_TRACE_DEBUG("%s: presentation delay[2] = %x", __func__, + p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[2]); + presen_delay = static_cast(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[0]) | + static_cast(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[1] << 8) | + static_cast(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[2] << 16); + BTIF_TRACE_DEBUG("%s: presen_delay = %dus", __func__, presen_delay); + peer_latency_ms = presen_delay/1000; + BTIF_TRACE_DEBUG("%s: s_to_m latency = %dms", __func__, + p_acm_data->config_info.qos_config.cig_config.max_tport_latency_s_to_m); + peer_latency_ms += p_acm_data->config_info.qos_config.cig_config.max_tport_latency_s_to_m; + peer_.SetPeerLatency(peer_latency_ms); + BTIF_TRACE_DEBUG("%s: cached peer Latency = %dms", __func__, peer_.GetPeerLatency()); + } + if (is_update_require) { + current_media_config = p_acm_data->config_info.codec_config; + BTIF_TRACE_DEBUG("%s: current_media_config.codec_specific_3: %" + PRIi64, __func__, current_media_config.codec_specific_3); + btif_acm_update_lc3q_params(¤t_media_config.codec_specific_3, p_acm_data); + btif_acm_report_source_codec_state(peer_.PeerAddress(), current_media_config, + unicast_codecs_capabilities, + unicast_codecs_capabilities, CONTEXT_TYPE_MUSIC); + } + } else if (contextType == CONTENT_TYPE_CONVERSATIONAL && + p_acm_data->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + BTIF_TRACE_DEBUG("%s: cache current_voice_config", __PRETTY_FUNCTION__); + current_voice_config = p_acm_data->config_info.codec_config; + BTIF_TRACE_DEBUG("%s: current_voice_config.codec_specific_3: %" + PRIi64, __func__, current_voice_config.codec_specific_3); + btif_acm_update_lc3q_params(¤t_voice_config.codec_specific_3, p_acm_data); + btif_acm_report_source_codec_state(peer_.PeerAddress(), current_voice_config, + unicast_codecs_capabilities, + unicast_codecs_capabilities, CONTEXT_TYPE_VOICE); + } + //Handle BAP START if reconfig comes in mid of streaming + //peer_.SetStreamReconfigInfo(p_acm->acm_reconfig); + //TODO: local capabilities + //CodecConfig record = p_bta_data->acm_reconfig.codec_config; + //saving codec config as negotiated parameter as true + //btif_pacs_add_record(peer_.PeerAddress(), true, CodecDirection::CODEC_DIR_SRC, &record); + + } break; + + case BTA_ACM_CONN_UPDATE_TIMEOUT_EVT: + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagRelaxedMode); + break; + + default: + BTIF_TRACE_WARNING("%s: Peer %s : Unhandled event=%s", + __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str()); + return false; + } + return true; +} + +bool btif_peer_device_is_streaming(uint8_t Id) { + bool is_streaming = false; + tBTA_CSIP_CSET cset_info; + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(Id); + if (cset_info.size == 0) { + BTIF_TRACE_ERROR("%s: CSET info size is zero, return", __func__); + return false; + } + std::vector::iterator itr; + BTIF_TRACE_DEBUG("%s: size of set members %d", __func__, (cset_info.set_members).size()); + if ((cset_info.set_members).size() > 0) { + for (itr =(cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(*itr); + if (peer != nullptr && (peer->IsStreaming() || peer->CheckFlags(BtifAcmPeer::kFlagPendingStart)) && + !peer->CheckFlags(BtifAcmPeer::kFlagPendingLocalSuspend)) { + BTIF_TRACE_DEBUG("%s: fellow device is streaming %s ", __func__, peer->PeerAddress().ToString().c_str()); + is_streaming = true; + break; + } + } + } + return is_streaming; +} + +bool btif_peer_device_is_reconfiguring(uint8_t Id) { + bool is_reconfigured = false; + if (Id < INVALID_SET_ID) { + tBTA_CSIP_CSET cset_info; + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(Id); + if (cset_info.size == 0) { + BTIF_TRACE_ERROR("%s: CSET info size is zero, return", __func__); + return false; + } + std::vector::iterator itr; + BTIF_TRACE_DEBUG("%s: size of set members %d", __func__, (cset_info.set_members).size()); + if ((cset_info.set_members).size() > 0) { + for (itr =(cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(*itr); + if (peer != nullptr && peer->CheckFlags(BtifAcmPeer::kFlagPendingReconfigure)) { + BTIF_TRACE_DEBUG("%s: peer is reconfiguring %s ", __func__, peer->PeerAddress().ToString().c_str()); + is_reconfigured = true; + break; + } + } + } + } else { + is_reconfigured = true; + BTIF_TRACE_ERROR("%s: peer is TWM device, return is_reconfigured %d", __func__, is_reconfigured); + } + return is_reconfigured; +} + +void BtifAcmStateMachine::StateOpened::OnEnter() { + BTIF_TRACE_DEBUG("%s: Peer %s, Peer SetId = %d, MusicActiveSetId = %d, ContextType = %d", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), peer_.SetId(), + btif_acm_initiator.MusicActiveCSetId(), peer_.GetContextType()); + + //Starting the timer for 5 seconds before moving to relaxed state as + //stop event or start streaming event moght immediately come + //which requires aggresive interval + if(btif_acm_initiator.IsConnUpdateEnabled()) { + btif_acm_check_and_start_conn_Interval_timer(&peer_); + } + peer_.ClearFlags(BtifAcmPeer::kFlagPendingLocalSuspend | + BtifAcmPeer::kFlagPendingStart | + BtifAcmPeer::kFlagPendingStop); + + BTIF_TRACE_DEBUG("%s: kFlagPendingReconfigure %d and kFLagPendingStartAfterReconfig %d", __PRETTY_FUNCTION__, + peer_.CheckFlags(BtifAcmPeer::kFlagPendingReconfigure), + peer_.CheckFlags(BtifAcmPeer::kFLagPendingStartAfterReconfig)); + + if (peer_.CheckFlags(BtifAcmPeer::kFlagPendingReconfigure)) { + peer_.ClearFlags(BtifAcmPeer::kFlagPendingReconfigure); + if ((peer_.GetRcfgProfileType() != BAP_CALL) && + (current_active_profile_type != peer_.GetRcfgProfileType())) { + current_active_profile_type = peer_.GetRcfgProfileType(); + if (current_active_profile_type != WMCP) + current_active_config = current_media_config; + else + current_active_config = current_recording_config; + + if (btif_peer_device_is_reconfiguring(peer_.SetId())) + btif_acm_source_restart_session(active_bda, active_bda); + + if (current_active_profile_type == WMCP) { + uint16_t sink_latency = btif_acm_get_active_device_latency(); + BTIF_TRACE_EVENT("%s: sink_latency = %dms", __func__, sink_latency); + if ((sink_latency > 0) && !btif_acm_update_sink_latency_change(sink_latency * 10)) { + BTIF_TRACE_ERROR("%s: unable to update latency", __func__); + } + } + if (current_active_profile_type == BAP) { + peer_.ResetProfileType(GCP); + peer_.SetProfileType(BAP); + } else if (current_active_profile_type == GCP) { + peer_.ResetProfileType(BAP); + peer_.SetProfileType(GCP); + } + BTIF_TRACE_DEBUG("%s: cummulative_profile_type %d", __func__, peer_.GetProfileType()); + BTIF_TRACE_DEBUG("%s: Reconfig + restart session completed for media, signal session ready", __func__); + btif_acm_signal_session_ready(); + } else if (current_active_profile_type == peer_.GetRcfgProfileType()) { + BTIF_TRACE_DEBUG("%s: Reconfig to remote is completed for media, restart session wasn't needed", __func__); + } else { + BTIF_TRACE_DEBUG("%s: Reconfig completed for BAP_CALL", __func__); + } + } + //Start the lock release timer here. + //check if peer device is in started state + if (btif_peer_device_is_streaming(peer_.SetId()) || + peer_.CheckFlags(BtifAcmPeer::kFLagPendingStartAfterReconfig)) { + StreamType type_1, type_2; + std::vector start_streams; + if (peer_.GetRcfgProfileType() != BAP_CALL) { + if ((current_active_profile_type == BAP || current_active_profile_type == GCP) && + (peer_.GetPeerMusicTxState() == StreamState::CONNECTED)) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + start_streams.push_back(type_1); + } else if ((current_active_profile_type == WMCP) && + (peer_.GetPeerMusicRxState() == StreamState::CONNECTED)) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + start_streams.push_back(type_1); + } + } else { + if ((peer_.GetPeerVoiceTxState() == StreamState::CONNECTED) && + (peer_.GetPeerVoiceRxState() == StreamState::CONNECTED)) { + type_1 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + start_streams.push_back(type_1); + start_streams.push_back(type_2); + } + } + + if(btif_acm_initiator.IsConnUpdateEnabled()) { + //Cancel the timer if start streamng comes before + // 5 seconds while moving the interval to relaxed mode. + if (alarm_is_scheduled(btif_acm_initiator.AcmConnIntervalTimer())) { + btif_acm_check_and_cancel_conn_Interval_timer(); + } else { + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagAggresiveMode); + } + } + sUcastClientInterface->Start(peer_.PeerAddress(), start_streams); + peer_.SetFlags(BtifAcmPeer::kFlagPendingStart); + peer_.ClearFlags(BtifAcmPeer::kFLagPendingStartAfterReconfig); + } + peer_.SetRcfgProfileType(0); + + if (peer_.StateMachine().PreviousStateId() == BtifAcmStateMachine::kStateStarted) { + BTIF_TRACE_DEBUG("%s: Entering Opened from Started State", __PRETTY_FUNCTION__); + if ((btif_acm_initiator.GetGroupLockStatus(peer_.SetId()) != + BtifAcmInitiator::kFlagStatusUnknown) && + alarm_is_scheduled(btif_acm_initiator.AcmGroupProcedureTimer())) { + BTIF_TRACE_DEBUG("%s: All locked and stop/suspend requested device have stopped, ack mm audio", __func__); + btif_acm_check_and_cancel_group_procedure_timer(btif_acm_initiator.MusicActiveCSetId()); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_SUCCESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_SUCCESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + btif_acm_check_and_start_lock_release_timer(btif_acm_initiator.MusicActiveCSetId()); + } + } + + if (peer_.StateMachine().PreviousStateId() == BtifAcmStateMachine::kStateStarted) { + if ((btif_acm_initiator.MusicActiveCSetId() > 0) && + (btif_acm_initiator.GetGroupLockStatus(btif_acm_initiator.MusicActiveCSetId()) == BtifAcmInitiator::kFlagStatusLocked)) { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str()); + btif_acm_check_and_start_lock_release_timer(btif_acm_initiator.MusicActiveCSetId()); + } + } +} + +void BtifAcmStateMachine::StateOpened::OnExit() { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str()); + + peer_.ClearFlags(BtifAcmPeer::kFlagPendingStart); +} + +bool BtifAcmStateMachine::StateOpened::ProcessEvent(uint32_t event, + void* p_data) { + tBTIF_ACM* p_acm = (tBTIF_ACM*)p_data; + + BTIF_TRACE_DEBUG("%s: Peer %s : event=%s flags=%s music_active_peer=%s voice_active_peer=%s", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str(), + logbool(peer_.IsPeerActiveForMusic()).c_str(), + logbool(peer_.IsPeerActiveForVoice()).c_str()); + + switch (event) { + case BTIF_ACM_CONNECT_REQ_EVT: { + tBTIF_ACM_CONN_DISC* p_bta_data = (tBTIF_ACM_CONN_DISC*)p_data; + bool can_connect = true; + // Check whether connection is allowed + if (peer_.IsAcceptor()) { + //There is no char in current spec. Should we check VMCP role here? + // shall we assume VMCP role would have been checked in apps and no need to check here? + can_connect = btif_acm_initiator.AllowedToConnect(peer_.PeerAddress()); + if (!can_connect) disconnect_acm_initiator(peer_.PeerAddress(), p_bta_data->contextType); + } + if (!can_connect) { + BTIF_TRACE_ERROR( + "%s: Cannot connect to peer %s: too many connected " + "peers", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str()); + break; + } + std::vector streams; + if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC) { + StreamConnect conn_media; + if (peer_.GetProfileType() & (BAP|GCP)) { + //keeping media tx as BAP/GCP config + memset(&conn_media, 0, sizeof(conn_media)); + fetch_media_tx_codec_qos_config(peer_.PeerAddress(), peer_.GetProfileType() & (BAP|GCP), &conn_media); + streams.push_back(conn_media); +#if 0 + if (false) {//enable when GCP support is available + SelectCodecQosConfig(peer_.PeerAddress(), (peer_.GetProfileType() & ~WMCP), VOICE_CONTEXT, SRC, EB_CONFIG); + StreamConnect conn_voice; + CodecQosConfig config; + conn_voice.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_voice.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + config = peer_.get_peer_voice_rx_codec_qos_config(); + print_codec_parameters(config.codec_config); + print_qos_parameters(config.qos_config); + conn_voice.stream_type.direction = ASE_DIRECTION_SRC; + conn_voice.codec_qos_config_pair.push_back(config); + streams.push_back(conn_voice); + } +#endif + } + if (peer_.GetProfileType() & WMCP) { + //keeping media rx as WMCP config + memset(&conn_media, 0, sizeof(conn_media)); + fetch_media_rx_codec_qos_config(peer_.PeerAddress(), WMCP, &conn_media); + streams.push_back(conn_media); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC_VOICE) { + StreamConnect conn_media, conn_voice; + //keeping voice tx as BAP config + memset(&conn_voice, 0, sizeof(conn_voice)); + fetch_voice_tx_codec_qos_config(peer_.PeerAddress(), BAP, &conn_voice); + streams.push_back(conn_voice); + //keeping voice rx as BAP config + memset(&conn_voice, 0, sizeof(conn_voice)); + fetch_voice_rx_codec_qos_config(peer_.PeerAddress(), BAP, &conn_voice); + streams.push_back(conn_voice); + if (peer_.GetProfileType() & (BAP|GCP)) { + //keeping media tx as BAP/GCP config + memset(&conn_media, 0, sizeof(conn_media)); + fetch_media_tx_codec_qos_config(peer_.PeerAddress(), peer_.GetProfileType() & (BAP|GCP), &conn_media); + streams.push_back(conn_media); + } + if (peer_.GetProfileType() & WMCP) { + //keeping media rx as WMCP config + memset(&conn_media, 0, sizeof(conn_media)); + fetch_media_rx_codec_qos_config(peer_.PeerAddress(), WMCP, &conn_media); + streams.push_back(conn_media); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_VOICE) { + StreamConnect conn_voice; + //keeping voice tx as BAP config + memset(&conn_voice, 0, sizeof(conn_voice)); + fetch_voice_tx_codec_qos_config(peer_.PeerAddress(), BAP, &conn_voice); + streams.push_back(conn_voice); + //keeping voice rx as BAP config + memset(&conn_voice, 0, sizeof(conn_voice)); + fetch_voice_rx_codec_qos_config(peer_.PeerAddress(), BAP, &conn_voice); + streams.push_back(conn_voice); + } + LOG(WARNING) << __func__ << " size of streams " << streams.size(); + if (!sUcastClientInterface) break; + std::vector address; + address.push_back(peer_.PeerAddress()); + sUcastClientInterface->Connect(address, false, streams); + //peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpening); + } break; + + case BTIF_ACM_STOP_STREAM_REQ_EVT: + case BTIF_ACM_SUSPEND_STREAM_REQ_EVT: { + BTIF_TRACE_DEBUG("%s: Already in OPENED state, ACK success", __PRETTY_FUNCTION__); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_SUCCESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_SUCCESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + } break; + + case BTIF_ACM_START_STREAM_REQ_EVT: { + LOG_INFO(LOG_TAG, "%s: Peer %s : event=%s flags=%s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str()); + if (peer_.CheckFlags(BtifAcmPeer::kFlagPendingStart)) { + BTIF_TRACE_DEBUG("%s: Ignore Start req", __PRETTY_FUNCTION__); + break; + } +#if 0 + //Can be either music or voice, prior to coming here, + //this must have been evaluated for locking logic + grp logic + StreamType type_1; + std::vector start_streams; + if (current_active_profile_type != WMCP) { + if (peer_.GetStreamContextType() == CONTEXT_TYPE_MUSIC) { + StreamType type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + start_streams.push_back(type_1); + } else if (peer_.GetStreamContextType() == CONTEXT_TYPE_VOICE) { + StreamType type_1 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + StreamType type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + start_streams.push_back(type_1); + start_streams.push_back(type_2); + LOG_INFO(LOG_TAG, "%s: sending start for voice###", __PRETTY_FUNCTION__); + } + } else { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + start_streams.push_back(type_1); + } + + if(btif_acm_initiator.IsConnUpdateEnabled()) { + //Cancel the timer if start streamng comes before + // 5 seconds while moving the interval to relaxed mode. + if (alarm_is_scheduled(btif_acm_initiator.AcmConnIntervalTimer())) { + btif_acm_check_and_cancel_conn_Interval_timer(); + } else { + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagAggresiveMode); + } + } + if (!sUcastClientInterface) break; + sUcastClientInterface->Start(peer_.PeerAddress(), start_streams); +#endif +#if 1 + if (peer_.GetStreamContextType() == CONTEXT_TYPE_MUSIC) { + reconfig_acm_initiator(peer_.PeerAddress(), current_active_profile_type); + } else if (peer_.GetStreamContextType() == CONTEXT_TYPE_VOICE) { + reconfig_acm_initiator(peer_.PeerAddress(), BAP_CALL); + } + peer_.SetFlags(BtifAcmPeer::kFLagPendingStartAfterReconfig); +#endif + } + break; + + case BTA_ACM_START_EVT: { + tBTIF_ACM_STATUS status = (uint8_t)p_acm->state_info.stream_state; + //int contextType = p_acm->state_info.stream_type.type; + LOG_INFO(LOG_TAG, + "%s: Peer %s : event=%s status=%d ", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(),status); + + if (p_acm->state_info.stream_state == StreamState::STARTING) { + //Check what to do in this case + BTIF_TRACE_DEBUG("%s: BAP returned as starting, ignore", __PRETTY_FUNCTION__); + break; + } else if (p_acm->state_info.stream_state == StreamState::STREAMING){ + peer_.ClearFlags(BtifAcmPeer::kFlagPendingStart); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateStarted); + } + } break; + + case BTIF_ACM_DISCONNECT_REQ_EVT:{ + tBTIF_ACM_CONN_DISC* p_bta_data = (tBTIF_ACM_CONN_DISC*)p_data; + std::vector disconnect_streams; + if (peer_.CheckFlags(BtifAcmPeer::kFlagPendingStart)) { + peer_.ClearFlags(BtifAcmPeer::kFlagPendingStart); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } + } + if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC) { + StreamType type_1; + if (p_bta_data->profileType & (BAP|GCP)) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_1); + } + if (p_bta_data->profileType & WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + disconnect_streams.push_back(type_1); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC_VOICE) { + StreamType type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + StreamType type_3 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_3); + disconnect_streams.push_back(type_2); + StreamType type_1; + if (p_bta_data->profileType & (BAP|GCP)) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_1); + } + if (p_bta_data->profileType & WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + disconnect_streams.push_back(type_1); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_VOICE) { + StreamType type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + StreamType type_3 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_3); + disconnect_streams.push_back(type_2); + } + LOG(WARNING) << __func__ << " size of disconnect_streams " << disconnect_streams.size(); + if (!sUcastClientInterface) break; + sUcastClientInterface->Disconnect(peer_.PeerAddress(), disconnect_streams); + + if ((p_bta_data->contextType == CONTEXT_TYPE_MUSIC) && ((peer_.GetPeerVoiceRxState() == StreamState::CONNECTED) || + (peer_.GetPeerVoiceTxState() == StreamState::CONNECTED))) { + LOG(WARNING) << __func__ << " voice connected remain in opened "; + } else if ((p_bta_data->contextType == CONTEXT_TYPE_VOICE) && ((peer_.GetPeerMusicTxState() == StreamState::CONNECTED) || + (peer_.GetPeerMusicRxState() == StreamState::CONNECTED))) { + LOG(WARNING) << __func__ << " Music connected remain in opened "; + } else { + LOG(WARNING) << __func__ << " Move in closing state "; + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } + } break; + + case BTA_ACM_STOP_EVT: { //Sumit: what is this case? + int contextType = p_acm->acm_connect.streams_info.stream_type.type; + btif_report_audio_state(peer_.PeerAddress(), BTACM_AUDIO_STATE_STOPPED, peer_.GetStreamContextType()); + if (contextType == CONTENT_TYPE_MEDIA) + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } break; + + case BTA_ACM_CONNECT_EVT: {// above evnt can come and handle for voice/media case + tBTIF_ACM_STATUS status = (uint8_t)p_acm->state_info.stream_state; + int contextType = p_acm->state_info.stream_type.type; + + LOG_INFO( + LOG_TAG, "%s: Peer %s : event=%s status=%d", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(),status); + if (peer_.CheckFlags(BtifAcmPeer::kFlagPendingLocalSuspend)) { + peer_.ClearFlags(BtifAcmPeer::kFlagPendingLocalSuspend); + BTIF_TRACE_DEBUG("%s: peer device is suspended, send MM any pending ACK", __func__); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_SUCCESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_SUCCESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + break; + } + if (p_acm->state_info.stream_state == StreamState::CONNECTED) { + if (contextType == CONTENT_TYPE_MEDIA) { + if ((btif_acm_initiator.MusicActivePeer() == peer_.PeerAddress()) && + peer_.CheckFlags(BtifAcmPeer::kFlagPendingReconfigure)) { //recheck + LOG(INFO) << __PRETTY_FUNCTION__ << " : Peer " << peer_.PeerAddress() + << " : Reconfig done - calling startSession() to audio HAL"; + std::promise peer_ready_promise; + std::future peer_ready_future = peer_ready_promise.get_future(); + //TODO: cannot use peer addr here, must need group address. + btif_acm_source_start_session(peer_.PeerAddress()); + //Perform group operation here + } else if (((peer_.GetPeerVoiceRxState() == StreamState::CONNECTED) || + (peer_.GetPeerVoiceTxState() == StreamState::CONNECTED) || + (peer_.GetPeerMusicRxState() == StreamState::CONNECTED)) && + (peer_.GetPeerMusicTxState() == StreamState::CONNECTING) && + (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK)) { + BTIF_TRACE_DEBUG("%s: music Tx connected when either Voice Tx/Rx or Music Rx was connected," + "remain in opened state", __func__); + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + BTIF_TRACE_DEBUG("%s: received connected state from BAP for Music TX, update state", __func__); + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_CONNECTED, CONTEXT_TYPE_MUSIC); + } else if ((peer_.GetPeerMusicRxState() == StreamState::CONNECTING) && + (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC)) { + BTIF_TRACE_DEBUG("%s: received connected state from BAP for Music RX(recording), update state", __func__); + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_CONNECTED, CONTEXT_TYPE_MUSIC); + } +#if 0 + if (peer_.CheckFlags(BtifAcmPeer::kFlagPendingStart)) { + LOG(INFO) << __PRETTY_FUNCTION__ << " : Peer " << peer_.PeerAddress() + << " : Reconfig done - calling BTA_AvStart()"; + StreamType type_1; + if (current_active_profile_type != WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + } else { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + } + std::vector start_streams; + start_streams.push_back(type_1); + if (!sUcastClientInterface) break; + sUcastClientInterface->Start(peer_.PeerAddress(), start_streams); + } +#endif + } else if (contextType == CONTENT_TYPE_CONVERSATIONAL) { + BTIF_TRACE_DEBUG("%s: voice context connected, remain in opened state" + " peer_.GetPeerVoiceTxState() %d peer_.GetPeerVoiceRxState() %d", + __func__, peer_.GetPeerVoiceTxState(), peer_.GetPeerVoiceRxState()); + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK && + (peer_.GetPeerVoiceTxState() != StreamState::CONNECTED)) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::CONNECTED) { + BTIF_TRACE_DEBUG("%s: received connected state from BAP for voice TX, update state", __func__); + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_CONNECTED, CONTEXT_TYPE_VOICE); + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC && + (peer_.GetPeerVoiceRxState() != StreamState::CONNECTED)) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::CONNECTED) { + BTIF_TRACE_DEBUG("%s: received connected state from BAP for voice RX, update state", __func__); + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_CONNECTED, CONTEXT_TYPE_VOICE); + } + } + } + } else if (p_acm->state_info.stream_state == StreamState::CONNECTING){ + if (contextType == CONTENT_TYPE_MEDIA) { + BTIF_TRACE_DEBUG("%s: received connecting state from BAP for MEDIA Tx or Rx, ignore", __func__); + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } else if (contextType == CONTENT_TYPE_CONVERSATIONAL) { + BTIF_TRACE_DEBUG("%s: received connecting state from BAP for CONVERSATIONAL Tx or Rx, ignore", __func__); + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + } + } + } + } break; + + case BTA_ACM_DISCONNECT_EVT: { + int context_type = p_acm->state_info.stream_type.type; + if (p_acm->state_info.stream_state == StreamState::DISCONNECTED) { + if (context_type == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_MUSIC); + } + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED && + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnected state from BAP" + " when Voice Tx+Rx & Media Rx/Tx was disconnected, move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnected state from BAP" + " when either Voice Tx or Rx or Media Rx/Tx is connected, remain in opened state", __func__); + } + } else if (context_type == CONTENT_TYPE_CONVERSATIONAL) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Tx," + " voice Rx, Music Tx & Rx are disconnected move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Tx," + " voice Rx is disconnected but music Tx or Rx still not disconnected," + " remain in opened state", __func__); + } + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Tx, Music Tx & Rx are disconnected move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Rx is disconnected but music Tx or Rx still not disconnected," + " remain in opened state", __func__); + } + } + } + } + } else if (p_acm->state_info.stream_state == StreamState::DISCONNECTING) { + if (context_type == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_MUSIC); + if ((peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED || + peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTING) && + (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED || + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTING) && + (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED || + peer_.GetPeerMusicTxState() == StreamState::DISCONNECTING) && + (peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED || + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTING)) { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnecting state from BAP" + " when Voice Tx+Rx and Media Rx/Tx disconnected/ing, move in closing state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } else { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnecting state from BAP" + " when either Voice Tx or Rx or Media Rx/Tx is connected, remain in opened state", __func__); + } + } else if (context_type == CONTENT_TYPE_CONVERSATIONAL) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTING || + peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_VOICE); + if (((peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTING)) && + ((peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicRxState() == StreamState::DISCONNECTING))) { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for voice Tx," + " voice Rx, music Tx+Rx are disconnected/ing move in closing state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } else { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for voice Tx," + " voice Rx is disconncted/ing but music Tx or Rx still not disconnected/ing," + " remain in opened state", __func__); + } + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTING || + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_VOICE); + if (((peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTING)) && + ((peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicRxState() == StreamState::DISCONNECTING))) { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for voice Rx," + " voice Tx, music Tx+Rx are disconnected/ing move in closing state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Tx is disconncted/ing but music Tx or Rx still not disconnected/ing," + " remain in opened state", __func__); + } + } + } + } + } + } + break; + + case BTIF_ACM_RECONFIG_REQ_EVT: { + std::vector reconf_streams; + StreamReconfig reconf_info; + CodecQosConfig cfg; + if (p_acm->acm_reconfig.streams_info.stream_type.type != CONTENT_TYPE_CONVERSATIONAL) { + reconf_info.stream_type.type = p_acm->acm_reconfig.streams_info.stream_type.type; + reconf_info.stream_type.audio_context = + p_acm->acm_reconfig.streams_info.stream_type.audio_context; + reconf_info.stream_type.direction = p_acm->acm_reconfig.streams_info.stream_type.direction; + reconf_info.reconf_type = p_acm->acm_reconfig.streams_info.reconf_type; + cfg = peer_.get_peer_media_codec_qos_config(); + reconf_info.codec_qos_config_pair.push_back(cfg); + reconf_streams.push_back(reconf_info); + } else { + reconf_info.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + reconf_info.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + reconf_info.stream_type.direction = ASE_DIRECTION_SRC; + reconf_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + if (peer_.IsStereoHsType()) { + SelectCodecQosConfig(peer_.PeerAddress(), BAP, VOICE_CONTEXT, SRC, STEREO_HS_CONFIG_1); + } else { + SelectCodecQosConfig(peer_.PeerAddress(), BAP, VOICE_CONTEXT, SRC, EB_CONFIG); + } + cfg = peer_.get_peer_voice_rx_codec_qos_config(); + print_codec_parameters(cfg.codec_config); + print_qos_parameters(cfg.qos_config); + reconf_info.codec_qos_config_pair.push_back(cfg); + reconf_streams.push_back(reconf_info); + + reconf_info.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + reconf_info.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + reconf_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + if (peer_.IsStereoHsType()) { + SelectCodecQosConfig(peer_.PeerAddress(), BAP, VOICE_CONTEXT, SRC, STEREO_HS_CONFIG_1); + } else { + SelectCodecQosConfig(peer_.PeerAddress(), BAP, VOICE_CONTEXT, SRC, EB_CONFIG); + } + cfg = peer_.get_peer_voice_tx_codec_qos_config(); + print_codec_parameters(cfg.codec_config); + print_qos_parameters(cfg.qos_config); + reconf_info.codec_qos_config_pair.push_back(cfg); + reconf_streams.push_back(reconf_info); + + peer_.SetPeerVoiceRxState(StreamState::RECONFIGURING); + peer_.SetPeerVoiceTxState(StreamState::RECONFIGURING); + } + if (!sUcastClientInterface) break; + sUcastClientInterface->Reconfigure(peer_.PeerAddress(), reconf_streams); + peer_.SetFlags(BtifAcmPeer::kFlagPendingReconfigure); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateReconfiguring); + } + break; + case BTA_ACM_CONFIG_EVT: { + tBTIF_ACM* p_acm_data = (tBTIF_ACM*)p_data; + uint16_t contextType = p_acm_data->state_info.stream_type.type; + uint16_t peer_latency_ms = 0; + uint32_t presen_delay = 0; + bool is_update_require = false; + if (contextType == CONTENT_TYPE_MEDIA) { + if (p_acm_data->state_info.stream_type.audio_context == CONTENT_TYPE_MEDIA) { + BTIF_TRACE_DEBUG("%s: compare with current media config", __PRETTY_FUNCTION__); + is_update_require = compare_codec_config_(current_media_config, p_acm_data->config_info.codec_config); + } else if (p_acm_data->state_info.stream_type.audio_context == CONTENT_TYPE_LIVE) { + BTIF_TRACE_DEBUG("%s: cache current_recording_config", __PRETTY_FUNCTION__); + current_recording_config = p_acm_data->config_info.codec_config; + } + if (mandatory_codec_selected) { + BTIF_TRACE_DEBUG("%s: Mandatory codec selected, do not store config", __PRETTY_FUNCTION__); + } else { + BTIF_TRACE_DEBUG("%s: store configuration", __PRETTY_FUNCTION__); + } + //Cache the peer latency in WMCP case + if (p_acm_data->state_info.stream_type.audio_context == CONTENT_TYPE_LIVE) { + BTIF_TRACE_DEBUG("%s: presentation delay[0] = %x", __func__, + p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[0]); + BTIF_TRACE_DEBUG("%s: presentation delay[1] = %x", __func__, + p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[1]); + BTIF_TRACE_DEBUG("%s: presentation delay[2] = %x", __func__, + p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[2]); + presen_delay = static_cast(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[0]) | + static_cast(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[1] << 8) | + static_cast(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[2] << 16); + BTIF_TRACE_DEBUG("%s: presen_delay = %dus", __func__, presen_delay); + peer_latency_ms = presen_delay/1000; + BTIF_TRACE_DEBUG("%s: s_to_m latency = %dms", __func__, + p_acm_data->config_info.qos_config.cig_config.max_tport_latency_s_to_m); + peer_latency_ms += p_acm_data->config_info.qos_config.cig_config.max_tport_latency_s_to_m; + peer_.SetPeerLatency(peer_latency_ms); + BTIF_TRACE_DEBUG("%s: cached peer Latency = %dms", __func__, peer_.GetPeerLatency()); + } + if (is_update_require) { + current_media_config = p_acm_data->config_info.codec_config; + BTIF_TRACE_DEBUG("%s: current_media_config.codec_specific_3: %" + PRIi64, __func__, current_media_config.codec_specific_3); + btif_acm_update_lc3q_params(¤t_media_config.codec_specific_3, p_acm_data); + btif_acm_report_source_codec_state(peer_.PeerAddress(), current_media_config, + unicast_codecs_capabilities, + unicast_codecs_capabilities, CONTEXT_TYPE_MUSIC); + } + } else if (contextType == CONTENT_TYPE_CONVERSATIONAL && + p_acm_data->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + BTIF_TRACE_DEBUG("%s: cache current_voice_config", __PRETTY_FUNCTION__); + current_voice_config = p_acm_data->config_info.codec_config; + BTIF_TRACE_DEBUG("%s: current_voice_config.codec_specific_3: %" + PRIi64, __func__, current_voice_config.codec_specific_3); + btif_acm_update_lc3q_params(¤t_voice_config.codec_specific_3, p_acm_data); + btif_acm_report_source_codec_state(peer_.PeerAddress(), current_voice_config, + unicast_codecs_capabilities, + unicast_codecs_capabilities, CONTEXT_TYPE_VOICE); + } + //Handle BAP START if reconfig comes in mid of streaming + //peer_.SetStreamReconfigInfo(p_acm->acm_reconfig); + //TODO: local capabilities + //CodecConfig record = p_bta_data->acm_reconfig.codec_config; + //saving codec config as negotiated parameter as true + //btif_pacs_add_record(peer_.PeerAddress(), true, CodecDirection::CODEC_DIR_SRC, &record); + + } break; + + case BTA_ACM_CONN_UPDATE_TIMEOUT_EVT: + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagRelaxedMode); + break; + + default: + BTIF_TRACE_WARNING("%s: Peer %s : Unhandled event=%s", + __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str()); + return false; + } + return true; +} + +bool btif_acm_check_if_requested_devices_started() { + std::vector::iterator itr; + if ((btif_acm_initiator.locked_devices).size() > 0) { + for (itr = (btif_acm_initiator.locked_devices).begin(); itr != (btif_acm_initiator.locked_devices).end(); itr++) { + BTIF_TRACE_DEBUG("%s: address =%s", __func__, *itr->ToString().c_str()); + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(*itr); + if ((peer == nullptr) || (peer != nullptr && !peer->IsStreaming())) { + break; + } + } + if (itr == (btif_acm_initiator.locked_devices).end()) { + return true; + } + } + return false; +} + +bool btif_acm_check_if_requested_devices_stopped() { + std::vector::iterator itr; + if ((btif_acm_initiator.locked_devices).size() > 0) { + for (itr = (btif_acm_initiator.locked_devices).begin(); itr != (btif_acm_initiator.locked_devices).end(); itr++) { + BTIF_TRACE_DEBUG("%s: address =%s", __func__, *itr->ToString().c_str()); + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(*itr); + if ((peer == nullptr) || (peer != nullptr /*&& !peer->IsSuspended()*/)) { + break; + } + } + if (itr == (btif_acm_initiator.locked_devices).end()) { + return true; + } + } + return false; +} + +void BtifAcmStateMachine::StateStarted::OnEnter() { + BTIF_TRACE_DEBUG("%s: Peer %s, Peer SetId = %d, MusicActiveSetId = %d, ContextType = %d", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + peer_.SetId(), btif_acm_initiator.MusicActiveCSetId(), peer_.GetContextType()); + + if(btif_acm_initiator.IsConnUpdateEnabled()) { + //Starting the timer for 5 seconds before moving to relaxed state as + //stop event or start streaming event moght immediately come + //which requires aggresive interval + btif_acm_check_and_start_conn_Interval_timer(&peer_); + } + + // Report that we have entered the Streaming stage. Usually, this should + // be followed by focus grant. See update_audio_focus_state() + btif_report_audio_state(peer_.PeerAddress(), BTACM_AUDIO_STATE_STARTED, peer_.GetStreamContextType()); + if (alarm_is_scheduled(btif_acm_initiator.AcmGroupProcedureTimer())) { + btif_acm_check_and_cancel_group_procedure_timer(btif_acm_initiator.MusicActiveCSetId()); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_SUCCESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_SUCCESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + } else { + BTIF_TRACE_DEBUG("%s:no group procedure timer running ACK pending cmd", __func__); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_SUCCESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_SUCCESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + } +#if 0 + if ((btif_acm_initiator.GetGroupLockStatus(peer_.SetId()) != BtifAcmInitiator::kFlagStatusUnknown) && + alarm_is_scheduled(btif_acm_initiator.AcmGroupProcedureTimer())) { + BTIF_TRACE_DEBUG("%s: All locked and start requested device have started, ack mm audio", __func__); + //in this case, we need to change channel mode to stereo + btif_acm_check_and_cancel_group_procedure_timer(btif_acm_initiator.MusicActiveCSetId()); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_SUCCESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_SUCCESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + btif_acm_check_and_start_lock_release_timer(btif_acm_initiator.MusicActiveCSetId()); + } + + //Start the lock release timer here. + if ((btif_acm_initiator.MusicActiveCSetId() != INVALID_SET_ID) && + (btif_acm_initiator.GetGroupLockStatus(btif_acm_initiator.MusicActiveCSetId()) == BtifAcmInitiator::kFlagStatusLocked)) { + BTIF_TRACE_DEBUG("%s: ", __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str()); + btif_acm_check_and_start_lock_release_timer(btif_acm_initiator.MusicActiveCSetId()); + } + if (!btif_acm_initiator.IsMusicActiveGroupStarted()) { + if (peer_.SetId() == btif_acm_initiator.MusicActiveCSetId()) + btif_acm_initiator.SetMusicActiveGroupStarted(true); + } +#endif + +} + +void BtifAcmStateMachine::StateStarted::OnExit() { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str()); +} + +bool BtifAcmStateMachine::StateStarted::ProcessEvent(uint32_t event, void* p_data) { + tBTIF_ACM* p_acm = (tBTIF_ACM*)p_data; + + BTIF_TRACE_DEBUG("%s: Peer %s : event=%s flags=%s music_active_peer=%s voice_active_peer=%s", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str(), + logbool(peer_.IsPeerActiveForMusic()).c_str(), + logbool(peer_.IsPeerActiveForVoice()).c_str()); + + switch (event) { + case BTIF_ACM_STOP_STREAM_REQ_EVT: + case BTIF_ACM_SUSPEND_STREAM_REQ_EVT: { + LOG_INFO(LOG_TAG, "%s: Peer %s : event=%s flags=%s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str()); + peer_.SetFlags(BtifAcmPeer::kFlagPendingLocalSuspend); + + StreamType type_1; + std::vector stop_streams; + if (peer_.GetStreamContextType() == CONTEXT_TYPE_MUSIC) { + if (current_active_profile_type != WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + stop_streams.push_back(type_1); + } else { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + stop_streams.push_back(type_1); + } + } else if (peer_.GetStreamContextType() == CONTEXT_TYPE_VOICE) { + StreamType type_2; + type_1 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + stop_streams.push_back(type_2); + stop_streams.push_back(type_1); + } + if(btif_acm_initiator.IsConnUpdateEnabled()) { + //Cancel the timer if start streamng comes before + // 5 seconds while moving the interval to relaxed mode. + if (alarm_is_scheduled(btif_acm_initiator.AcmConnIntervalTimer())) { + btif_acm_check_and_cancel_conn_Interval_timer(); + } + else { + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagAggresiveMode); + } + } + + if (!sUcastClientInterface) break; + sUcastClientInterface->Stop(peer_.PeerAddress(), stop_streams); + } + break; + + case BTIF_ACM_DISCONNECT_REQ_EVT: { + int contextType = p_acm->state_info.stream_type.type; + LOG_INFO(LOG_TAG, "%s: Peer %s : event=%s flags=%s contextType=%d", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str(), contextType); + + tBTIF_ACM_CONN_DISC* p_bta_data = (tBTIF_ACM_CONN_DISC*)p_data; + std::vector disconnect_streams; + if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC) { + StreamType type_1; + if (p_bta_data->profileType & (BAP|GCP)) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_1); + } + if (p_bta_data->profileType & WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + disconnect_streams.push_back(type_1); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_MUSIC_VOICE) { + StreamType type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + StreamType type_3 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_3); + disconnect_streams.push_back(type_2); + StreamType type_1; + if (p_bta_data->profileType & (BAP|GCP)) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_1); + } + if (p_bta_data->profileType & WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + disconnect_streams.push_back(type_1); + } + } else if (p_bta_data->contextType == CONTEXT_TYPE_VOICE) { + StreamType type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + StreamType type_3 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_3); + disconnect_streams.push_back(type_2); + } + LOG(WARNING) << __func__ << " size of disconnect_streams " << disconnect_streams.size(); + if (!sUcastClientInterface) break; + sUcastClientInterface->Disconnect(peer_.PeerAddress(), disconnect_streams); + + // Inform the application that we are disconnecting + if ((p_bta_data->contextType == CONTEXT_TYPE_MUSIC) && ((peer_.GetPeerVoiceRxState() == StreamState::CONNECTED) || + (peer_.GetPeerVoiceTxState() == StreamState::CONNECTED))) { + LOG(WARNING) << __func__ << " voice connected move in opened state "; + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } else if ((p_bta_data->contextType == CONTEXT_TYPE_VOICE) && ((peer_.GetPeerMusicTxState() == StreamState::CONNECTED) || + (peer_.GetPeerMusicRxState() == StreamState::CONNECTED))) { + LOG(WARNING) << __func__ << " Music connected remain in started state "; + } else { + LOG(WARNING) << __func__ << " Move in closing state "; + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } + } + break; + + case BTA_ACM_STOP_EVT: { + int contextType = p_acm->state_info.stream_type.type; + LOG_INFO(LOG_TAG, "%s: Peer %s : event=%s flags=%s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str()); + if (contextType == CONTENT_TYPE_MEDIA) { + BTIF_TRACE_DEBUG("%s: STOPPING event came from BAP for Media, ignore", __func__); + } else if (contextType == CONTENT_TYPE_CONVERSATIONAL) { + BTIF_TRACE_DEBUG("%s: STOPPING event came from BAP for Voice, ignore", __func__); + } + } + break; + + case BTA_ACM_DISCONNECT_EVT: { + int context_type = p_acm->state_info.stream_type.type; + if (p_acm->state_info.stream_state == StreamState::DISCONNECTED) { + if (context_type == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_MUSIC); + } + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED && + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnected state from BAP" + " when Voice Tx+Rx & Media Rx/Tx was disconnected, move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnected state from BAP" + " when either Voice Tx or Rx or Media Rx/Tx is connected, remain in started state", __func__); + } + } else if (context_type == CONTENT_TYPE_CONVERSATIONAL) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Tx," + " voice Rx, Music Tx & Rx are disconnected move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Tx," + " voice Rx is disconnected but music Tx or Rx still not disconnected," + " remain in started state", __func__); + } + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Tx, Music Tx & Rx are disconnected move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Rx is disconnected but music Tx or Rx still not disconnected," + " remain in started state", __func__); + } + } + } + } + } else if (p_acm->state_info.stream_state == StreamState::DISCONNECTING) { + if (context_type == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + if ((peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED || + peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTING) && + (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED || + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTING) && + (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED || + peer_.GetPeerMusicTxState() == StreamState::DISCONNECTING) && + (peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED || + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTING)) { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnecting state from BAP" + " when Voice Tx+Rx and Media Rx/Tx disconnected/ing, move in closing state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } else { + if (peer_.GetStreamContextType() == CONTEXT_TYPE_MUSIC) { + std::vector disconnect_streams; + btif_report_audio_state(peer_.PeerAddress(), BTACM_AUDIO_STATE_STOPPED, CONTEXT_TYPE_MUSIC); + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnecting state from BAP while streaming" + " when either Voice Tx or Rx or Media Rx/Tx is connected, move to opened state", __func__); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTING) { + BTIF_TRACE_DEBUG("%s: Received disconnecting for Music-Tx, initiate for Rx also", __func__); + StreamType type_1; + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + disconnect_streams.push_back(type_1); + if (!sUcastClientInterface) break; + sUcastClientInterface->Disconnect(peer_.PeerAddress(), disconnect_streams); + } + if (peer_.GetPeerMusicRxState() == StreamState::DISCONNECTING) { + BTIF_TRACE_DEBUG("%s: Received disconnecting for Music-Rx, initiate for Tx also", __func__); + StreamType type_1; + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + disconnect_streams.push_back(type_1); + if (!sUcastClientInterface) break; + sUcastClientInterface->Disconnect(peer_.PeerAddress(), disconnect_streams); + } + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } else { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnecting state from BAP" + " when either Voice Tx or Rx or Media Rx/Tx is connected, remain in started state", __func__); + } + } + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_MUSIC); + } else if (context_type == CONTENT_TYPE_CONVERSATIONAL) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTING || + peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED) { + if (((peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTING)) && + ((peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicRxState() == StreamState::DISCONNECTING))) { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for voice Tx," + " voice Rx, music Tx+Rx are disconnected/ing move in closing state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } else { + if (peer_.GetStreamContextType() == CONTEXT_TYPE_VOICE) { + btif_report_audio_state(peer_.PeerAddress(), BTACM_AUDIO_STATE_STOPPED, CONTEXT_TYPE_VOICE); + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for voice Tx while streaming," + " voice Rx is disconncted/ing but music Tx or Rx still not disconnected/ing," + " move to opened state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } else { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for voice Tx," + " voice Rx is disconncted/ing but music Tx or Rx still not disconnected/ing," + " remain in started state", __func__); + } + } + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_VOICE); + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTING || + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED) { + if (((peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTING)) && + ((peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicRxState() == StreamState::DISCONNECTING))) { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for voice Rx," + " voice Tx, music Tx+Rx are disconnected/ing move in closing state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } else { + if (peer_.GetStreamContextType() == CONTEXT_TYPE_VOICE) { + btif_report_audio_state(peer_.PeerAddress(), BTACM_AUDIO_STATE_STOPPED, CONTEXT_TYPE_VOICE); + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx while streaming," + " voice Tx is disconncted/ing but music Tx or Rx still not disconnected/ing," + " move to Opened state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Tx is disconncted/ing but music Tx or Rx still not disconnected/ing," + " remain in started state", __func__); + } + } + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_VOICE); + } + } + } + } + } + break; + + case BTA_ACM_CONNECT_EVT: {// above evnt can come and handle for voice/media case + int contextType = p_acm->state_info.stream_type.type; + LOG_INFO( + LOG_TAG, "%s: Peer %s : event=%s context=%d", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), contextType); + LOG_INFO( + LOG_TAG, "%s: context=%d, converted=%d, Streaming context=%d", + __PRETTY_FUNCTION__, contextType, btif_acm_bap_to_acm_context(contextType), peer_.GetStreamContextType()); + if (btif_acm_bap_to_acm_context(contextType) != peer_.GetStreamContextType()) { + if (contextType == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + if (p_acm->state_info.stream_state == StreamState::CONNECTED) { + BTIF_TRACE_DEBUG("%s: received connected state from BAP for Music Rx, update state", __func__); + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_state == StreamState::CONNECTING){ + BTIF_TRACE_DEBUG("%s: received connecting state from BAP for Music Rx, ignore", __func__); + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + if (p_acm->state_info.stream_state == StreamState::CONNECTED) { + BTIF_TRACE_DEBUG("%s: received connected state from BAP for Music Tx, update state", __func__); + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_state == StreamState::CONNECTING){ + BTIF_TRACE_DEBUG("%s: received connecting state from BAP for Music Tx, ignore", __func__); + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } + } + if (p_acm->state_info.stream_state == StreamState::CONNECTED) + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_CONNECTED, CONTEXT_TYPE_MUSIC); + } else if (contextType == CONTENT_TYPE_CONVERSATIONAL) { + if (p_acm->state_info.stream_state == StreamState::CONNECTED) { + BTIF_TRACE_DEBUG("%s: voice context connected, remain in started state", __func__); + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::CONNECTED) { + BTIF_TRACE_DEBUG("%s: received connected state from BAP for voice Tx, update state", __func__); + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_CONNECTED, CONTEXT_TYPE_VOICE); + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::CONNECTED) { + BTIF_TRACE_DEBUG("%s: received connected state from BAP for voice Rx, update state", __func__); + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_CONNECTED, CONTEXT_TYPE_VOICE); + } + } + } else if (p_acm->state_info.stream_state == StreamState::CONNECTING) { + BTIF_TRACE_DEBUG("%s: received connecting state from BAP for voice Tx or Rx, ignore", __func__); + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + } + } + } + } else { + if (peer_.CheckFlags(BtifAcmPeer::kFlagPendingLocalSuspend)) { + peer_.ClearFlags(BtifAcmPeer::kFlagPendingLocalSuspend); + BTIF_TRACE_DEBUG("%s: peer device is suspended, send MM any pending ACK", __func__); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_SUCCESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_SUCCESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + BTIF_TRACE_DEBUG("%s: report STOP to apps and move to Opened", __func__); + btif_report_audio_state(peer_.PeerAddress(), BTACM_AUDIO_STATE_STOPPED, peer_.GetStreamContextType()); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } + } + if (alarm_is_scheduled(btif_acm_initiator.AcmGroupProcedureTimer())) + btif_acm_check_and_cancel_group_procedure_timer(btif_acm_initiator.MusicActiveCSetId()); + + } break; + + case BTIF_ACM_RECONFIG_REQ_EVT: { + BTIF_TRACE_DEBUG("%s: sending stop to BAP before reconfigure", __func__); + btif_a2dp_source_end_session(active_bda); + peer_.SetFlags(BtifAcmPeer::kFlagPendingLocalSuspend); + StreamType type_1; + std::vector stop_streams; + if (peer_.GetStreamContextType() == CONTEXT_TYPE_MUSIC) { + if (current_active_profile_type != WMCP) { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + stop_streams.push_back(type_1); + } else { + type_1 = {.type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + stop_streams.push_back(type_1); + } + } else if (peer_.GetStreamContextType() == CONTEXT_TYPE_VOICE) { + StreamType type_2; + type_1 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + type_2 = {.type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + stop_streams.push_back(type_2); + stop_streams.push_back(type_1); + } + if (!sUcastClientInterface) break; + sUcastClientInterface->Stop(peer_.PeerAddress(), stop_streams); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateReconfiguring); + } + break; + + case BTA_ACM_CONFIG_EVT: { + tBTIF_ACM* p_acm_data = (tBTIF_ACM*)p_data; + uint16_t contextType = p_acm_data->state_info.stream_type.type; + uint16_t peer_latency_ms = 0; + uint32_t presen_delay = 0; + bool is_update_require = false; + if (contextType == CONTENT_TYPE_MEDIA) { + if (p_acm_data->state_info.stream_type.audio_context == CONTENT_TYPE_MEDIA) { + BTIF_TRACE_DEBUG("%s: compare current_media_config", __PRETTY_FUNCTION__); + is_update_require = compare_codec_config_(current_media_config, p_acm_data->config_info.codec_config); + } else if (p_acm_data->state_info.stream_type.audio_context == CONTENT_TYPE_LIVE) { + BTIF_TRACE_DEBUG("%s: cache current_recording_config", __PRETTY_FUNCTION__); + current_recording_config = p_acm_data->config_info.codec_config; + } + if (mandatory_codec_selected) { + BTIF_TRACE_DEBUG("%s: Mandatory codec selected, do not store config", __PRETTY_FUNCTION__); + } else { + BTIF_TRACE_DEBUG("%s: store configuration", __PRETTY_FUNCTION__); + } + //Cache the peer latency in WMCP case + if (p_acm_data->state_info.stream_type.audio_context == CONTENT_TYPE_LIVE) { + BTIF_TRACE_DEBUG("%s: presentation delay[0] = %x", __func__, + p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[0]); + BTIF_TRACE_DEBUG("%s: presentation delay[1] = %x", __func__, + p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[1]); + BTIF_TRACE_DEBUG("%s: presentation delay[2] = %x", __func__, + p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[2]); + presen_delay = static_cast(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[0]) | + static_cast(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[1] << 8) | + static_cast(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[2] << 16); + BTIF_TRACE_DEBUG("%s: presen_delay = %dus", __func__, presen_delay); + peer_latency_ms = presen_delay/1000; + BTIF_TRACE_DEBUG("%s: s_to_m latency = %dms", __func__, + p_acm_data->config_info.qos_config.cig_config.max_tport_latency_s_to_m); + peer_latency_ms += p_acm_data->config_info.qos_config.cig_config.max_tport_latency_s_to_m; + peer_.SetPeerLatency(peer_latency_ms); + BTIF_TRACE_DEBUG("%s: cached peer Latency = %dms", __func__, peer_.GetPeerLatency()); + } + if (is_update_require) { + current_media_config = p_acm_data->config_info.codec_config; + BTIF_TRACE_DEBUG("%s: current_media_config.codec_specific_3: %" + PRIi64, __func__, current_media_config.codec_specific_3); + btif_acm_update_lc3q_params(¤t_media_config.codec_specific_3, p_acm_data); + btif_acm_report_source_codec_state(peer_.PeerAddress(), current_media_config, + unicast_codecs_capabilities, + unicast_codecs_capabilities, CONTEXT_TYPE_MUSIC); + } + } else if (contextType == CONTENT_TYPE_CONVERSATIONAL && + p_acm_data->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + BTIF_TRACE_DEBUG("%s: cache current_voice_config", __PRETTY_FUNCTION__); + current_voice_config = p_acm_data->config_info.codec_config; + BTIF_TRACE_DEBUG("%s: current_voice_config.codec_specific_3: %" + PRIi64, __func__, current_voice_config.codec_specific_3); + btif_acm_update_lc3q_params(¤t_voice_config.codec_specific_3, p_acm_data); + btif_acm_report_source_codec_state(peer_.PeerAddress(), current_voice_config, + unicast_codecs_capabilities, + unicast_codecs_capabilities, CONTEXT_TYPE_VOICE); + } + //Handle BAP START if reconfig comes in mid of streaming + //peer_.SetStreamReconfigInfo(p_acm->acm_reconfig); + //TODO: local capabilities + //CodecConfig record = p_bta_data->acm_reconfig.codec_config; + //saving codec config as negotiated parameter as true + //btif_pacs_add_record(peer_.PeerAddress(), true, CodecDirection::CODEC_DIR_SRC, &record); + + } break; + + case BTA_ACM_CONN_UPDATE_TIMEOUT_EVT: + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagRelaxedMode); + break; + + default: + BTIF_TRACE_WARNING("%s: Peer %s : Unhandled event=%s", + __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str()); + return false; + } + + return true; +} + +void BtifAcmStateMachine::StateReconfiguring::OnEnter() { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str()); + if(btif_acm_initiator.IsConnUpdateEnabled()) { + //Cancel the timer if running if not, move to aggressive mode + if (alarm_is_scheduled(btif_acm_initiator.AcmConnIntervalTimer())) { + btif_acm_check_and_cancel_conn_Interval_timer(); + } else { + BTIF_TRACE_DEBUG("%s: conn timer not running, push aggressive intervals", __func__); + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagAggresiveMode); + } + } +} + +void BtifAcmStateMachine::StateReconfiguring::OnExit() { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str()); +} + +bool BtifAcmStateMachine::StateReconfiguring::ProcessEvent(uint32_t event, void* p_data) { + tBTIF_ACM* p_acm = (tBTIF_ACM*)p_data; + BTIF_TRACE_DEBUG("%s: Peer %s : event=%s flags=%s music_active_peer=%s voice_active_peer=%s", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str(), + logbool(peer_.IsPeerActiveForMusic()).c_str(), + logbool(peer_.IsPeerActiveForVoice()).c_str()); + + switch (event) { + case BTIF_ACM_SUSPEND_STREAM_REQ_EVT: + + case BTA_ACM_STOP_EVT: { + BTIF_TRACE_DEBUG("%s: STOPPING event from BAP, ignore", __func__); + } break; + + case BTA_ACM_RECONFIG_EVT: { + BTIF_TRACE_DEBUG("%s: received reconfiguring state from BAP, ignore", __func__); + } break; + + case BTA_ACM_CONFIG_EVT: { + uint16_t contextType = p_acm->state_info.stream_type.type; + uint16_t peer_latency_ms = 0; + uint32_t presen_delay = 0; + bool is_update_require = false; + if (contextType == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.audio_context == CONTENT_TYPE_MEDIA) { + BTIF_TRACE_DEBUG("%s: compare current_media_config", __PRETTY_FUNCTION__); + is_update_require = compare_codec_config_(current_media_config, p_acm->config_info.codec_config); + } else if (p_acm->state_info.stream_type.audio_context == CONTENT_TYPE_LIVE) { + BTIF_TRACE_DEBUG("%s: cache current_recording_config", __PRETTY_FUNCTION__); + current_recording_config = p_acm->config_info.codec_config; + } + //Cache the peer latency in WMCP case + if (peer_.GetRcfgProfileType() == WMCP) { + BTIF_TRACE_DEBUG("%s: presentation delay[0] = %x", __func__, + p_acm->config_info.qos_config.ascs_configs[0].presentation_delay[0]); + BTIF_TRACE_DEBUG("%s: presentation delay[1] = %x", __func__, + p_acm->config_info.qos_config.ascs_configs[0].presentation_delay[1]); + BTIF_TRACE_DEBUG("%s: presentation delay[2] = %x", __func__, + p_acm->config_info.qos_config.ascs_configs[0].presentation_delay[2]); + presen_delay = static_cast(p_acm->config_info.qos_config.ascs_configs[0].presentation_delay[0]) | + static_cast(p_acm->config_info.qos_config.ascs_configs[0].presentation_delay[1] << 8) | + static_cast(p_acm->config_info.qos_config.ascs_configs[0].presentation_delay[2] << 16); + BTIF_TRACE_DEBUG("%s: presen_delay = %dus", __func__, presen_delay); + peer_latency_ms = presen_delay/1000; + BTIF_TRACE_DEBUG("%s: s_to_m latency = %dms", __func__, + p_acm->config_info.qos_config.cig_config.max_tport_latency_s_to_m); + peer_latency_ms += p_acm->config_info.qos_config.cig_config.max_tport_latency_s_to_m; + peer_.SetPeerLatency(peer_latency_ms); + BTIF_TRACE_DEBUG("%s: cached peer Latency = %dms", __func__, peer_.GetPeerLatency()); + } + if (is_update_require) { + current_media_config = p_acm->config_info.codec_config; + BTIF_TRACE_DEBUG("%s: current_media_config.codec_specific_3: %" + PRIi64, __func__, current_media_config.codec_specific_3); + btif_acm_update_lc3q_params(¤t_media_config.codec_specific_3, p_acm); + btif_acm_report_source_codec_state(peer_.PeerAddress(), current_media_config, + unicast_codecs_capabilities, + unicast_codecs_capabilities, CONTEXT_TYPE_MUSIC); + } + } else if (contextType == CONTENT_TYPE_CONVERSATIONAL) { + BTIF_TRACE_DEBUG("%s: cache current_voice_config"); + current_voice_config = p_acm->config_info.codec_config; + BTIF_TRACE_DEBUG("%s: current_voice_config.codec_specific_3: %" + PRIi64, __func__, current_voice_config.codec_specific_3); + btif_acm_update_lc3q_params(¤t_voice_config.codec_specific_3, p_acm); + } + } break; + + case BTA_ACM_CONNECT_EVT: { + uint8_t status = (uint8_t)p_acm->state_info.stream_state; + LOG_INFO( + LOG_TAG, "%s: Peer %s : event=%s flags=%s status=%d", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), peer_.FlagsToString().c_str(), + status); + if (peer_.CheckFlags(BtifAcmPeer::kFlagPendingReconfigure)) { + if (p_acm->state_info.stream_state == StreamState::CONNECTED) { + if (p_acm->state_info.stream_type.type == CONTENT_TYPE_MEDIA) { + BTIF_TRACE_DEBUG("%s: Reconfig complete, move in opened state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } else { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::CONNECTED) { + BTIF_TRACE_DEBUG("%s: Report Call audio config to apps? move to opened when both Voice Tx and Rx done", __func__); + BTIF_TRACE_DEBUG("%s: received connected state from BAP for voice Tx, move in opened state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::CONNECTED) { + BTIF_TRACE_DEBUG("%s: Report Call audio config to apps? move to opened when both Voice Tx and Rx done", __func__); + BTIF_TRACE_DEBUG("%s: received connected state from BAP for voice Rx, move in opened state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateOpened); + } + } + } + } + break; + } + if (peer_.CheckFlags(BtifAcmPeer::kFlagPendingLocalSuspend)) { + peer_.ClearFlags(BtifAcmPeer::kFlagPendingLocalSuspend); + BTIF_TRACE_DEBUG("%s: peer device is suspended, send MM any pending ACK", __func__); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_SUCCESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_SUCCESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + if (alarm_is_scheduled(btif_acm_initiator.AcmGroupProcedureTimer())) + btif_acm_check_and_cancel_group_procedure_timer(btif_acm_initiator.MusicActiveCSetId()); + btif_report_audio_state(peer_.PeerAddress(), BTACM_AUDIO_STATE_STOPPED, peer_.GetStreamContextType()); + std::vector reconf_streams; + StreamReconfig reconf_info; + CodecQosConfig cfg; + reconf_info.stream_type.type = CONTENT_TYPE_MEDIA; + // TODO to change audio context based on use case ( media or gaming or Live audio) + if (peer_.GetRcfgProfileType() != WMCP) { + reconf_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; + reconf_info.stream_type.direction = ASE_DIRECTION_SINK; + } else { + reconf_info.stream_type.audio_context = CONTENT_TYPE_LIVE; + reconf_info.stream_type.direction = ASE_DIRECTION_SRC; + } + reconf_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + cfg = peer_.get_peer_media_codec_qos_config(); + reconf_info.codec_qos_config_pair.push_back(cfg); + reconf_streams.push_back(reconf_info); + peer_.SetFlags(BtifAcmPeer::kFlagPendingReconfigure); + if (!sUcastClientInterface) break; + sUcastClientInterface->Reconfigure(peer_.PeerAddress(), reconf_streams); + } + } break; + + case BTA_ACM_DISCONNECT_EVT: { + int context_type = p_acm->state_info.stream_type.type; + if (p_acm->state_info.stream_state == StreamState::DISCONNECTED) { + if (context_type == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_MUSIC); + } + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED && + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnected state from BAP" + " when Voice Tx+Rx & Media Rx/Tx was disconnected, move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnected state from BAP" + " when either Voice Tx or Rx or Media Rx/Tx is connected, remain in reconfiguring state", __func__); + } + } else if (context_type == CONTENT_TYPE_CONVERSATIONAL) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Tx," + " voice Rx, Music Tx & Rx are disconnected move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Tx," + " voice Rx is disconnected but music Tx or Rx still not disconnected," + " remain in reconfiguring state", __func__); + } + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Tx, Music Tx & Rx are disconnected move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Rx is disconnected but music Tx or Rx still not disconnected," + " remain in reconfiguring state", __func__); + } + } + } + } + } else if (p_acm->state_info.stream_state == StreamState::DISCONNECTING) { + if (context_type == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_MUSIC); + if ((peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED || + peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTING) && + (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED || + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTING) && + (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED || + peer_.GetPeerMusicTxState() == StreamState::DISCONNECTING) && + (peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED || + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTING)) { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnecting state from BAP" + " when Voice Tx+Rx and Media Rx/Tx disconnected/ing, move in closing state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } else { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnecting state from BAP" + " when either Voice Tx or Rx or Media Rx/Tx is connected, remain in reconfiguring state", __func__); + } + } else if (context_type == CONTENT_TYPE_CONVERSATIONAL) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTING || + peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_VOICE); + if (((peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTING)) && + ((peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicRxState() == StreamState::DISCONNECTING))) { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for voice Tx," + " voice Rx, music Tx+Rx are disconnected/ing move in closing state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } else { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for voice Tx," + " voice Rx is disconncted/ing but music Tx or Rx still not disconnected/ing," + " remain in reconfiguring state", __func__); + } + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTING || + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTING, CONTEXT_TYPE_VOICE); + if (((peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTING)) && + ((peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) || + (peer_.GetPeerMusicRxState() == StreamState::DISCONNECTING))) { + BTIF_TRACE_DEBUG("%s: received disconnecting state from BAP for voice Rx," + " voice Tx, music Tx+Rx are disconnected/ing move in closing state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateClosing); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Tx is disconncted/ing but music Tx or Rx still not disconnected/ing," + " remain in reconfiguring state", __func__); + } + } + } + } + } + } break; + + case BTA_ACM_CONN_UPDATE_TIMEOUT_EVT: + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagRelaxedMode); + break; + + default: + BTIF_TRACE_WARNING("%s: Peer %s : Unhandled event=%s", + __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str()); + return false; + } + return true; +} + +void BtifAcmStateMachine::StateClosing::OnEnter() { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str()); + if(btif_acm_initiator.IsConnUpdateEnabled()) { + //Cancel the timer if running if not, move to aggressive mode + if (alarm_is_scheduled(btif_acm_initiator.AcmConnIntervalTimer())) { + btif_acm_check_and_cancel_conn_Interval_timer(); + } + else { + BTIF_TRACE_DEBUG("%s: conn timer not running, push aggressive intervals", __func__); + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagAggresiveMode); + } + } + +} + +void BtifAcmStateMachine::StateClosing::OnExit() { + BTIF_TRACE_DEBUG("%s: Peer %s", __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str()); +} + +bool BtifAcmStateMachine::StateClosing::ProcessEvent(uint32_t event, void* p_data) { + tBTIF_ACM* p_acm = (tBTIF_ACM*)p_data; + BTIF_TRACE_DEBUG("%s: Peer %s : event=%s flags=%s music_active_peer=%s voice_active_peer=%s", + __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str(), + peer_.FlagsToString().c_str(), + logbool(peer_.IsPeerActiveForMusic()).c_str(), + logbool(peer_.IsPeerActiveForVoice()).c_str()); + + switch (event) { + case BTIF_ACM_SUSPEND_STREAM_REQ_EVT: + case BTIF_ACM_START_STREAM_REQ_EVT: + case BTA_ACM_STOP_EVT: + case BTIF_ACM_STOP_STREAM_REQ_EVT: + break; + + case BTA_ACM_DISCONNECT_EVT: { + int context_type = p_acm->state_info.stream_type.type; + if (p_acm->state_info.stream_state == StreamState::DISCONNECTED) { + if (context_type == CONTENT_TYPE_MEDIA) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), + BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_MUSIC); + } + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED && + peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnected state from BAP" + " when Voice Tx+Rx & Media Rx/Tx was disconnected, move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received Media Tx/Rx disconnected state from BAP" + " when either Voice Tx or Rx or Media Rx/Tx is connected, remain in closing state", __func__); + } + } else if (context_type == CONTENT_TYPE_CONVERSATIONAL) { + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceRxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Tx," + " voice Rx, Music Tx & Rx are disconnected move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Tx," + " voice Rx is disconnected but music Tx or Rx still not disconnected," + " remain in closing state", __func__); + } + } + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + if (peer_.GetPeerVoiceTxState() == StreamState::DISCONNECTED) { + btif_report_connection_state(peer_.PeerAddress(), BTACM_CONNECTION_STATE_DISCONNECTED, CONTEXT_TYPE_VOICE); + if (peer_.GetPeerMusicTxState() == StreamState::DISCONNECTED && + peer_.GetPeerMusicRxState() == StreamState::DISCONNECTED) { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Tx, Music Tx & Rx are disconnected move in idle state", __func__); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + } else { + BTIF_TRACE_DEBUG("%s: received disconnected state from BAP for voice Rx," + " voice Rx is disconnected but music Tx or Rx still not disconnected," + " remain in closing state", __func__); + } + } + } + } + } else if (p_acm->state_info.stream_state == StreamState::DISCONNECTING) { + if (context_type == CONTENT_TYPE_MEDIA) { + BTIF_TRACE_DEBUG("%s: received Music Tx or Rx disconnecting state from BAP, ignore", __func__); + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) + peer_.SetPeerMusicTxState(p_acm->state_info.stream_state); + else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) + peer_.SetPeerMusicRxState(p_acm->state_info.stream_state); + } else if (context_type == CONTENT_TYPE_CONVERSATIONAL) { + BTIF_TRACE_DEBUG("%s: received voice Tx or Rx disconnecting state from BAP, ignore", __func__); + if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SINK) { + peer_.SetPeerVoiceTxState(p_acm->state_info.stream_state); + } else if (p_acm->state_info.stream_type.direction == ASE_DIRECTION_SRC) { + peer_.SetPeerVoiceRxState(p_acm->state_info.stream_state); + } + } + } + } + break; + + case BTA_ACM_CONN_UPDATE_TIMEOUT_EVT: + peer_.SetConnUpdateMode(BtifAcmPeer::kFlagRelaxedMode); + break; + + default: + BTIF_TRACE_WARNING("%s: Peer %s : Unhandled event=%s", + __PRETTY_FUNCTION__, + peer_.PeerAddress().ToString().c_str(), + BtifAcmEvent::EventName(event).c_str()); + peer_.StateMachine().TransitionTo(BtifAcmStateMachine::kStateIdle); + return false; + } + return true; +} + +void btif_acm_update_lc3q_params(int64_t* cs3, tBTIF_ACM* p_data) { + + /* ================================================================== + * CS3: Res |LC3Q-len| QTI | VMT | VML | ver/For_Als |LC3Q-support + * ================================================================== + * 0x00 |0B | 000A | FF | 0F | 01/03 | 10 + * ================================================================== + * CS4: Res + * ============================== + * 0x00,00,00,00,00,00,00,00 + * ============================== */ + + if (GetVendorMetaDataLc3QPref( + &p_data->config_info.codec_config)) { + *cs3 &= ~((int64_t)0xFF << (LE_AUDIO_CS_3_1ST_BYTE_INDEX * 8)); + *cs3 |= ((int64_t)0x10 << (LE_AUDIO_CS_3_1ST_BYTE_INDEX * 8)); + + uint8_t lc3q_ver = GetVendorMetaDataLc3QVer(&p_data->config_info.codec_config); + BTIF_TRACE_DEBUG("%s: lc3q_ver: %d", __func__, lc3q_ver); + *cs3 &= ~((int64_t)0xFF << (LE_AUDIO_CS_3_2ND_BYTE_INDEX * 8)); + *cs3 |= ((int64_t)lc3q_ver << (LE_AUDIO_CS_3_2ND_BYTE_INDEX * 8)); + + //*cs3 &= ~((int64_t)LE_AUDIO_MASK); + *cs3 |= (int64_t)LE_AUDIO_AVAILABLE_LICENSED; + + *cs3 &= ~((int64_t)0xFF << (LE_AUDIO_CS_3_3RD_BYTE_INDEX * 8)); + *cs3 |= ((int64_t)0x0F << (LE_AUDIO_CS_3_3RD_BYTE_INDEX * 8)); + + *cs3 &= ~((int64_t)0xFF << (LE_AUDIO_CS_3_4TH_BYTE_INDEX * 8)); + *cs3 |= ((int64_t)0xFF << (LE_AUDIO_CS_3_4TH_BYTE_INDEX * 8)); + + *cs3 &= ~((int64_t)0xFFFF << (LE_AUDIO_CS_3_5TH_BYTE_INDEX * 8)); + *cs3 |= ((int64_t)0x000A << (LE_AUDIO_CS_3_5TH_BYTE_INDEX * 8)); + + *cs3 &= ~((int64_t)0xFF << (LE_AUDIO_CS_3_7TH_BYTE_INDEX * 8)); + *cs3 |= ((int64_t)0x0B << (LE_AUDIO_CS_3_7TH_BYTE_INDEX * 8)); + + CodecConfig temp = unicast_codecs_capabilities.back(); + unicast_codecs_capabilities.pop_back(); + temp.codec_specific_3 = *cs3; + unicast_codecs_capabilities.push_back(temp); + } + BTIF_TRACE_DEBUG("%s: cs3: %" PRIi64, __func__, *cs3); + BTIF_TRACE_DEBUG("%s: cs3= 0x%" PRIx64, __func__, *cs3); +} + +static void btif_report_connection_state(const RawAddress& peer_address, + btacm_connection_state_t state, uint16_t contextType) { + LOG_INFO(LOG_TAG, "%s: peer_address=%s state=%d contextType=%d", __func__, + peer_address.ToString().c_str(), state, contextType); + if (btif_acm_initiator.Enabled()) { + do_in_jni_thread(FROM_HERE, + Bind(btif_acm_initiator.Callbacks()->connection_state_cb, + peer_address, state, contextType)); + } +} + +static void btif_report_audio_state(const RawAddress& peer_address, + btacm_audio_state_t state, uint16_t contextType) { + LOG_INFO(LOG_TAG, "%s: peer_address=%s state=%d contextType=%d", __func__, + peer_address.ToString().c_str(), state, contextType); + if (btif_acm_initiator.Enabled()) { + do_in_jni_thread(FROM_HERE, + Bind(btif_acm_initiator.Callbacks()->audio_state_cb, + peer_address, state, contextType)); + } +} + +void btif_acm_report_source_codec_state( + const RawAddress& peer_address, + const CodecConfig& codec_config, + const std::vector& codecs_local_capabilities, + const std::vector& + codecs_selectable_capabilities, int contextType) { + BTIF_TRACE_EVENT("%s: peer_address=%s contextType=%d", __func__, + peer_address.ToString().c_str(), contextType); + if (btif_acm_initiator.Enabled()) { + do_in_jni_thread(FROM_HERE, + Bind(btif_acm_initiator.Callbacks()->audio_config_cb, peer_address, + codec_config, codecs_local_capabilities, + codecs_selectable_capabilities, contextType)); + } +} + +static void btif_acm_handle_evt(uint16_t event, char* p_param) { + BtifAcmPeer* peer = nullptr; + BTIF_TRACE_DEBUG("Handle the ACM event = %d ", event); + switch (event) { + case BTIF_ACM_DISCONNECT_REQ_EVT: { + if (p_param == NULL) { + BTIF_TRACE_ERROR("%s: Invalid p_param, dropping event: %d", __func__, event); + return; + } + tBTIF_ACM_CONN_DISC* p_acm = (tBTIF_ACM_CONN_DISC*)p_param; + peer = btif_acm_initiator.FindOrCreatePeer(p_acm->bd_addr); + if (peer == nullptr) { + BTIF_TRACE_ERROR( + "%s: Cannot find peer for peer_address=%s" + ": event dropped: %d", + __func__, p_acm->bd_addr.ToString().c_str(), + event); + return; + } else { + BTIF_TRACE_EVENT( + "%s: BTIF_ACM_DISCONNECT_REQ_EVT peer_address=%s" + ": contextType=%d", + __func__, p_acm->bd_addr.ToString().c_str(), + p_acm->contextType); + } + break; + } + case BTIF_ACM_START_STREAM_REQ_EVT: + case BTIF_ACM_SUSPEND_STREAM_REQ_EVT: + case BTIF_ACM_STOP_STREAM_REQ_EVT: { + if (p_param == NULL) { + BTIF_TRACE_ERROR("%s: Invalid p_param, dropping event: %d", __func__, event); + return; + } + tBTIF_ACM_CONN_DISC* p_acm = (tBTIF_ACM_CONN_DISC*)p_param; + peer = btif_acm_initiator.FindOrCreatePeer(p_acm->bd_addr); + if (peer == nullptr) { + BTIF_TRACE_ERROR("%s: Cannot find peer for peer_address=%s" + ": event dropped: %d", + __func__, p_acm->bd_addr.ToString().c_str(), event); + return; + } + } break; + case BTA_ACM_DISCONNECT_EVT: + case BTA_ACM_CONNECT_EVT: + case BTA_ACM_START_EVT: + case BTA_ACM_STOP_EVT: + case BTA_ACM_RECONFIG_EVT: { + if (p_param == NULL) { + BTIF_TRACE_ERROR("%s: Invalid p_param, dropping event: %d", __func__, event); + return; + } + tBTA_ACM_STATE_INFO* p_acm = (tBTA_ACM_STATE_INFO*)p_param; + peer = btif_acm_initiator.FindOrCreatePeer(p_acm->bd_addr); + if (peer == nullptr) { + BTIF_TRACE_ERROR("%s: Cannot find or create peer for peer_address=%s" + ": event dropped: %d", + __func__, p_acm->bd_addr.ToString().c_str(), event); + return; + } + } break; + case BTA_ACM_CONFIG_EVT: { + if (p_param == NULL) { + BTIF_TRACE_ERROR("%s: Invalid p_param, dropping event: %d", __func__, event); + return; + } + tBTA_ACM_CONFIG_INFO* p_acm = (tBTA_ACM_CONFIG_INFO*)p_param; + peer = btif_acm_initiator.FindPeer(p_acm->bd_addr); + if (peer == nullptr) { + BTIF_TRACE_ERROR("%s: Cannot find or create peer for peer_address=%s" + ": event dropped: %d", + __func__, p_acm->bd_addr.ToString().c_str(), event); + return; + } + } break; + case BTIF_ACM_RECONFIG_REQ_EVT: { + if (p_param == NULL) { + BTIF_TRACE_ERROR("%s: Invalid p_param, dropping event: %d", __func__, event); + return; + } + tBTIF_ACM_RECONFIG* p_acm = (tBTIF_ACM_RECONFIG*)p_param; + peer = btif_acm_initiator.FindPeer(p_acm->bd_addr); + if (peer == nullptr) { + BTIF_TRACE_ERROR("%s: Cannot find or create peer for peer_address=%s" + ": event dropped: %d", + __func__, p_acm->bd_addr.ToString().c_str(), event); + return; + } + } break; + + case BTA_ACM_CONN_UPDATE_TIMEOUT_EVT: { + if (p_param == NULL) { + BTIF_TRACE_ERROR("%s: Invalid p_param, dropping event: %d", __func__, event); + return; + } + tBTA_ACM_CONN_UPDATE_TIMEOUT_INFO * p_acm = + (tBTA_ACM_CONN_UPDATE_TIMEOUT_INFO *)p_param; + peer = btif_acm_initiator.FindPeer(p_acm->bd_addr); + if (peer == nullptr) { + BTIF_TRACE_ERROR("%s: Cannot find or create peer for peer_address=%s" + ": event dropped: %d", + __func__, p_acm->bd_addr.ToString().c_str(), event); + return; + } + } break; + + default : + BTIF_TRACE_DEBUG("UNHandled ACM event = %d ", event); + break; + } + peer->StateMachine().ProcessEvent(event, (void*)p_param); +} + +/** + * Process BTA CSIP events. The processing is done on the JNI + * thread. + */ +static void btif_acm_handle_bta_csip_event(uint16_t evt, char* p_param) { + BtifCsipEvent btif_csip_event(evt, p_param, sizeof(tBTA_CSIP_DATA)); + tBTA_CSIP_EVT event = btif_csip_event.Event(); + tBTA_CSIP_DATA* p_data = (tBTA_CSIP_DATA*)btif_csip_event.Data(); + BTIF_TRACE_DEBUG("%s: event=%s", __func__, btif_csip_event.ToString().c_str()); + + switch (event) { + case BTA_CSIP_LOCK_STATUS_CHANGED_EVT: { + const tBTA_LOCK_STATUS_CHANGED& lock_status_param = p_data->lock_status_param; + BTIF_TRACE_DEBUG("%s: app_id=%d, set_id=%d, status=%d ", __func__, + lock_status_param.app_id, lock_status_param.set_id, + lock_status_param.status); + + std::vector set_members =lock_status_param.addr; + + for (int j = 0; j < (int)set_members.size(); j++) { + BTIF_TRACE_DEBUG("%s: address =%s", __func__, set_members[j].ToString().c_str()); + } + + BTIF_TRACE_DEBUG("%s: Get current lock status: %d ", __func__, + btif_acm_initiator.GetGroupLockStatus(lock_status_param.set_id)); + if (btif_acm_initiator.GetGroupLockStatus(lock_status_param.set_id) == BtifAcmInitiator::kFlagStatusPendingLock) { + BTIF_TRACE_DEBUG("%s: lock was awaited for this set ", __func__); + } + + if (btif_acm_initiator.GetGroupLockStatus(lock_status_param.set_id) == BtifAcmInitiator::kFlagStatusPendingUnlock) { + BTIF_TRACE_DEBUG("%s: Unlock was awaited for this set ", __func__); + } + + BTIF_TRACE_DEBUG("%s: Get CSIP app id: %d ", __func__, + btif_acm_initiator.GetCsipAppId()); + if (btif_acm_initiator.GetCsipAppId() != lock_status_param.app_id) { + BTIF_TRACE_DEBUG("%s: app id mismatch ERROR!!! ", __func__); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_FAILURE); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_FAILURE); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + return; + } + + switch (lock_status_param.status) { + case LOCK_RELEASED: + BTIF_TRACE_DEBUG("%s: unlocked attempt succeeded ", __func__); + btif_acm_initiator.SetOrUpdateGroupLockStatus(lock_status_param.set_id, BtifAcmInitiator::kFlagStatusUnlocked); + break; + case LOCK_RELEASED_TIMEOUT: + BTIF_TRACE_DEBUG("%s: peer unlocked due to timeout ", __func__); + //in this case evaluate which device has sent TO and how to use it ? + btif_acm_initiator.SetOrUpdateGroupLockStatus(lock_status_param.set_id, BtifAcmInitiator::kFlagStatusUnlocked); + break; + case ALL_LOCKS_ACQUIRED: + btif_acm_initiator.SetOrUpdateGroupLockStatus(lock_status_param.set_id, BtifAcmInitiator::kFlagStatusLocked); + btif_acm_handle_csip_status_locked(lock_status_param.addr, lock_status_param.set_id); + BTIF_TRACE_DEBUG("%s: All locks acquired ", __func__); + break; + case SOME_LOCKS_ACQUIRED_REASON_TIMEOUT: + //proceed to continue use case; + /*case SOME_LOCKS_ACQUIRED_REASON_DISC: + //proceed to continue use case; + BTIF_TRACE_DEBUG("%s: locked attempt succeeded with status = %d", __func__, lock_status_param.status); + BTIF_TRACE_DEBUG("%s: locked set member count = %d, setsize = %d", + __func__, (lock_status_param.addr).size(), setSize); + btif_acm_initiator.music_active_set_locked_dev_count_ += (lock_status_param.addr).size(); + btif_acm_initiator.locked_devices.insert(btif_acm_initiator.locked_devices.end(), + lock_status_param.addr.begin(), lock_status_param.addr.end()); + btif_acm_handle_csip_status_locked(lock_status_param.addr, lock_status_param.set_id); + if (btif_acm_initiator.music_active_set_locked_dev_count_ < setSize) { + btif_acm_initiator.SetOrUpdateGroupLockStatus(lock_status_param.set_id, BtifAcmInitiator::kFlagStatusSubsetLocked); + } else { + btif_acm_initiator.SetOrUpdateGroupLockStatus(lock_status_param.set_id, BtifAcmInitiator::kFlagStatusLocked); + } + break;*/ + case LOCK_DENIED: { + //proceed to discontinue use case; + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_FAILURE); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_FAILURE); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + btif_acm_check_and_cancel_group_procedure_timer(lock_status_param.set_id); + btif_acm_initiator.SetOrUpdateGroupLockStatus(lock_status_param.set_id, BtifAcmInitiator::kFlagStatusUnlocked); + } break; + case INVALID_REQUEST_PARAMS: { + BTIF_TRACE_DEBUG("%s: invalid lock request ", __func__); + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_FAILURE); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_FAILURE); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + btif_acm_check_and_cancel_group_procedure_timer(lock_status_param.set_id); + if (btif_acm_initiator.GetGroupLockStatus(lock_status_param.set_id) == BtifAcmInitiator::kFlagStatusPendingLock) + btif_acm_initiator.SetOrUpdateGroupLockStatus(lock_status_param.set_id, BtifAcmInitiator::kFlagStatusUnlocked); + else + btif_acm_initiator.SetOrUpdateGroupLockStatus(lock_status_param.set_id, BtifAcmInitiator::kFlagStatusLocked); + } break; + default: + break; + } + } break; + case BTA_CSIP_SET_MEMBER_FOUND_EVT: { + const tBTA_SET_MEMBER_FOUND& set_member_param = p_data->set_member_param; + BTIF_TRACE_DEBUG("%s: set_id=%d, uuid=%d ", __func__, + set_member_param.set_id, + set_member_param.uuid); + } break; + + case BTA_CSIP_LOCK_AVAILABLE_EVT: { + const tBTA_LOCK_AVAILABLE& lock_available_param = p_data->lock_available_param; + BTIF_TRACE_DEBUG("%s: app_id=%d, set_id=%d ", __func__, + lock_available_param.app_id, lock_available_param.set_id); + } break; + } +} + +static void btif_acm_handle_csip_status_locked(std::vector addr, uint8_t setId) { + if (addr.empty()) { + BTIF_TRACE_ERROR("%s: vector size is empty", __func__); + return; + } + tA2DP_CTRL_CMD pending_cmd;// = A2DP_CTRL_CMD_START;//TODO: change to None + pending_cmd = btif_ahim_get_pending_command(); + std::vector::iterator itr; + int req = 0; + if (pending_cmd == A2DP_CTRL_CMD_START) { + req = BTIF_ACM_START_STREAM_REQ_EVT; + } else if (pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + req = BTIF_ACM_SUSPEND_STREAM_REQ_EVT; + } else if (pending_cmd == A2DP_CTRL_CMD_STOP) { + req = BTIF_ACM_STOP_STREAM_REQ_EVT; + } else { + BTIF_TRACE_EVENT("%s: No pending command, check if this list of peers belong to MusicActive streaming started group", __func__); +//if (btif_acm_initiator.IsMusicActiveGroupStarted() && (setId == btif_acm_initiator.MusicActiveCSetId())) +//req = BTIF_ACM_START_STREAM_REQ_EVT; + } + if (req) { + for (itr = addr.begin(); itr != addr.end(); itr++) { + btif_acm_initiator_dispatch_sm_event(*itr, static_cast(req)); + } + } +// BtifAcmPeer* peer_ = btif_acm_initiator.FindPeer(peer_address); + /*if ((peer_.IsPeerActiveForMusic() || !btif_acm_stream_started_ready())) { + // Immediately stop transmission of frames while suspend is pending + if (req == BTIF_ACM_STOP_STREAM_REQ_EVT) { + //btif_acm_on_stopped(nullptr); + } else if (req == BTIF_ACM_SUSPEND_STREAM_REQ_EVT) { + // ensure tx frames are immediately suspended + //btif_acm_source_set_tx_flush(true); + } + }*/ +} + +static void btif_acm_check_and_start_conn_Interval_timer(BtifAcmPeer* peer) { + + btif_acm_check_and_cancel_conn_Interval_timer(); + BTIF_TRACE_DEBUG("%s: ", __func__); + + alarm_set_on_mloop(btif_acm_initiator.AcmConnIntervalTimer(), + BtifAcmInitiator::kTimeoutConnIntervalMs, + btif_acm_initiator_conn_Interval_timer_timeout, + (void *)peer); +} + +static void btif_acm_check_and_cancel_conn_Interval_timer() { + + BTIF_TRACE_DEBUG("%s: ", __func__); + if (alarm_is_scheduled(btif_acm_initiator.AcmConnIntervalTimer())) { + alarm_cancel(btif_acm_initiator.AcmConnIntervalTimer()); + } +} + + +static void btif_acm_initiator_conn_Interval_timer_timeout(void *data) { + + BTIF_TRACE_DEBUG("%s: ", __func__); + BtifAcmPeer *peer = (BtifAcmPeer *)data; + tBTA_ACM_CONN_UPDATE_TIMEOUT_INFO p_data; + p_data.bd_addr = peer->PeerAddress(); + btif_transfer_context(btif_acm_handle_evt, BTA_ACM_CONN_UPDATE_TIMEOUT_EVT, + (char*)&p_data, + sizeof(tBTA_ACM_CONN_UPDATE_TIMEOUT_INFO), NULL); +} + +static void btif_acm_check_and_start_group_procedure_timer(uint8_t setId) { + uint8_t *arg = NULL; + arg = (uint8_t *) osi_malloc(sizeof(uint8_t)); + BTIF_TRACE_DEBUG("%s: ", __func__); + btif_acm_check_and_cancel_group_procedure_timer(setId); + + *arg = setId; + alarm_set_on_mloop(btif_acm_initiator.AcmGroupProcedureTimer(), + BtifAcmInitiator::kTimeoutAcmGroupProcedureMs, + btif_acm_initiator_group_procedure_timer_timeout, + (void*) arg); + +} + +static void btif_acm_check_and_cancel_group_procedure_timer(uint8_t setId) { + if (alarm_is_scheduled(btif_acm_initiator.AcmGroupProcedureTimer())) { + BTIF_TRACE_ERROR("%s: acm group procedure already running for setId = %d, cancel", __func__, setId); + alarm_cancel(btif_acm_initiator.AcmGroupProcedureTimer()); + } +} + +static void btif_acm_initiator_group_procedure_timer_timeout(void *data) { + BTIF_TRACE_DEBUG("%s: ", __func__); + tBTA_CSIP_CSET cset_info; // need to do memset ? + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + std::vector streaming_devices; + std::vector non_streaming_devices; + uint8_t *arg = (uint8_t*) data; + if (!arg) { + BTIF_TRACE_ERROR("%s: coordinate arg is null, return", __func__); + return; + } + uint8_t setId = *arg; + if (setId == INVALID_SET_ID) { + BTIF_TRACE_ERROR("%s: coordinate SetId is invalid, return", __func__); + if (arg) osi_free(arg); + return; + } + + cset_info = BTA_CsipGetCoordinatedSet(setId); + if (cset_info.size == 0) { + BTIF_TRACE_ERROR("%s: CSET info size is zero, return", __func__); + if (arg) osi_free(arg); + return; + } + std::vector::iterator itr; + BTIF_TRACE_DEBUG("%s: size of set members %d", __func__, (cset_info.set_members).size()); + if ((cset_info.set_members).size() > 0) { + for (itr =(cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + //BTIF_TRACE_DEBUG("%s: address = %s", __func__, itr->ToString().c_str()); + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(*itr); + if ((peer == nullptr) || (peer != nullptr && !peer->IsStreaming())) { + non_streaming_devices.push_back(*itr); + } else { + streaming_devices.push_back(*itr); + } + } + } + + if (streaming_devices.size() > 0) { + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_SUCCESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_SUCCESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + BTIF_TRACE_DEBUG("%s: Get music active setid: %d", __func__, + btif_acm_initiator.MusicActiveCSetId()); + btif_acm_check_and_start_lock_release_timer(btif_acm_initiator.MusicActiveCSetId()); + if (streaming_devices.size() < (cset_info.set_members).size()) { + // this case should continue with mono mode since all set members are not streaming + } + } else { + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_FAILURE); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_FAILURE); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + } + + if (non_streaming_devices.size() > 0) //do we need to unlock and then disconnect ?? + // le_Acl_disconnect (non_streaming_devices); + + if (arg) osi_free(arg); +} + +static void btif_acm_check_and_start_lock_release_timer(uint8_t setId) { + uint8_t *arg = NULL; + arg = (uint8_t *) osi_malloc(sizeof(uint8_t)); + + btif_acm_check_and_cancel_lock_release_timer(setId); + + *arg = setId; + alarm_set_on_mloop(btif_acm_initiator.MusicSetLockReleaseTimer(), + BtifAcmPeer::kTimeoutLockReleaseMs, + btif_acm_initiator_lock_release_timer_timeout, + (void*) arg); +} + +static void btif_acm_check_and_cancel_lock_release_timer(uint8_t setId) { + if (alarm_is_scheduled(btif_acm_initiator.MusicSetLockReleaseTimer())) { + BTIF_TRACE_ERROR("%s: lock release already running for setId = %d, cancel ", __func__, setId); + alarm_cancel(btif_acm_initiator.MusicSetLockReleaseTimer()); + } +} + +static void btif_acm_initiator_lock_release_timer_timeout(void *data) { + uint8_t *arg = (uint8_t*) data; + if (!arg) { + BTIF_TRACE_ERROR("%s: coordinate arg is null, return", __func__); + return; + } + uint8_t setId = *arg; + if (setId == INVALID_SET_ID) { + BTIF_TRACE_ERROR("%s: coordinate SetId is invalid, return", __func__); + if (arg) osi_free(arg); + return; + } + if ((btif_acm_initiator.GetGroupLockStatus(setId) != BtifAcmInitiator::kFlagStatusLocked) || + (btif_acm_initiator.GetGroupLockStatus(setId) != BtifAcmInitiator::kFlagStatusSubsetLocked)) { + BTIF_TRACE_ERROR("%s: SetId = %d Lock Status = %d returning", + __func__, setId, btif_acm_initiator.GetGroupLockStatus(setId)); + if (arg) osi_free(arg); + return; + } + if (!btif_acm_request_csip_unlock(setId)) { + BTIF_TRACE_ERROR("%s: error unlocking", __func__); + } + if (arg) osi_free(arg); +} + +static void bta_csip_callback(tBTA_CSIP_EVT event, tBTA_CSIP_DATA* p_data) { + BTIF_TRACE_DEBUG("%s: event: %d", __func__, event); + btif_transfer_context(btif_acm_handle_bta_csip_event, event, (char*)p_data, + sizeof(tBTA_CSIP_DATA), NULL); +} + +// Initializes the ACM interface for initiator mode +static bt_status_t init_acm_initiator( + btacm_initiator_callbacks_t* callbacks, int max_connected_acceptors, + const std::vector& codec_priorities) { + BTIF_TRACE_EVENT("%s", __func__); + return btif_acm_initiator.Init(callbacks, max_connected_acceptors, + codec_priorities); +} + +// Establishes the BAP connection with the remote acceptor device +static void connect_int(uint16_t uuid, char* p_param) { + tBTIF_ACM_CONN_DISC connection; + memset(&connection, 0, sizeof(tBTIF_ACM_CONN_DISC)); + memcpy(&connection, p_param, sizeof(connection)); + RawAddress peer_address = RawAddress::kEmpty; + BtifAcmPeer* peer = nullptr; + peer_address = connection.bd_addr; + if (uuid == ACM_UUID) { + peer = btif_acm_initiator.FindOrCreatePeer(peer_address); + } + if (peer == nullptr) { + BTIF_TRACE_ERROR("%s: peer is NULL", __func__); + return; + } + peer->SetContextType(connection.contextType); + peer->SetProfileType(connection.profileType); + BTIF_TRACE_DEBUG("%s: cummulative_profile_type %d", __func__, peer->GetProfileType()); + //peer->SetPrefContextType(preferredContext); + peer->StateMachine().ProcessEvent(BTIF_ACM_CONNECT_REQ_EVT, &connection); +} + +// Set the active peer for contexttype +static void set_acm_active_peer_int(const RawAddress& peer_address, + uint16_t contextType, uint16_t profileType, + std::promise peer_ready_promise) { + BTIF_TRACE_EVENT("%s: peer_address=%s", __func__, peer_address.ToString().c_str()); + if (peer_address.IsEmpty()) { + int setid = INVALID_SET_ID; + if (contextType == CONTEXT_TYPE_MUSIC) + setid = btif_acm_initiator.MusicActiveCSetId(); + else if (contextType == CONTEXT_TYPE_VOICE) + setid = btif_acm_initiator.VoiceActiveCSetId(); + + if (setid < INVALID_SET_ID) { + tBTA_CSIP_CSET cset_info; + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(setid); + if (cset_info.size != 0) { + std::vector::iterator itr; + BTIF_TRACE_DEBUG("%s: size of set members %d", __func__, (cset_info.set_members).size()); + if ((cset_info.set_members).size() > 0) { + for (itr =(cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(*itr); + if (peer != nullptr && peer->IsStreaming() && + (contextType == peer->GetStreamContextType())) { + BTIF_TRACE_DEBUG("%s: peer is streaming %s ", __func__, peer->PeerAddress().ToString().c_str()); + btif_acm_initiator_dispatch_sm_event(*itr, BTIF_ACM_STOP_STREAM_REQ_EVT); + } + } + } + } + } else { + BTIF_TRACE_DEBUG("%s: set active for twm device ", __func__); + BtifAcmPeer* peer = nullptr; + if (contextType == CONTEXT_TYPE_MUSIC) + peer = btif_acm_initiator.FindPeer(btif_acm_initiator.MusicActivePeer()); + else if (contextType == CONTEXT_TYPE_VOICE) + peer = btif_acm_initiator.FindPeer(btif_acm_initiator.VoiceActivePeer()); + if (peer != nullptr && peer->IsStreaming() && + (contextType == peer->GetStreamContextType())) { + BTIF_TRACE_DEBUG("%s: peer is streaming %s ", __func__, peer->PeerAddress().ToString().c_str()); + if (contextType == CONTEXT_TYPE_MUSIC) + btif_acm_initiator_dispatch_sm_event(btif_acm_initiator.MusicActivePeer(), BTIF_ACM_STOP_STREAM_REQ_EVT); + else if (contextType == CONTEXT_TYPE_VOICE) + btif_acm_initiator_dispatch_sm_event(btif_acm_initiator.VoiceActivePeer(), BTIF_ACM_STOP_STREAM_REQ_EVT); + } + } + } + if (!btif_acm_initiator.SetAcmActivePeer(peer_address, contextType, profileType, + std::move(peer_ready_promise))) { + BTIF_TRACE_ERROR("%s: Error setting %s as active peer", __func__, + peer_address.ToString().c_str()); + } +} + +static bt_status_t connect_acm_initiator(const RawAddress& peer_address, + uint16_t contextType, uint16_t profileType, + uint16_t preferredContext) { + BTIF_TRACE_EVENT("%s: Peer %s contextType=%d profileType=%d preferredContext=%d", __func__, + peer_address.ToString().c_str(), contextType, profileType, preferredContext); + + if (!btif_acm_initiator.Enabled()) { + BTIF_TRACE_WARNING("%s: BTIF ACM Initiator is not enabled", __func__); + return BT_STATUS_NOT_READY; + } + + tBTIF_ACM_CONN_DISC conn; + conn.contextType = contextType; + conn.profileType = profileType; + conn.bd_addr = peer_address; + return btif_transfer_context(connect_int, ACM_UUID, (char*)&conn, + sizeof(tBTIF_ACM_CONN_DISC), NULL); +} + +static bt_status_t disconnect_acm_initiator(const RawAddress& peer_address, + uint16_t contextType) { + BTIF_TRACE_EVENT("%s: Peer %s contextType=%d", __func__, + peer_address.ToString().c_str(), contextType); + + if (!btif_acm_initiator.Enabled()) { + BTIF_TRACE_WARNING("%s: BTIF ACM Initiator is not enabled", __func__); + return BT_STATUS_NOT_READY; + } + + BtifAcmPeer* peer = btif_acm_initiator.FindOrCreatePeer(peer_address); + if (peer == nullptr) { + BTIF_TRACE_ERROR("%s: peer is NULL", __func__); + return BT_STATUS_FAIL; + } + + tBTIF_ACM_CONN_DISC disc; + peer->ResetContextType(contextType); + if (contextType == CONTEXT_TYPE_MUSIC) { + peer->ResetProfileType(BAP|GCP|WMCP); + disc.profileType = BAP|GCP|WMCP; + } else if (contextType == CONTEXT_TYPE_VOICE) { + peer->ResetProfileType(BAP_CALL); + disc.profileType = BAP_CALL; + } else if (contextType == CONTEXT_TYPE_MUSIC_VOICE) { + peer->ResetProfileType(BAP|GCP|WMCP|BAP_CALL); + disc.profileType = BAP|GCP|WMCP|BAP_CALL; + } + BTIF_TRACE_DEBUG("%s: cummulative_profile_type %d", __func__, peer->GetProfileType()); + + disc.bd_addr = peer_address; + disc.contextType = contextType; + btif_transfer_context(btif_acm_handle_evt, BTIF_ACM_DISCONNECT_REQ_EVT, (char*)&disc, + sizeof(tBTIF_ACM_CONN_DISC), NULL); + return BT_STATUS_SUCCESS; +} + +static bt_status_t set_active_acm_initiator(const RawAddress& peer_address, + uint16_t profileType) { + uint16_t contextType = CONTEXT_TYPE_MUSIC; + if (profileType == BAP || profileType == GCP || profileType == WMCP) + contextType = CONTEXT_TYPE_MUSIC; + else if (profileType == BAP_CALL) + contextType = CONTEXT_TYPE_VOICE; + + BTIF_TRACE_EVENT("%s: Peer %s contextType=%d profileType=%d", __func__, + peer_address.ToString().c_str(), contextType, profileType); + if (!btif_acm_initiator.Enabled()) { + LOG(WARNING) << __func__ << ": BTIF ACM Initiator is not enabled"; + return BT_STATUS_NOT_READY; + } + + BtifAcmPeer* peer = nullptr; + if (contextType == CONTEXT_TYPE_MUSIC) { + peer = btif_acm_initiator.FindPeer(btif_acm_initiator.MusicActivePeer()); + if ((peer != nullptr) && (peer->GetStreamContextType() == CONTEXT_TYPE_MUSIC) && + (peer->CheckFlags(BtifAcmPeer::kFlagPendingStart | BtifAcmPeer::kFlagPendingLocalSuspend | + BtifAcmPeer::kFlagPendingReconfigure))) { + LOG(WARNING) << __func__ << ": Active music device is pending start or suspend or reconfig"; + return BT_STATUS_NOT_READY; + } + } else if (contextType == CONTEXT_TYPE_VOICE) { + peer = btif_acm_initiator.FindPeer(btif_acm_initiator.VoiceActivePeer()); + if ((peer != nullptr) && (peer->GetStreamContextType() == CONTEXT_TYPE_VOICE) && + (peer->CheckFlags(BtifAcmPeer::kFlagPendingStart | + BtifAcmPeer::kFlagPendingLocalSuspend))) { + LOG(WARNING) << __func__ << ": Active voice device is pending start or suspend"; + return BT_STATUS_NOT_READY; + } + } + std::promise peer_ready_promise; + std::future peer_ready_future = peer_ready_promise.get_future(); + set_acm_active_peer_int(peer_address, contextType, profileType, + std::move(peer_ready_promise)); + return BT_STATUS_SUCCESS; +} + +static bt_status_t start_stream_acm_initiator(const RawAddress& peer_address, + uint16_t contextType) { + LOG_INFO(LOG_TAG, "%s: Peer %s", __func__, peer_address.ToString().c_str()); + + if (!btif_acm_initiator.Enabled()) { + BTIF_TRACE_WARNING("%s: BTIF ACM Initiator is not enabled", __func__); + return BT_STATUS_NOT_READY; + } + int id = btif_acm_initiator.VoiceActiveCSetId(); + if (id < INVALID_SET_ID) { + tBTA_CSIP_CSET cset_info; + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(id); + std::vector::iterator itr; + BTIF_TRACE_DEBUG("%s: size of set members %d", __func__, (cset_info.set_members).size()); + if ((cset_info.set_members).size() > 0) { + for (itr =(cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + BTIF_TRACE_DEBUG("%s: Sending start request ", __func__); + BtifAcmPeer* p = btif_acm_initiator.FindPeer(*itr); + if (p && p->IsConnected()) { + p->SetStreamContextType(contextType); + btif_acm_initiator_dispatch_sm_event(*itr, BTIF_ACM_START_STREAM_REQ_EVT); + } + } + } + btif_acm_check_and_start_group_procedure_timer(btif_acm_initiator.VoiceActiveCSetId()); + } else { + BTIF_TRACE_DEBUG("%s: Sending start to twm device ", __func__); + BtifAcmPeer* p = btif_acm_initiator.FindPeer(btif_acm_initiator.VoiceActivePeer()); + if (p != nullptr && p->IsConnected()) { + p->SetStreamContextType(CONTEXT_TYPE_VOICE); + btif_acm_initiator_dispatch_sm_event(btif_acm_initiator.VoiceActivePeer(), BTIF_ACM_START_STREAM_REQ_EVT); + } else { + BTIF_TRACE_DEBUG("%s: Unable to send start to twm device ", __func__); + } + } + return BT_STATUS_SUCCESS; +} + +static bt_status_t stop_stream_acm_initiator(const RawAddress& peer_address, + uint16_t contextType) { + LOG_INFO(LOG_TAG, "%s: Peer %s", __func__, peer_address.ToString().c_str()); + + if (!btif_acm_initiator.Enabled()) { + BTIF_TRACE_WARNING("%s: BTIF ACM Initiator is not enabled", __func__); + return BT_STATUS_NOT_READY; + } + + int id = btif_acm_initiator.VoiceActiveCSetId(); + if (id < INVALID_SET_ID) { + tBTA_CSIP_CSET cset_info; + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(id); + std::vector::iterator itr; + BTIF_TRACE_DEBUG("%s: size of set members %d", __func__, (cset_info.set_members).size()); + if ((cset_info.set_members).size() > 0) { + for (itr =(cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + BTIF_TRACE_DEBUG("%s: Sending stop request ", __func__); + BtifAcmPeer* p = btif_acm_initiator.FindPeer(*itr); + if (p && p->IsConnected()) { + p->SetStreamContextType(contextType); + btif_acm_initiator_dispatch_sm_event(*itr, BTIF_ACM_STOP_STREAM_REQ_EVT); + } + } + } + btif_acm_check_and_start_group_procedure_timer(btif_acm_initiator.VoiceActiveCSetId()); + } else { + BTIF_TRACE_DEBUG("%s: Sending stop to twm device ", __func__); + BtifAcmPeer* p = btif_acm_initiator.FindPeer(btif_acm_initiator.VoiceActivePeer()); + if (p != nullptr && p->IsConnected()) { + p->SetStreamContextType(CONTEXT_TYPE_VOICE); + btif_acm_initiator_dispatch_sm_event(btif_acm_initiator.VoiceActivePeer(), BTIF_ACM_STOP_STREAM_REQ_EVT); + } else { + BTIF_TRACE_DEBUG("%s: Unable to send stop to twm device ", __func__); + } + } + return BT_STATUS_SUCCESS; +} + +static bt_status_t codec_config_acm_initiator(const RawAddress& peer_address, + std::vector codec_preferences, + uint16_t contextType, uint16_t profileType) { + BTIF_TRACE_EVENT("%s", __func__); + + if (!btif_acm_initiator.Enabled()) { + LOG(WARNING) << __func__ << ": BTIF ACM Initiator is not enabled"; + return BT_STATUS_NOT_READY; + } + + if (peer_address.IsEmpty()) { + LOG(WARNING) << __func__ << ": BTIF ACM Initiator, peer empty"; + return BT_STATUS_PARM_INVALID; + } + + std::promise peer_ready_promise; + std::future peer_ready_future = peer_ready_promise.get_future(); + bt_status_t status = BT_STATUS_SUCCESS; + if (status == BT_STATUS_SUCCESS) { + peer_ready_future.wait(); + } else { + LOG(WARNING) << __func__ << ": BTIF ACM Initiator fails to config codec"; + } + return status; +} + +static bt_status_t change_codec_config_acm_initiator(const RawAddress& peer_address, + char* msg) { + BTIF_TRACE_DEBUG("%s: codec change string: %s", __func__, msg); + tBTIF_ACM_RECONFIG data; + if (!btif_acm_initiator.Enabled()) { + LOG(WARNING) << __func__ << ": BTIF ACM Initiator is not enabled"; + return BT_STATUS_NOT_READY; + } + + if (peer_address.IsEmpty()) { + LOG(WARNING) << __func__ << ": BTIF ACM Initiator, peer empty"; + return BT_STATUS_PARM_INVALID; + } + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(peer_address); + if (peer == nullptr) + LOG(ERROR) << __func__ << ": BTIF ACM Initiator, peer is null"; + return BT_STATUS_FAIL; + + CodecQosConfig codec_qos_cfg; + memset(&codec_qos_cfg, 0, sizeof(codec_qos_cfg)); + if (!strcmp(msg, "GCP_TX") && peer->GetContextType() == CONTEXT_TYPE_MUSIC) { + data.bd_addr = peer_address; + data.streams_info.stream_type.type = CONTENT_TYPE_MEDIA; + data.streams_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; + data.streams_info.stream_type.direction = ASE_DIRECTION_SINK; + data.streams_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + if (peer->IsStereoHsType()) { + SelectCodecQosConfig(peer_address, GCP, MEDIA_CONTEXT, SNK, STEREO_HS_CONFIG_1); + } else { + SelectCodecQosConfig(peer_address, GCP, MEDIA_CONTEXT, SNK, EB_CONFIG); + } + codec_qos_cfg = peer->get_peer_media_codec_qos_config(); + data.streams_info.codec_qos_config_pair.push_back(codec_qos_cfg); + } else if (!strcmp(msg, "GCP_TX_RX") && peer->GetContextType() == CONTEXT_TYPE_MUSIC) { + data.bd_addr = peer_address; + data.streams_info.stream_type.type = CONTENT_TYPE_MEDIA; + data.streams_info.stream_type.direction = ASE_DIRECTION_SINK; + data.streams_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + SelectCodecQosConfig(peer_address, GCP, MEDIA_CONTEXT, SNK, EB_CONFIG); + codec_qos_cfg = peer->get_peer_media_codec_qos_config(); + codec_qos_cfg.qos_config.cig_config.cig_id++; + codec_qos_cfg.qos_config.ascs_configs[0].cig_id++; + peer->set_peer_media_qos_config(codec_qos_cfg.qos_config); + peer->set_peer_media_codec_qos_config(codec_qos_cfg); + data.streams_info.codec_qos_config_pair.push_back(codec_qos_cfg); + } else if (!strcmp(msg, "MEDIA_TX")) { + data.bd_addr = peer_address; + data.streams_info.stream_type.type = CONTENT_TYPE_MEDIA; + data.streams_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + data.streams_info.stream_type.direction = ASE_DIRECTION_SINK; + if (peer->IsStereoHsType()) { + SelectCodecQosConfig(peer_address, BAP, MEDIA_CONTEXT, SNK, STEREO_HS_CONFIG_1); + } else { + SelectCodecQosConfig(peer_address, BAP, MEDIA_CONTEXT, SNK, EB_CONFIG); + } + codec_qos_cfg = peer->get_peer_media_codec_qos_config(); + data.streams_info.codec_qos_config_pair.push_back(codec_qos_cfg); + } else if (!strcmp(msg, "MEDIA_RX")) { + data.bd_addr = peer_address; + data.streams_info.stream_type.type = CONTENT_TYPE_MEDIA; + data.streams_info.stream_type.audio_context = CONTENT_TYPE_LIVE; //Live Audio Context + data.streams_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + data.streams_info.stream_type.direction = ASE_DIRECTION_SRC; + SelectCodecQosConfig(peer_address, WMCP, MEDIA_CONTEXT, SRC, EB_CONFIG); + codec_qos_cfg = peer->get_peer_media_codec_qos_config(); + data.streams_info.codec_qos_config_pair.push_back(codec_qos_cfg); + } + print_codec_parameters(codec_qos_cfg.codec_config); + print_qos_parameters(codec_qos_cfg.qos_config); + btif_transfer_context(btif_acm_handle_evt, BTIF_ACM_RECONFIG_REQ_EVT, (char*)&data, + sizeof(tBTIF_ACM_RECONFIG), NULL); + return BT_STATUS_SUCCESS; +} + +bool reconfig_acm_initiator(const RawAddress& peer_address, int profileType) { + BTIF_TRACE_DEBUG("%s: profileType: %d", __func__, profileType); + tBTIF_ACM_RECONFIG data; + if (!btif_acm_initiator.Enabled()) { + LOG(WARNING) << __func__ << ": BTIF ACM Initiator is not enabled"; + return false; + } + + if (peer_address.IsEmpty()) { + LOG(WARNING) << __func__ << ": BTIF ACM Initiator, peer empty"; + return false; + } + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(peer_address); + if (peer == nullptr) { + LOG(ERROR) << __func__ << ": BTIF ACM Initiator, peer is null"; + return false; + } + + CodecQosConfig codec_qos_cfg; + memset(&codec_qos_cfg, 0, sizeof(codec_qos_cfg)); + if ((profileType == GCP) && (peer->GetContextType() & CONTEXT_TYPE_MUSIC)) { + data.bd_addr = peer_address; + data.streams_info.stream_type.type = CONTENT_TYPE_MEDIA; + data.streams_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; + data.streams_info.stream_type.direction = ASE_DIRECTION_SINK; + data.streams_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + if (peer->IsStereoHsType()) { + SelectCodecQosConfig(peer_address, GCP, MEDIA_CONTEXT, SNK, STEREO_HS_CONFIG_1); + } else { + SelectCodecQosConfig(peer_address, GCP, MEDIA_CONTEXT, SNK, EB_CONFIG); + } + codec_qos_cfg = peer->get_peer_media_codec_qos_config(); + data.streams_info.codec_qos_config_pair.push_back(codec_qos_cfg); + } else if ((profileType == GCP_TX_RX) && (peer->GetContextType() & CONTEXT_TYPE_MUSIC)) { + data.bd_addr = peer_address; + data.streams_info.stream_type.type = CONTENT_TYPE_MEDIA; + data.streams_info.stream_type.direction = ASE_DIRECTION_SINK; + data.streams_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + SelectCodecQosConfig(peer_address, GCP, MEDIA_CONTEXT, SNK, EB_CONFIG); + codec_qos_cfg = peer->get_peer_media_codec_qos_config(); + codec_qos_cfg.qos_config.cig_config.cig_id++; + codec_qos_cfg.qos_config.ascs_configs[0].cig_id++; + peer->set_peer_media_qos_config(codec_qos_cfg.qos_config); + peer->set_peer_media_codec_qos_config(codec_qos_cfg); + data.streams_info.codec_qos_config_pair.push_back(codec_qos_cfg); + } else if ((profileType == BAP) && (peer->GetContextType() & CONTEXT_TYPE_MUSIC)) { + data.bd_addr = peer_address; + data.streams_info.stream_type.type = CONTENT_TYPE_MEDIA; + data.streams_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; + data.streams_info.stream_type.direction = ASE_DIRECTION_SINK; + data.streams_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + if (peer->IsStereoHsType()) { + SelectCodecQosConfig(peer_address, BAP, MEDIA_CONTEXT, SNK, STEREO_HS_CONFIG_1); + } else { + SelectCodecQosConfig(peer_address, BAP, MEDIA_CONTEXT, SNK, EB_CONFIG); + } + codec_qos_cfg = peer->get_peer_media_codec_qos_config(); + data.streams_info.codec_qos_config_pair.push_back(codec_qos_cfg); + } else if ((profileType == WMCP) && (peer->GetContextType() & CONTEXT_TYPE_MUSIC)) { + data.bd_addr = peer_address; + data.streams_info.stream_type.type = CONTENT_TYPE_MEDIA; + data.streams_info.stream_type.audio_context = CONTENT_TYPE_LIVE; //Live Audio Context + data.streams_info.stream_type.direction = ASE_DIRECTION_SRC; + data.streams_info.reconf_type = bluetooth::bap::ucast::StreamReconfigType::CODEC_CONFIG; + if (peer->IsStereoHsType()) { + SelectCodecQosConfig(peer_address, WMCP, MEDIA_CONTEXT, SRC, STEREO_HS_CONFIG_1); + } else { + SelectCodecQosConfig(peer_address, WMCP, MEDIA_CONTEXT, SRC, EB_CONFIG); + } + codec_qos_cfg = peer->get_peer_media_codec_qos_config(); + data.streams_info.codec_qos_config_pair.push_back(codec_qos_cfg); + } else if ((profileType == BAP_CALL) && (peer->GetContextType() & CONTEXT_TYPE_VOICE)) { + data.bd_addr = peer_address; + data.streams_info.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + } + + peer->SetRcfgProfileType(profileType); + if (profileType != BAP_CALL) { + print_codec_parameters(codec_qos_cfg.codec_config); + print_qos_parameters(codec_qos_cfg.qos_config); + } + btif_transfer_context(btif_acm_handle_evt, BTIF_ACM_RECONFIG_REQ_EVT, (char*)&data, + sizeof(tBTIF_ACM_RECONFIG), NULL); + return true; +} + +static void cleanup_acm_initiator(void) { + BTIF_TRACE_EVENT("%s", __func__); + do_in_bta_thread(FROM_HERE, Bind(&BtifAcmInitiator::Cleanup, + base::Unretained(&btif_acm_initiator))); +} + +static const btacm_initiator_interface_t bt_acm_initiator_interface = { + sizeof(btacm_initiator_interface_t), + init_acm_initiator, + connect_acm_initiator, + disconnect_acm_initiator, + set_active_acm_initiator, + start_stream_acm_initiator, + stop_stream_acm_initiator, + codec_config_acm_initiator, + change_codec_config_acm_initiator, + cleanup_acm_initiator, +}; + +RawAddress btif_acm_initiator_music_active_peer(void) { + return btif_acm_initiator.MusicActivePeer(); +} + +RawAddress btif_acm_initiator_voice_active_peer(void) { + return btif_acm_initiator.VoiceActivePeer(); +} + +bool btif_acm_request_csip_lock(uint8_t setId) { + LOG_INFO(LOG_TAG, "%s", __func__); + tBTA_CSIP_CSET cset_info; // need to do memset ? + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(setId); + /*if (cset_info.p_srvc_uuid != ACM_UUID) { + return false; + }*/ + if (cset_info.size > cset_info.total_discovered) { + LOG_INFO(LOG_TAG, "%s not complete set discovered yet. size = %d discovered = %d", + __func__, cset_info.size, cset_info.total_discovered); + } + if (setId == cset_info.set_id) { + LOG_INFO(LOG_TAG, "%s correct set id", __func__); + } else { + return false; + } + + btif_acm_check_and_cancel_lock_release_timer(setId); + + //Aquire lock for entire group. + tBTA_SET_LOCK_PARAMS lock_params; //need to do memset ? + lock_params.app_id = btif_acm_initiator.GetCsipAppId(); + lock_params.set_id = cset_info.set_id; + lock_params.lock_value = LOCK_VALUE;//For lock + lock_params.members_addr = cset_info.set_members; + BTA_CsipSetLockValue (lock_params); + btif_acm_initiator.SetLockFlags(BtifAcmInitiator::kFlagStatusPendingLock); + btif_acm_initiator.SetOrUpdateGroupLockStatus(cset_info.set_id, + btif_acm_initiator.CheckLockFlags(BtifAcmInitiator::kFlagStatusPendingLock)); + return true; +} + +bool btif_acm_request_csip_unlock(uint8_t setId) { + tBTA_CSIP_CSET cset_info; // need to do memset ? + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(setId); + /*if (cset_info.p_srvc_uuid != Uuid::FromString("2B86")) { + return false; + }*/ + if (cset_info.size > cset_info.total_discovered) { + LOG_INFO(LOG_TAG, "%s not complete set discovered yet. size = %d discovered = %d", + __func__, cset_info.size, cset_info.total_discovered); + } + if (setId == cset_info.set_id) { + LOG_INFO(LOG_TAG, "%s correct app id", __func__); + } else { + return false; + } + //Aquire lock for entire group. + tBTA_SET_LOCK_PARAMS lock_params; //need to do memset ? + lock_params.app_id = btif_acm_initiator.GetCsipAppId(); + lock_params.set_id = cset_info.set_id; + lock_params.lock_value = UNLOCK_VALUE;//For Unlock + lock_params.members_addr = cset_info.set_members; + BTA_CsipSetLockValue (lock_params); + btif_acm_initiator.SetLockFlags(BtifAcmInitiator::kFlagStatusPendingUnlock); + btif_acm_initiator.SetOrUpdateGroupLockStatus(cset_info.set_id, + btif_acm_initiator.CheckLockFlags(BtifAcmInitiator::kFlagStatusPendingUnlock)); + return true; +} + +bool btif_acm_is_call_active(void) { + BtifAcmPeer* peer = nullptr; + peer = btif_acm_initiator.FindPeer(btif_acm_initiator.VoiceActivePeer()); + if (peer != nullptr && (peer->IsStreaming() || peer->CheckFlags(BtifAcmPeer::kFlagPendingStart)) && + (peer->GetStreamContextType() == CONTEXT_TYPE_VOICE)) + return true; + + return false; +} + +void btif_acm_stream_start(void) { + LOG_INFO(LOG_TAG, "%s", __func__); + if (!btif_acm_initiator.Enabled()) + return; + bool ret = false; + if (false/*btif_acm_initiator.IsCsipRegistered() && (btif_acm_initiator.MusicActiveCSetId() != INVALID_SET_ID)*/) { + ret = btif_acm_request_csip_lock(btif_acm_initiator.MusicActiveCSetId()); + if (ret == false) { + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_FAILURE); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_FAILURE); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + return; + } + //call below in lock changed success CB + //should be dispatched to list of peers in active music group. + btif_acm_check_and_start_group_procedure_timer(btif_acm_initiator.MusicActiveCSetId()); + } else { + int id = btif_acm_initiator.MusicActiveCSetId(); + if (id < INVALID_SET_ID) { + bool send_neg_ack = true; + tBTA_CSIP_CSET cset_info; + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(id); + std::vector::iterator itr; + BTIF_TRACE_DEBUG("%s: size of set members %d", __func__, (cset_info.set_members).size()); + if ((cset_info.set_members).size() > 0) { + for (itr =(cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + BTIF_TRACE_DEBUG("%s: Sending start request ", __func__); + BtifAcmPeer* p = btif_acm_initiator.FindPeer(*itr); + if (p != nullptr && p->IsConnected()) { + send_neg_ack = false; + p->SetStreamContextType(CONTEXT_TYPE_MUSIC); + btif_acm_initiator_dispatch_sm_event(*itr, BTIF_ACM_START_STREAM_REQ_EVT); + } + } + } + if (send_neg_ack) { + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + return; + } + btif_acm_check_and_start_group_procedure_timer(btif_acm_initiator.MusicActiveCSetId()); + } else { + BTIF_TRACE_DEBUG("%s: Sending start to twm device ", __func__); + BtifAcmPeer* p = btif_acm_initiator.FindPeer(btif_acm_initiator_music_active_peer()); + + if (p != nullptr && p->IsStreaming()) { + BTIF_TRACE_DEBUG("%s: Already streaming ongoing", __func__); + btif_acm_on_started(A2DP_CTRL_ACK_SUCCESS); + return; + } + + if (p != nullptr && p->IsConnected()) { + p->SetStreamContextType(CONTEXT_TYPE_MUSIC); + btif_acm_initiator_dispatch_sm_event(btif_acm_initiator_music_active_peer(), BTIF_ACM_START_STREAM_REQ_EVT); + } else { + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + } + } + } +} + +void btif_acm_stream_stop(void) { + LOG_INFO(LOG_TAG, "%s ", __func__); + bool ret = false; + if (false /*btif_acm_initiator.IsCsipRegistered() && (btif_acm_initiator.MusicActiveCSetId() != INVALID_SET_ID)*/) { + ret = btif_acm_request_csip_lock(btif_acm_initiator.MusicActiveCSetId()); + if (ret == false) { + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_FAILURE); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_FAILURE); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + return; + } + btif_acm_check_and_start_group_procedure_timer(btif_acm_initiator.MusicActiveCSetId()); + } else { + BTIF_TRACE_DEBUG("%s: Sending stop to twm device ", __func__); + BtifAcmPeer* p = btif_acm_initiator.FindPeer(btif_acm_initiator_music_active_peer()); + if (p != nullptr && p->IsConnected()) { + p->SetStreamContextType(CONTEXT_TYPE_MUSIC); + btif_acm_initiator_dispatch_sm_event(btif_acm_initiator_music_active_peer(), BTIF_ACM_STOP_STREAM_REQ_EVT); + } else { + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + } + } +} + +void btif_acm_stream_suspend(void) { + LOG_INFO(LOG_TAG, "%s", __func__); + if (!btif_acm_initiator.Enabled()) + return; + bool ret = false; + if (false /*btif_acm_initiator.IsCsipRegistered() && (btif_acm_initiator.MusicActiveCSetId() != INVALID_SET_ID)*/) { + ret = btif_acm_request_csip_lock(btif_acm_initiator.MusicActiveCSetId()); + //call below in lock changed success CB. + //should be dispatched to list of peers in active music group. + if (ret == false) { + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_FAILURE); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_FAILURE); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + return; + } + btif_acm_check_and_start_group_procedure_timer(btif_acm_initiator.MusicActiveCSetId()); + } else { + int id = btif_acm_initiator.MusicActiveCSetId(); + if (id < INVALID_SET_ID) { + bool send_neg_ack = true; + tBTA_CSIP_CSET cset_info; // need to do memset ? + memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); + cset_info = BTA_CsipGetCoordinatedSet(id); + std::vector::iterator itr; + BTIF_TRACE_DEBUG("%s: size of set members %d", __func__, (cset_info.set_members).size()); + if ((cset_info.set_members).size() > 0) { + for (itr =(cset_info.set_members).begin(); itr != (cset_info.set_members).end(); itr++) { + BTIF_TRACE_DEBUG("%s: Sending suspend request ", __func__); + BtifAcmPeer* p = btif_acm_initiator.FindPeer(*itr); + if (p != nullptr && p->IsConnected()) { + send_neg_ack = false; + p->SetStreamContextType(CONTEXT_TYPE_MUSIC); + btif_acm_initiator_dispatch_sm_event(*itr, BTIF_ACM_SUSPEND_STREAM_REQ_EVT); + } + } + } + if (send_neg_ack) { + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + return; + } + btif_acm_check_and_start_group_procedure_timer(btif_acm_initiator.MusicActiveCSetId()); + } else { + BTIF_TRACE_DEBUG("%s: Sending suspend to twm device ", __func__); + BtifAcmPeer* p = btif_acm_initiator.FindPeer(btif_acm_initiator_music_active_peer()); + if (p != nullptr && p->IsConnected()) { + p->SetStreamContextType(CONTEXT_TYPE_MUSIC); + btif_acm_initiator_dispatch_sm_event(btif_acm_initiator_music_active_peer(), BTIF_ACM_SUSPEND_STREAM_REQ_EVT); + } else { + tA2DP_CTRL_CMD pending_cmd = btif_ahim_get_pending_command(); + if (pending_cmd == A2DP_CTRL_CMD_STOP || + pending_cmd == A2DP_CTRL_CMD_SUSPEND) { + btif_acm_source_on_suspended(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } else if (pending_cmd == A2DP_CTRL_CMD_START) { + btif_acm_on_started(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + } else { + BTIF_TRACE_DEBUG("%s: no pending command to ack mm audio", __func__); + } + } + } + } +} + +void btif_acm_disconnect(const RawAddress& peer_address, int context_type) { + LOG_INFO(LOG_TAG, "%s: peer %s", __func__, peer_address.ToString().c_str()); + disconnect_acm_initiator(peer_address, context_type); +} + +static void btif_acm_initiator_dispatch_sm_event(const RawAddress& peer_address, + btif_acm_sm_event_t event) { + BtifAcmEvent btif_acm_event(event, nullptr, 0); + BTIF_TRACE_EVENT("%s: peer_address=%s event=%s", __func__, + peer_address.ToString().c_str(), + btif_acm_event.ToString().c_str()); + + btif_transfer_context(btif_acm_handle_evt, event, (char *)&peer_address, + sizeof(RawAddress), NULL); +} + +bt_status_t btif_acm_initiator_execute_service(bool enable) { + BTIF_TRACE_EVENT("%s: service: %s", __func__, + (enable) ? "enable" : "disable"); + + if (enable) { + BTA_RegisterCsipApp(bta_csip_callback, base::Bind([](uint8_t status, uint8_t app_id) { + if (status != BTA_CSIP_SUCCESS) { + LOG(ERROR) << "Can't register CSIP module "; + return; + } + BTIF_TRACE_DEBUG("App ID: %d", app_id); + btif_acm_initiator.SetCsipAppId(app_id); + btif_acm_initiator.SetCsipRegistration(true);} )); + return BT_STATUS_SUCCESS; + } + + // Disable the service + //BTA_UnregisterCsipApp(); + return BT_STATUS_FAIL; +} + +// Get the ACM callback interface for ACM Initiator profile +const btacm_initiator_interface_t* btif_acm_initiator_get_interface(void) { + BTIF_TRACE_EVENT("%s", __func__); + return &bt_acm_initiator_interface; +} + +uint16_t btif_acm_get_active_device_latency() { + BtifAcmPeer* peer = btif_acm_initiator.FindMusicActivePeer(); + if (peer == nullptr) { + BTIF_TRACE_WARNING("%s: peer is NULL", __func__); + return 0; + } else { + return peer->GetPeerLatency(); + } +} + +static void SelectCodecQosConfig(const RawAddress& bd_addr, + int profile_type, int context_type, + int direction, int config_type) { + + BTIF_TRACE_DEBUG("%s: Peer %s , context type: %d, profile_type: %d," + " direction: %d config_type %d", __func__, + bd_addr.ToString().c_str(), context_type, + profile_type, direction, config_type); + + BtifAcmPeer* peer = btif_acm_initiator.FindPeer(bd_addr); + if (peer == nullptr) { + BTIF_TRACE_WARNING("%s: peer is NULL", __func__); + return; + } + + uint8_t CigId = peer->CigId(); + uint8_t set_size = 0; + tBTA_CSIP_CSET cset_info; + memset(&cset_info, 0, sizeof(cset_info)); + cset_info = BTA_CsipGetCoordinatedSet(peer->SetId()); + BTIF_TRACE_DEBUG("%s: cset members size: %d", + __func__, (uint8_t)(cset_info.size)); + + if (cset_info.size == 0) { + BTIF_TRACE_WARNING("%s: this shud be case for stereo-HS, config_type %d", + __func__, config_type); + set_size = (config_type == STEREO_HS_CONFIG_1) ? 2 : 1; + } else { + set_size = cset_info.size; + } + + CodecConfig codec_config_; + CodecQosConfig codec_qos_config; + QosConfig qos_configs; + CISConfig cis_config; + std::vector vmcp_qos_config; + BTIF_TRACE_WARNING("%s: going for best config", __func__); + memset(&codec_config_, 0, sizeof(codec_config_)); + select_best_codec_config(bd_addr, context_type, profile_type, + &codec_config_, direction, config_type); + codec_qos_config.codec_config = codec_config_; + BTIF_TRACE_DEBUG("%s: sample rate : %d, frame_duration: %d, octets: %d, ", + __func__, static_cast(codec_config_.sample_rate), + GetFrameDuration(&codec_config_), + GetOctsPerFrame(&codec_config_)); + + if (context_type == MEDIA_CONTEXT) { + if (profile_type != WMCP) { + vmcp_qos_config = get_qos_params_for_codec(profile_type, + MEDIA_LL_CONTEXT, + codec_config_.sample_rate, + GetFrameDuration(&codec_config_), + GetOctsPerFrame(&codec_config_)); + } else { + vmcp_qos_config = get_qos_params_for_codec(profile_type, + MEDIA_HR_CONTEXT, + codec_config_.sample_rate, + GetFrameDuration(&codec_config_), + GetOctsPerFrame(&codec_config_)); + } + } else if (context_type == VOICE_CONTEXT) { + vmcp_qos_config = get_qos_params_for_codec(profile_type, + VOICE_CONTEXT, + codec_config_.sample_rate, + GetFrameDuration(&codec_config_), + GetOctsPerFrame(&codec_config_)); + } + BTIF_TRACE_DEBUG("%s: vmcp qos size: %d", + __func__, (uint8_t)vmcp_qos_config.size()); + + bool qhs_enable = false; + char qhs_value[PROPERTY_VALUE_MAX] = "false"; + property_get("persist.vendor.btstack.qhs_enable", qhs_value, "false"); + if (!strncmp("true", qhs_value, 4)) { + if (btm_acl_qhs_phy_supported(bd_addr, BT_TRANSPORT_LE)) { + qhs_enable = true; + } + } else { + qhs_enable = false; + } + + //TODO: fill cig id and cis count from + //Currently it is a single size vector + for (uint8_t j = 0; j < (uint8_t)vmcp_qos_config.size(); j++) { + if (vmcp_qos_config[j].mandatory == 0) { + uint32_t sdu_interval = vmcp_qos_config[j].sdu_int_micro_secs; + codec_qos_config.qos_config.cig_config = { + .cig_id = CigId, + .cis_count = set_size, + .packing = 0x01, // interleaved + .framing = vmcp_qos_config[j].framing, // unframed + .max_tport_latency_m_to_s = vmcp_qos_config[j].max_trans_lat, + .max_tport_latency_s_to_m = vmcp_qos_config[j].max_trans_lat, + .sdu_interval_m_to_s = { + static_cast(sdu_interval & 0xFF), + static_cast((sdu_interval >> 8)& 0xFF), + static_cast((sdu_interval >> 16)& 0xFF) + }, + .sdu_interval_s_to_m = { + static_cast(sdu_interval & 0xFF), + static_cast((sdu_interval >> 8)& 0xFF), + static_cast((sdu_interval >> 16)& 0xFF) + } + }; + BTIF_TRACE_DEBUG("%s: framing: %d, transport latency: %d" + " sdu_interval: %d", __func__, + vmcp_qos_config[j].framing, + vmcp_qos_config[j].max_trans_lat, + vmcp_qos_config[j].sdu_int_micro_secs); + BTIF_TRACE_DEBUG("%s: CIG: packing: %d, transport latency m to s: %d," + " transport latency s to m: %d", __func__, + codec_qos_config.qos_config.cig_config.packing, + codec_qos_config.qos_config.cig_config.max_tport_latency_m_to_s, + codec_qos_config.qos_config.cig_config.max_tport_latency_s_to_m); + BTIF_TRACE_DEBUG("%s: Filled CIG config ", __func__); + } + } + + for (uint8_t i = 0; i < set_size; i++) { + //Currently it is a single size vector + uint8_t check_memset = 0; + for (uint8_t j = 0; j < (uint8_t)vmcp_qos_config.size(); j++) { + if (vmcp_qos_config[j].mandatory == 0) { + memset(&cis_config, 0, sizeof(cis_config)); + if (!check_memset) + check_memset = 1; + cis_config.cis_id = i; + if (profile_type != WMCP) + cis_config.max_sdu_m_to_s = vmcp_qos_config[j].max_sdu_size; + else + cis_config.max_sdu_m_to_s = 0; + if ((context_type == VOICE_CONTEXT) || (profile_type == WMCP)) + cis_config.max_sdu_s_to_m = vmcp_qos_config[j].max_sdu_size; + else + cis_config.max_sdu_s_to_m = 0; + + BTIF_TRACE_DEBUG("%s: qhs_enable: %d", __func__, qhs_enable); + + if (qhs_enable) { + cis_config.phy_m_to_s = LE_QHS_PHY; + cis_config.phy_s_to_m = LE_QHS_PHY; + } else { + cis_config.phy_m_to_s = LE_2M_PHY;//2mbps + cis_config.phy_s_to_m = LE_2M_PHY; + } + cis_config.rtn_m_to_s = vmcp_qos_config[j].retrans_num; + cis_config.rtn_s_to_m = vmcp_qos_config[j].retrans_num; + } + } + if (!check_memset) + memset(&cis_config, 0, sizeof(cis_config)); + codec_qos_config.qos_config.cis_configs.push_back(cis_config); + BTIF_TRACE_DEBUG("%s: Filled CIS config for %d", __func__, i); + } + + for (uint8_t j = 0; j < (uint8_t)vmcp_qos_config.size(); j++) { + if (vmcp_qos_config[j].mandatory == 0) { + uint32_t presen_delay = vmcp_qos_config[j].presentation_delay; + ASCSConfig ascs_config_1 = { + .cig_id = CigId, + .cis_id = peer->CisId(), + .target_latency = 0x03,//Target higher reliability + .bi_directional = false, + .presentation_delay = {static_cast(presen_delay & 0xFF), + static_cast((presen_delay >> 8)& 0xFF), + static_cast((presen_delay >> 16)& 0xFF)} + }; + codec_qos_config.qos_config.ascs_configs.push_back(ascs_config_1); + BTIF_TRACE_DEBUG("%s: presentation delay = %d", __func__, presen_delay); + BTIF_TRACE_DEBUG("%s: Filled ASCS config for %d", __func__, ascs_config_1.cis_id); + if (config_type == STEREO_HS_CONFIG_1) { + ASCSConfig ascs_config_2 = ascs_config_1; + ascs_config_2.cis_id = peer->CisId()+1; + codec_qos_config.qos_config.ascs_configs.push_back(ascs_config_2); + BTIF_TRACE_DEBUG("%s: Filled ASCS config for %d", __func__, ascs_config_2.cis_id); + } + } + } + + if (profile_type == BAP) { + if (context_type == VOICE_CONTEXT) { + if (direction == SNK) { + codec_qos_config.qos_config.cig_config.cig_id = CigId + 2; + codec_qos_config.qos_config.ascs_configs[0].cig_id = CigId + 2; + codec_qos_config.qos_config.ascs_configs[0].target_latency = 0x01; + codec_qos_config.qos_config.ascs_configs[0].bi_directional = true; + if (config_type == STEREO_HS_CONFIG_1) { + codec_qos_config.qos_config.ascs_configs[1].cig_id = CigId + 2; + codec_qos_config.qos_config.ascs_configs[1].target_latency = 0x01; + codec_qos_config.qos_config.ascs_configs[1].bi_directional = true; + } + peer->set_peer_voice_tx_codec_config(codec_config_); + peer->set_peer_voice_tx_qos_config(codec_qos_config.qos_config); + peer->set_peer_voice_tx_codec_qos_config(codec_qos_config); + } else if (direction == SRC) { + codec_qos_config.qos_config.cig_config.cig_id = CigId + 2; + codec_qos_config.qos_config.ascs_configs[0].cig_id = CigId + 2; + codec_qos_config.qos_config.ascs_configs[0].target_latency = 0x01; + codec_qos_config.qos_config.ascs_configs[0].bi_directional = true; + if (config_type == STEREO_HS_CONFIG_1) { + codec_qos_config.qos_config.ascs_configs[1].cig_id = CigId + 2; + codec_qos_config.qos_config.ascs_configs[1].target_latency = 0x01; + codec_qos_config.qos_config.ascs_configs[1].bi_directional = true; + } + peer->set_peer_voice_rx_codec_config(codec_config_); + peer->set_peer_voice_rx_qos_config(codec_qos_config.qos_config); + peer->set_peer_voice_rx_codec_qos_config(codec_qos_config); + } + } else { + peer->set_peer_media_codec_config(codec_config_); + peer->set_peer_media_qos_config(codec_qos_config.qos_config); + peer->set_peer_media_codec_qos_config(codec_qos_config); + } + } else if (profile_type == GCP) { + if (context_type == VOICE_CONTEXT) { + codec_qos_config.qos_config.cig_config.cig_id = CigId + 1; + codec_qos_config.qos_config.ascs_configs[0].cig_id = CigId + 1; + codec_qos_config.qos_config.ascs_configs[0].target_latency = 0x01; + codec_qos_config.qos_config.ascs_configs[0].bi_directional = true; + peer->set_peer_voice_rx_codec_config(codec_config_); + peer->set_peer_voice_rx_qos_config(codec_qos_config.qos_config); + peer->set_peer_voice_rx_codec_qos_config(codec_qos_config); + } else { + peer->set_peer_media_codec_config(codec_config_); + peer->set_peer_media_qos_config(codec_qos_config.qos_config); + peer->set_peer_media_codec_qos_config(codec_qos_config); + } + } else if (profile_type == WMCP) { + if (context_type == MEDIA_CONTEXT) { + codec_qos_config.qos_config.cig_config.cig_id = CigId + 3; + codec_qos_config.qos_config.ascs_configs[0].cig_id = CigId + 3; + if (config_type == STEREO_HS_CONFIG_1) + codec_qos_config.qos_config.ascs_configs[1].cig_id = CigId + 3; + peer->set_peer_media_codec_config(codec_config_); + peer->set_peer_media_qos_config(codec_qos_config.qos_config); + peer->set_peer_media_codec_qos_config(codec_qos_config); + } + } + //print_codec_parameters(codec_config_); + //print_qos_parameters(codec_qos_config.qos_config); +} + +static bool select_best_sample_rate(uint16_t samp_freq, CodecConfig *result_config) { + BTIF_TRACE_DEBUG("%s: samp_freq: %d", __func__, samp_freq); + if (samp_freq & static_cast(CodecSampleRate::CODEC_SAMPLE_RATE_48000)) { + result_config->sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_48000; + return true; + } + if (samp_freq & static_cast(CodecSampleRate::CODEC_SAMPLE_RATE_44100)) { + result_config->sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_44100; + return true; + } + if (samp_freq & static_cast(CodecSampleRate::CODEC_SAMPLE_RATE_32000)) { + result_config->sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_32000; + return true; + } + if (samp_freq & static_cast(CodecSampleRate::CODEC_SAMPLE_RATE_24000)) { + result_config->sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_24000; + return true; + } + if (samp_freq & static_cast(CodecSampleRate::CODEC_SAMPLE_RATE_16000)) { + result_config->sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_16000; + return true; + } + if (samp_freq & static_cast(CodecSampleRate::CODEC_SAMPLE_RATE_8000)) { + result_config->sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_8000; + return true; + } + return false; +} + +static bool select_best_frame_dura(uint8_t frame_dura, + CodecConfig *result_config) { + BTIF_TRACE_DEBUG("%s: frame_duration: %d", __func__, frame_dura); + if (frame_dura & static_cast(CodecFrameDuration::FRAME_DUR_10)) { + BTIF_TRACE_DEBUG("%s: selecting 10ms as best frame duration", __func__); + UpdateFrameDuration(result_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + return true; + } + + if ((frame_dura & + static_cast(CodecFrameDuration::FRAME_DUR_7_5)) == 0) { + BTIF_TRACE_DEBUG("%s: selecting 7.5ms as best frame duration", __func__); + UpdateFrameDuration(result_config, + static_cast(CodecFrameDuration::FRAME_DUR_7_5)); + return true; + } + return true; +} + +void select_best_codec_config(const RawAddress& bd_addr, + uint16_t context_type, + uint8_t profile_type, + CodecConfig *codec_config, + int dir, int config_type) { + + BTIF_TRACE_DEBUG("%s: select best codec config for context type: %d," + " profile type %d config_type %d", __func__, + context_type, profile_type, config_type); + + CodecConfig result_codec_config; + uint16_t vmcp_samp_freq = 0; + uint8_t vmcp_fram_dur = 0; + uint32_t vmcp_octets_per_frame = 0; + std::vector pac_record; + std::vector local_codec_config; + memset(&result_codec_config, 0, sizeof(result_codec_config)); + bool pac_found = false; + uint16_t audio_context_type = CONTENT_TYPE_UNSPECIFIED; + + bool is_lc3q_supported = false; + char lc3q_value[PROPERTY_VALUE_MAX] = "false"; + property_get("persist.vendor.service.bt.is_lc3q_supported", lc3q_value, "false"); + if (!strncmp("true", lc3q_value, 4)) { + is_lc3q_supported = true; + } else { + is_lc3q_supported = false; + } + BTIF_TRACE_IMP("%s: is_lc3q_supported: %d", __func__, is_lc3q_supported); + + if (context_type == MEDIA_CONTEXT) { + audio_context_type |= + (profile_type == WMCP) ? CONTENT_TYPE_LIVE : CONTENT_TYPE_MEDIA; + } else if (context_type == VOICE_CONTEXT) { + audio_context_type |= CONTENT_TYPE_CONVERSATIONAL; + } + BTIF_TRACE_IMP("%s: audio_context_type: %d", __func__, audio_context_type); + + pac_found = btif_bap_get_records(bd_addr, REC_TYPE_CAPABILITY, audio_context_type, + ((dir == SRC) ? CodecDirection::CODEC_DIR_SRC : CodecDirection::CODEC_DIR_SINK), + &pac_record); + + if (pac_found) { + BTIF_TRACE_DEBUG("%s: PAC record found, select best codec config", __func__); + uint16_t peer_samp_freq = 0; + uint8_t peer_channel_mode = 0; + uint8_t peer_fram_dur = 0; + uint16_t peer_min_octets_per_frame = 0; + uint16_t peer_max_octets_per_frame = 0; + uint8_t peer_max_sup_lc3_frames = 0; + uint16_t peer_preferred_context = 0; + bool peer_lc3q_pref = 0; + uint8_t peer_lc3q_ver = 0; + + //currently differentiating based on frequency later we will do on context type + for (auto it = pac_record.begin(); it != pac_record.end(); ++it) { + if (it->codec_type == CodecIndex::CODEC_INDEX_SOURCE_LC3) { + //performing only for MUSIC context type based on 44.1KHz and 48KHz + BTIF_TRACE_DEBUG("%s: pac_record sample_rate: %d", __func__, it->sample_rate); + if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_48000) { + peer_samp_freq |= static_cast(it->sample_rate); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_44100) { + peer_samp_freq |= static_cast(it->sample_rate); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_32000) { + peer_samp_freq |= static_cast(it->sample_rate); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_24000) { + peer_samp_freq |= static_cast(it->sample_rate); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_16000) { + peer_samp_freq |= static_cast(it->sample_rate); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_8000) { + peer_samp_freq |= static_cast(it->sample_rate); + } + } + } + + local_codec_config = get_all_codec_configs(profile_type, context_type); + BTIF_TRACE_DEBUG("%s: vmcp codec size: %d", + __func__, (uint8_t)local_codec_config.size()); + for (auto it = local_codec_config.begin(); + it != local_codec_config.end(); ++it) { + if (it->codec_type == CodecIndex::CODEC_INDEX_SOURCE_LC3) { + BTIF_TRACE_DEBUG("%s: local_codec_config sample_rate: %d", + __func__, it->sample_rate); + if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_48000) { + vmcp_samp_freq |= static_cast(it->sample_rate); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_44100) { + vmcp_samp_freq |= static_cast(it->sample_rate); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_32000) { + vmcp_samp_freq |= static_cast(it->sample_rate); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_24000) { + vmcp_samp_freq |= static_cast(it->sample_rate); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_16000) { + vmcp_samp_freq |= static_cast(it->sample_rate); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_8000) { + vmcp_samp_freq |= static_cast(it->sample_rate); + } + } + } + + select_best_sample_rate(peer_samp_freq & vmcp_samp_freq, + &result_codec_config); + for (auto it = pac_record.begin(); it != pac_record.end(); ++it) { + if (it->codec_type == CodecIndex::CODEC_INDEX_SOURCE_LC3) { + BTIF_TRACE_DEBUG("%s: pac_record sample_rate: %d," + " result_codec_config.sample_rate: %d", + __func__, it->sample_rate, + result_codec_config.sample_rate); + if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_48000 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_48000) { + BTIF_TRACE_DEBUG("%s: selecting 48KHz for config", __func__); + peer_channel_mode = static_cast(it->channel_mode); + peer_fram_dur = GetCapaSupFrameDurations(&(*it)); + peer_min_octets_per_frame = GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF; + peer_max_octets_per_frame = (GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF0000) >> 16; + peer_max_sup_lc3_frames = GetCapaMaxSupLc3Frames(&(*it)); + peer_lc3q_pref = GetCapaVendorMetaDataLc3QPref(&(*it)); + peer_lc3q_ver = GetCapaVendorMetaDataLc3QVer(&(*it)); + peer_preferred_context = GetCapaPreferredContexts(&(*it)); + break; + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_44100 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_44100) { + BTIF_TRACE_DEBUG("%s: selecting 44.1KHz for config", __func__); + peer_channel_mode = static_cast(it->channel_mode); + peer_fram_dur = GetCapaSupFrameDurations(&(*it)); + peer_min_octets_per_frame = GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF; + peer_max_octets_per_frame = (GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF0000) >> 16; + peer_max_sup_lc3_frames = GetCapaMaxSupLc3Frames(&(*it)); + peer_lc3q_pref = GetCapaVendorMetaDataLc3QPref(&(*it)); + peer_lc3q_ver = GetCapaVendorMetaDataLc3QVer(&(*it)); + peer_preferred_context = GetCapaPreferredContexts(&(*it)); + break; + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_32000 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_32000) { + BTIF_TRACE_DEBUG("%s: selecting 32KHz for config", __func__); + peer_channel_mode = static_cast(it->channel_mode); + peer_fram_dur = GetCapaSupFrameDurations(&(*it)); + peer_min_octets_per_frame = GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF; + peer_max_octets_per_frame = (GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF0000) >> 16; + peer_max_sup_lc3_frames = GetCapaMaxSupLc3Frames(&(*it)); + peer_lc3q_pref = GetCapaVendorMetaDataLc3QPref(&(*it)); + peer_lc3q_ver = GetCapaVendorMetaDataLc3QVer(&(*it)); + peer_preferred_context = GetCapaPreferredContexts(&(*it)); + break; + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_24000 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_24000) { + BTIF_TRACE_DEBUG("%s: selecting 24KHz for config", __func__); + peer_channel_mode = static_cast(it->channel_mode); + peer_fram_dur = GetCapaSupFrameDurations(&(*it)); + peer_min_octets_per_frame = GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF; + peer_max_octets_per_frame = (GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF0000) >> 16; + peer_max_sup_lc3_frames = GetCapaMaxSupLc3Frames(&(*it)); + peer_lc3q_pref = GetCapaVendorMetaDataLc3QPref(&(*it)); + peer_lc3q_ver = GetCapaVendorMetaDataLc3QVer(&(*it)); + peer_preferred_context = GetCapaPreferredContexts(&(*it)); + break; + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_16000 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_16000) { + BTIF_TRACE_DEBUG("%s: selecting 16KHz for config", __func__); + peer_channel_mode = static_cast(it->channel_mode); + peer_fram_dur = GetCapaSupFrameDurations(&(*it)); + peer_min_octets_per_frame = GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF; + peer_max_octets_per_frame = (GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF0000) >> 16; + peer_max_sup_lc3_frames = GetCapaMaxSupLc3Frames(&(*it)); + peer_lc3q_pref = GetCapaVendorMetaDataLc3QPref(&(*it)); + peer_lc3q_ver = GetCapaVendorMetaDataLc3QVer(&(*it)); + peer_preferred_context = GetCapaPreferredContexts(&(*it)); + break; + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_8000 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_8000) { + BTIF_TRACE_DEBUG("%s: selecting 8KHz for config", __func__); + peer_channel_mode = static_cast(it->channel_mode); + peer_fram_dur = GetCapaSupFrameDurations(&(*it)); + peer_min_octets_per_frame = GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF; + peer_max_octets_per_frame = (GetCapaSupOctsPerFrame(&(*it)) & 0xFFFF0000) >> 16; + peer_max_sup_lc3_frames = GetCapaMaxSupLc3Frames(&(*it)); + peer_lc3q_pref = GetCapaVendorMetaDataLc3QPref(&(*it)); + peer_lc3q_ver = GetCapaVendorMetaDataLc3QVer(&(*it)); + peer_preferred_context = GetCapaPreferredContexts(&(*it)); + break; + } + } + } + + BTIF_TRACE_DEBUG("%s: PAC parameters, peer supported sample_freqncies=%d," + " channel_mode=%d, frame_dura=%d", __func__, + peer_samp_freq, peer_channel_mode, peer_fram_dur); + BTIF_TRACE_DEBUG("%s: PAC parameters, min_octets_per_frame=%d," + " max_octets_per_frame=%d, peer_max_sup_lc3_frames=%d", + __func__, peer_min_octets_per_frame, peer_max_octets_per_frame, + peer_max_sup_lc3_frames); + BTIF_TRACE_DEBUG("%s: PAC parameters, peer_preferred_context=%d", + __func__, peer_preferred_context); + BTIF_TRACE_DEBUG("%s: PAC parameters, peer_lc3q_pref=%d, peer_lc3q_ver=%d", + __func__, peer_lc3q_pref, peer_lc3q_ver); + + local_codec_config = get_all_codec_configs(profile_type, context_type); + BTIF_TRACE_DEBUG("%s: vmcp codec size: %d", + __func__, (uint8_t)local_codec_config.size()); + for (auto it = local_codec_config.begin(); + it != local_codec_config.end(); ++it) { + if (it->codec_type == CodecIndex::CODEC_INDEX_SOURCE_LC3) { + BTIF_TRACE_DEBUG("%s: local_codec_config sample_rate: %d," + " result_codec_config.sample_rate: %d", + __func__, it->sample_rate, result_codec_config.sample_rate); + if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_48000 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_48000) { + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_44100 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_44100) { + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_32000 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_32000) { + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_24000 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_24000) { + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_16000 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_16000) { + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_8000 && + result_codec_config.sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_8000) { + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } + } + } + + if (config_type == STEREO_HS_CONFIG_2) + vmcp_octets_per_frame = vmcp_octets_per_frame * 2; + + BTIF_TRACE_DEBUG("%s: VMCP parameters, sample_freq=%d," + " frame_duration=%d, octets=%d", __func__, + vmcp_samp_freq, vmcp_fram_dur, vmcp_octets_per_frame); + + result_codec_config.codec_type = CodecIndex::CODEC_INDEX_SOURCE_LC3; + result_codec_config.codec_priority = CodecPriority::CODEC_PRIORITY_DEFAULT; + + if (config_type == STEREO_HS_CONFIG_2) { + result_codec_config.channel_mode = CodecChannelMode::CODEC_CHANNEL_MODE_STEREO; + } else { + result_codec_config.channel_mode = CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + } + + select_best_frame_dura((peer_fram_dur >> 1) & vmcp_fram_dur, &result_codec_config); + + if (vmcp_octets_per_frame < peer_min_octets_per_frame || + vmcp_octets_per_frame > peer_max_octets_per_frame) { + BTIF_TRACE_DEBUG("%s: octets per frame is out of bound: %d ", + __func__, vmcp_octets_per_frame); + UpdateOctsPerFrame(&result_codec_config, vmcp_octets_per_frame); + } else { + BTIF_TRACE_DEBUG("%s: octets per frame is in limit update 100 octets: %d ", + __func__, vmcp_octets_per_frame); + UpdateOctsPerFrame(&result_codec_config, vmcp_octets_per_frame);//TODO: make this as peer octets + } + + if (is_lc3q_supported) { + UpdateLc3QPreference(&result_codec_config, true); + } + UpdateCapaMaxSupLc3Frames(&result_codec_config, peer_max_sup_lc3_frames); + UpdateLc3BlocksPerSdu(&result_codec_config, 1); + UpdatePreferredAudioContext(&result_codec_config, audio_context_type); + *codec_config = result_codec_config; + } else { + BTIF_TRACE_DEBUG("%s: PAC record not found, select mandatory config", __func__); + mandatory_codec_selected = true; + std::vector codec_pref_config; + codec_pref_config = get_all_codec_configs(profile_type, context_type); + BTIF_TRACE_DEBUG("%s: vmcp codec size %d", __func__, (uint8_t)codec_pref_config.size()); + for (auto it = codec_pref_config.begin(); it != codec_pref_config.end(); ++it) { + if (it->codec_type == CodecIndex::CODEC_INDEX_SOURCE_LC3) { + if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_48000) { + BTIF_TRACE_DEBUG("%s: selecting 48KHz from VMCP", __func__); + result_codec_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_48000; + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_44100) { + BTIF_TRACE_DEBUG("%s: selecting 44.1KHz from VMCP", __func__); + result_codec_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_44100; + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_32000) { + BTIF_TRACE_DEBUG("%s: selecting 32KHz from VMCP", __func__); + result_codec_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_32000; + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_24000) { + BTIF_TRACE_DEBUG("%s: selecting 24KHz from VMCP", __func__); + result_codec_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_24000; + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_16000) { + BTIF_TRACE_DEBUG("%s: selecting 16KHz from VMCP", __func__); + result_codec_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_16000; + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_8000) { + BTIF_TRACE_DEBUG("%s: selecting 8KHz from VMCP", __func__); + result_codec_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_8000; + vmcp_fram_dur |= GetFrameDuration(&(*it)); + if (vmcp_octets_per_frame < GetOctsPerFrame(&(*it))) + vmcp_octets_per_frame = GetOctsPerFrame(&(*it)); + } + } + } + + if (config_type == STEREO_HS_CONFIG_2) + vmcp_octets_per_frame = vmcp_octets_per_frame * 2; + + BTIF_TRACE_DEBUG("%s: VMCP parameters, frame_duration=%d, octets=%d", + __func__, vmcp_fram_dur, vmcp_octets_per_frame); + + result_codec_config.codec_type = CodecIndex::CODEC_INDEX_SOURCE_LC3; + result_codec_config.codec_priority = CodecPriority::CODEC_PRIORITY_DEFAULT; + if (config_type == STEREO_HS_CONFIG_2) + result_codec_config.channel_mode = CodecChannelMode::CODEC_CHANNEL_MODE_STEREO; + else + result_codec_config.channel_mode = CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //select_best_sample_rate(peer_samp_freq & vmcp_samp_freq, &result_codec_config, context_type); + select_best_frame_dura(vmcp_fram_dur, &result_codec_config); + UpdateOctsPerFrame(&result_codec_config, vmcp_octets_per_frame); + if (is_lc3q_supported) { + UpdateLc3QPreference(&result_codec_config, true); + } + UpdateLc3BlocksPerSdu(&result_codec_config, 1);//currently making it for media case + UpdatePreferredAudioContext(&result_codec_config, audio_context_type); + BTIF_TRACE_DEBUG("%s: saved codec config", __func__); + *codec_config = result_codec_config; + } +} + +uint16_t btif_acm_get_sample_rate() { + if (current_active_config.sample_rate != + CodecSampleRate::CODEC_SAMPLE_RATE_NONE) { + BTIF_TRACE_DEBUG("[ACM]:%s: sample_rate = %d", + __func__, current_active_config.sample_rate); + return static_cast(current_active_config.sample_rate); + } else { + BTIF_TRACE_DEBUG("[ACM]:%s: default sample_rate = %d", + __func__, default_config.sample_rate); + return static_cast(default_config.sample_rate); + } +} + +uint8_t btif_acm_get_ch_mode() { + if (current_active_config.channel_mode != + CodecChannelMode::CODEC_CHANNEL_MODE_NONE) { + BTIF_TRACE_DEBUG("[ACM]:%s: channel mode = %d", + __func__, current_active_config.channel_mode); + return static_cast(current_active_config.channel_mode); + } else { + BTIF_TRACE_DEBUG("[ACM]:%s: channel mode = %d", + __func__, default_config.channel_mode); + return static_cast(default_config.channel_mode); + } +} + +uint32_t btif_acm_get_bitrate() { + //based on bitrate set (100(80kbps), 120 (96kbps), 155 (128kbps)) + uint32_t bitrate = 0; + uint16_t octets = static_cast(GetOctsPerFrame(¤t_active_config)); + BTIF_TRACE_DEBUG("[ACM]:%s: octets = %d",__func__, octets); + + switch (octets) { + case 26: + bitrate = 27800; + break; + + case 30: + if (btif_acm_get_sample_rate() == + (static_cast(CodecSampleRate::CODEC_SAMPLE_RATE_8000))) { + bitrate = 24000; + } else { + bitrate = 32000; + } + break; + + case 40: + bitrate = 32000; + break; + + case 45: + bitrate = 48000; + break; + + case 60: + if (btif_acm_get_sample_rate() == + (static_cast(CodecSampleRate::CODEC_SAMPLE_RATE_24000))) { + bitrate = 48000; + } else { + bitrate = 64000; + } + break; + + case 80: + bitrate = 64000; + break; + + case 98: + case 130: + bitrate = 95550; + break; + + case 75: + case 100: + bitrate = 80000; + break; + + case 90: + case 120: + bitrate = 96000; + break; + + case 117: + case 155: + bitrate = 124000; + break; + + default: + bitrate = 124000; + break; + } + BTIF_TRACE_DEBUG("[ACM]%s: bitrate = %d",__func__,bitrate); + return bitrate; +} + +uint32_t btif_acm_get_octets(uint32_t bit_rate) { + uint32_t octets = 0; + octets = GetOctsPerFrame(¤t_active_config); + BTIF_TRACE_DEBUG("[ACM]%s: octets = %d",__func__,octets); + return octets; +} + +uint16_t btif_acm_get_framelength() { + uint16_t frame_duration; + switch (GetFrameDuration(¤t_active_config)) { + case 0: + frame_duration = 7500; //7.5msec + break; + + case 1: + frame_duration = 10000; //10msec + break; + + default: + frame_duration = 10000; + } + BTIF_TRACE_DEBUG("[ACM]%s: frame duration = %d", + __func__,frame_duration); + return frame_duration; +} + +uint16_t btif_acm_get_current_active_profile() { + return current_active_profile_type; +} +uint8_t btif_acm_get_ch_count() {//update channel mode based on device connection + uint8_t ch_mode = 0; + if (current_active_config.channel_mode == + CodecChannelMode::CODEC_CHANNEL_MODE_STEREO) { + ch_mode = 0x02; + } else if (current_active_config.channel_mode == + CodecChannelMode::CODEC_CHANNEL_MODE_MONO) { + ch_mode = 0x01; + } + BTIF_TRACE_DEBUG("[ACM]%s: channel count = %d",__func__,ch_mode); + return ch_mode; +} + +bool btif_acm_is_codec_type_lc3q() { + BTIF_TRACE_DEBUG("[ACM]%s",__func__); + return GetVendorMetaDataLc3QPref(¤t_active_config); +} + +uint8_t btif_acm_lc3q_ver() { + BTIF_TRACE_DEBUG("[ACM]%s",__func__); + return GetVendorMetaDataLc3QVer(¤t_active_config); +} + +uint16_t btif_acm_bap_to_acm_context(uint16_t bap_context) { + switch (bap_context) { + case CONTENT_TYPE_MEDIA: + case CONTENT_TYPE_LIVE: + return CONTEXT_TYPE_MUSIC; + + case CONTENT_TYPE_CONVERSATIONAL: + return CONTEXT_TYPE_VOICE; + + default: + BTIF_TRACE_DEBUG("%s: Unknown bap context",__func__); + return CONTEXT_TYPE_UNKNOWN; + } +} + +static void btif_debug_acm_peer_dump(int fd, const BtifAcmPeer& peer) { + std::string state_str; + int state = peer.StateMachine().StateId(); + switch (state) { + case BtifAcmStateMachine::kStateIdle: + state_str = "Idle"; + break; + + case BtifAcmStateMachine::kStateOpening: + state_str = "Opening"; + break; + + case BtifAcmStateMachine::kStateOpened: + state_str = "Opened"; + break; + + case BtifAcmStateMachine::kStateStarted: + state_str = "Started"; + break; + + case BtifAcmStateMachine::kStateReconfiguring: + state_str = "Reconfiguring"; + break; + + case BtifAcmStateMachine::kStateClosing: + state_str = "Closing"; + break; + + default: + state_str = "Unknown(" + std::to_string(state) + ")"; + break; + } + + dprintf(fd, " Peer: %s\n", peer.PeerAddress().ToString().c_str()); + dprintf(fd, " Connected: %s\n", peer.IsConnected() ? "true" : "false"); + dprintf(fd, " Streaming: %s\n", peer.IsStreaming() ? "true" : "false"); + dprintf(fd, " State Machine: %s\n", state_str.c_str()); + dprintf(fd, " Flags: %s\n", peer.FlagsToString().c_str()); + +} + +bool compare_codec_config_(CodecConfig &first, CodecConfig &second) { + if (first.codec_type != second.codec_type) { + BTIF_TRACE_DEBUG("[ACM] Codec type mismatch %s",__func__); + return true; + } else if (first.sample_rate != second.sample_rate) { + BTIF_TRACE_DEBUG("[ACM] Sample rate mismatch %s",__func__); + return true; + } else if (first.bits_per_sample != second.bits_per_sample) { + BTIF_TRACE_DEBUG("[ACM] Bits per sample mismatch %s",__func__); + return true; + } else if (first.channel_mode != second.channel_mode) { + BTIF_TRACE_DEBUG("[ACM] Channel mode mismatch %s",__func__); + return true; + } else { + uint8_t frame_first = GetFrameDuration(&first); + uint8_t frame_second = GetFrameDuration(&second); + if (frame_first != frame_second) { + BTIF_TRACE_DEBUG("[ACM] frame duration mismatch %s",__func__); + return true; + } + uint8_t lc3blockspersdu_first = GetLc3BlocksPerSdu(&first); + uint8_t lc3blockspersdu_second = GetLc3BlocksPerSdu(&second); + if (lc3blockspersdu_first != lc3blockspersdu_second) { + BTIF_TRACE_DEBUG("[ACM] LC3blocks per SDU mismatch %s",__func__); + return true; + } + uint16_t octets_first = GetOctsPerFrame(&first); + uint16_t octets_second = GetOctsPerFrame(&second); + if (octets_first != octets_second) { + BTIF_TRACE_DEBUG("[ACM] LC3 octets mismatch %s",__func__); + return true; + } + return false; + } +} + +void print_codec_parameters(CodecConfig config) { + uint8_t frame = GetFrameDuration(&config); + uint8_t lc3blockspersdu = GetLc3BlocksPerSdu(&config); + uint16_t octets = GetOctsPerFrame(&config); + bool vendormetadatalc3qpref = GetCapaVendorMetaDataLc3QPref(&config); + uint8_t vendormetadatalc3qver = GetCapaVendorMetaDataLc3QVer(&config); + LOG_DEBUG( + LOG_TAG, + "codec_type=%d codec_priority=%d " + "sample_rate=0x%x bits_per_sample=0x%x " + "channel_mode=0x%x", + config.codec_type, config.codec_priority, + config.sample_rate, config.bits_per_sample, + config.channel_mode); + LOG_DEBUG( + LOG_TAG, + "frame_duration=%d, lc3_blocks_per_SDU=%d," + " octets_per_frame=%d, vendormetadatalc3qpref=%d," + " vendormetadatalc3qver=%d ", + frame, lc3blockspersdu, octets, + vendormetadatalc3qpref, vendormetadatalc3qver); +} + +void print_qos_parameters(QosConfig qos) { + LOG_DEBUG( + LOG_TAG, + "CIG --> cig_id=%d cis_count=%d " + "packing=%d framing=%d " + "max_tport_latency_m_to_s=%d " + "max_tport_latency_s_to_m=%d " + "sdu_interval_m_to_s[0] = %x " + "sdu_interval_m_to_s[1] = %x " + "sdu_interval_m_to_s[2] = %x ", + qos.cig_config.cig_id, qos.cig_config.cis_count, + qos.cig_config.packing, qos.cig_config.framing, + qos.cig_config.max_tport_latency_m_to_s, + qos.cig_config.max_tport_latency_s_to_m, + qos.cig_config.sdu_interval_m_to_s[0], + qos.cig_config.sdu_interval_m_to_s[1], + qos.cig_config.sdu_interval_m_to_s[2]); + for (auto it = qos.cis_configs.begin(); it != qos.cis_configs.end(); ++it) { + LOG_DEBUG( + LOG_TAG, + "CIS --> cis_id = %d max_sdu_m_to_s = %d " + "max_sdu_s_to_m=%d " + "phy_m_to_s = %d " + "phy_s_to_m = %d " + "rtn_m_to_s = %d " + "rtn_s_to_m = %d", + it->cis_id, it->max_sdu_m_to_s, + it->max_sdu_s_to_m, + it->phy_m_to_s, it->phy_s_to_m, + it->rtn_m_to_s, it->rtn_s_to_m); + } + for (auto it = qos.ascs_configs.begin(); it != qos.ascs_configs.end(); ++it) { + LOG_DEBUG( + LOG_TAG, + "ASCS --> cig_id = %d cis_id = %d " + "target_latency=%d " + "bi_directional = %d " + "presentation_delay[0] = %x " + "presentation_delay[1] = %x " + "presentation_delay[2] = %x ", + it->cig_id, + it->cis_id, + it->target_latency, + it->bi_directional, + it->presentation_delay[0], + it->presentation_delay[1], + it->presentation_delay[2]); + } +} + +static void btif_debug_acm_initiator_dump(int fd) { + bool enabled = btif_acm_initiator.Enabled(); + + dprintf(fd, "\nA2DP Source State: %s\n", (enabled) ? "Enabled" : "Disabled"); + if (!enabled) return; + //dprintf(fd, " Active peer: %s\n", + // btif_acm_initiator.ActivePeer().ToString().c_str()); + for (auto it : btif_acm_initiator.Peers()) { + const BtifAcmPeer* peer = it.second; + btif_debug_acm_peer_dump(fd, *peer); + } +} + +void btif_debug_acm_dump(int fd) { + btif_debug_acm_initiator_dump(fd); +} diff --git a/le_audio/system/bt/btif/src/btif_acm_source.cc b/le_audio/system/bt/btif/src/btif_acm_source.cc new file mode 100644 index 00000000000..a342d20befc --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_acm_source.cc @@ -0,0 +1,228 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + ******************************************************************************/ + +#include +#include "bt_trace.h" +#include "btif_acm_source.h" +#include "btif_ahim.h" +#include "btif_acm.h" +#include "osi/include/thread.h" + +extern thread_t* get_worker_thread(); + +#if AHIM_ENABLED + + +void btif_acm_process_request(tA2DP_CTRL_CMD cmd) +{ + tA2DP_CTRL_ACK status = A2DP_CTRL_ACK_FAILURE; + // update pending command + btif_ahim_update_pending_command(cmd, AUDIO_GROUP_MGR); + + BTIF_TRACE_IMP("%s: cmd %u", __func__, cmd); + + switch (cmd) { + case A2DP_CTRL_CMD_START: + { + if (btif_acm_is_call_active()) { + BTIF_TRACE_IMP("%s: call active, return incall_failure", __func__); + status = A2DP_CTRL_ACK_INCALL_FAILURE; + } else { + // ACM is in right state + status = A2DP_CTRL_ACK_PENDING; + btif_acm_stream_start(); + } + btif_ahim_ack_stream_started(status, AUDIO_GROUP_MGR); + break; + } + + case A2DP_CTRL_CMD_SUSPEND: + { + if (btif_acm_is_call_active()) { + BTIF_TRACE_IMP("%s: call active, return success", __func__); + status = A2DP_CTRL_ACK_SUCCESS; + } else { + status = A2DP_CTRL_ACK_PENDING; + btif_acm_stream_suspend(); + } + btif_ahim_ack_stream_suspended(status, AUDIO_GROUP_MGR); + break; + } + + case A2DP_CTRL_CMD_STOP: + { + status = A2DP_CTRL_ACK_SUCCESS; + if (btif_acm_is_call_active()) { + BTIF_TRACE_IMP("%s: call active, return success", __func__); + } else { + btif_acm_stream_stop(); + } + btif_ahim_ack_stream_suspended(status, AUDIO_GROUP_MGR); + break; + } + default: + APPL_TRACE_ERROR("%s: unsupported command %d", __func__, cmd); + break; + } +} + + +void btif_acm_handle_event(uint16_t event, char* p_param) +{ + + switch(event) { + case BTIF_ACM_PROCESS_HIDL_REQ_EVT: + btif_acm_process_request((tA2DP_CTRL_CMD ) *p_param); + break; + default: + BTIF_TRACE_IMP("%s: unhandled event", __func__); + break; + } +} + +void process_hidl_req_acm(tA2DP_CTRL_CMD cmd) +{ + btif_transfer_context(btif_acm_handle_event, BTIF_ACM_PROCESS_HIDL_REQ_EVT, (char*)&cmd, sizeof(cmd), NULL); +} + +static btif_ahim_client_callbacks_t sAhimAcmCallbacks = { + 1, // mode + process_hidl_req_acm, + btif_acm_get_sample_rate, + btif_acm_get_ch_mode, + btif_acm_get_bitrate, + btif_acm_get_octets, + btif_acm_get_framelength, + btif_acm_get_ch_count, + nullptr, + btif_acm_get_current_active_profile, + btif_acm_is_codec_type_lc3q, + btif_acm_lc3q_ver +}; + +void btif_register_cb() +{ + reg_cb_with_ahim(AUDIO_GROUP_MGR, &sAhimAcmCallbacks); +} + +bt_status_t btif_acm_source_setup_codec() { + APPL_TRACE_EVENT("%s", __func__); + + bt_status_t status = BT_STATUS_FAIL; + + + APPL_TRACE_EVENT("%s ## setup_codec ##", __func__); + btif_ahim_setup_codec(AUDIO_GROUP_MGR); + + // TODO: check the status + return status; +} + +bool btif_acm_source_start_session(const RawAddress& peer_address) { + bt_status_t status = BT_STATUS_FAIL; + APPL_TRACE_DEBUG("%s: starting session for BD addr %s",__func__, + peer_address.ToString().c_str()); + + // initialize hal. + btif_ahim_init_hal(get_worker_thread(), AUDIO_GROUP_MGR); + + status = btif_acm_source_setup_codec(); + + btif_ahim_start_session(); + + return true; +} + +bool btif_acm_source_end_session(const RawAddress& peer_address) { + APPL_TRACE_DEBUG("%s: starting session for BD addr %s",__func__, + peer_address.ToString().c_str()); + + btif_ahim_end_session(); + + return true; +} + +bool btif_acm_source_restart_session(const RawAddress& old_peer_address, + const RawAddress& new_peer_address) { + bool is_streaming = btif_ahim_is_streaming(); + SessionType session_type = btif_ahim_get_session_type(); + + APPL_TRACE_IMP("%s: old_peer_address=%s, new_peer_address=%s, is_streaming=%d ", + __func__, old_peer_address.ToString().c_str(), + new_peer_address.ToString().c_str(), is_streaming); + + // TODO: do we need to check for new empty address + //CHECK(!new_peer_address.IsEmpty()); + + // If the old active peer was valid or if session is not + // unknown, end the old session. + if (!old_peer_address.IsEmpty() || + session_type != SessionType::UNKNOWN) { + btif_acm_source_end_session(old_peer_address); + } + + btif_acm_source_start_session(new_peer_address); + + return true; +} + +bool btif_acm_update_sink_latency_change(uint16_t sink_latency) { + APPL_TRACE_DEBUG("%s: update_sink_latency %d for active session ",__func__, + sink_latency); + + btif_ahim_set_remote_delay(sink_latency); + + return true; +} + +void btif_acm_source_command_ack(tA2DP_CTRL_CMD cmd, tA2DP_CTRL_ACK status) { + switch (cmd) { + case A2DP_CTRL_CMD_START: + btif_ahim_ack_stream_started(status, AUDIO_GROUP_MGR); + break; + case A2DP_CTRL_CMD_SUSPEND: + case A2DP_CTRL_CMD_STOP: + btif_ahim_ack_stream_suspended(status, AUDIO_GROUP_MGR); + break; + default: + break; + } +} + +void btif_acm_source_on_stopped(tA2DP_CTRL_ACK status) { + APPL_TRACE_EVENT("%s: status %u", __func__, status); + + btif_ahim_ack_stream_suspended(status, AUDIO_GROUP_MGR); + + btif_ahim_reset_pending_command(AUDIO_GROUP_MGR); +} + +void btif_acm_source_on_suspended(tA2DP_CTRL_ACK status) { + APPL_TRACE_EVENT("%s: status %u", __func__, status); + + btif_ahim_ack_stream_suspended(status, AUDIO_GROUP_MGR); + + btif_ahim_reset_pending_command(AUDIO_GROUP_MGR); +} + +bool btif_acm_on_started(tA2DP_CTRL_ACK status) { + APPL_TRACE_EVENT("%s: status %u", __func__, status); + bool retval = false; + + if(0/* TODO: check if call is in progress*/) { + APPL_TRACE_WARNING("%s: call in progress, sending failure", __func__); + btif_ahim_ack_stream_started(A2DP_CTRL_ACK_INCALL_FAILURE, AUDIO_GROUP_MGR); + } + else { + btif_ahim_ack_stream_started(status, AUDIO_GROUP_MGR); + retval = true; + } + + btif_ahim_reset_pending_command(AUDIO_GROUP_MGR); + return retval; +} + + +#endif // AHIM_ENABLED diff --git a/le_audio/system/bt/btif/src/btif_apm.cc b/le_audio/system/bt/btif/src/btif_apm.cc new file mode 100644 index 00000000000..95c34b6bf1f --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_apm.cc @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + **************************************************************************/ + +#define LOG_TAG "btif_apm" + +#include +#include + +#include +#include + +#include "bt_common.h" +#include "btif_common.h" +#include "btif_ahim.h" + +#define A2DP_PROFILE 0x0001 +#define BROADCAST_BREDR 0x0400 +#define BROADCAST_LE 0x0800 +#define ACTIVE_VOICE_PROFILE_HFP 0x0002 + +std::mutex apm_mutex; +btapm_initiator_callbacks_t* callbacks_; + +static bt_status_t init(btapm_initiator_callbacks_t* callbacks); +static void cleanup(); +static bt_status_t update_active_device(const RawAddress& bd_addr, uint16_t profile, uint16_t audio_type); +static bt_status_t set_content_control_id(uint16_t content_control_id, uint16_t audio_type); +static bool apm_enabled = false; + + +#define CHECK_BTAPM_INIT() \ + do { \ + if (!apm_enabled) { \ + BTIF_TRACE_WARNING("%s: BTAV not initialized", __func__); \ + return BT_STATUS_NOT_READY; \ + } \ + } while (0) + +typedef enum { + BTIF_APM_AUDIO_TYPE_VOICE = 0x0, + BTIF_APM_AUDIO_TYPE_MEDIA, + + BTIF_APM_AUDIO_TYPE_SIZE +} btif_av_state_t; + +typedef struct { + RawAddress peer_bda; + int profile; +} btif_apm_device_profile_combo_t; + +typedef struct { + RawAddress peer_bda; +} btif_apm_get_active_profile; + +int active_profile_info; + +static btif_apm_device_profile_combo_t active_device_profile[BTIF_APM_AUDIO_TYPE_SIZE]; +static uint16_t content_control_id[BTIF_APM_AUDIO_TYPE_SIZE]; + +static void btif_update_active_device(uint16_t audio_type, char* param); +void btif_get_active_device(btif_av_state_t audio_type, RawAddress* peer_bda); +static void btif_update_content_control(uint16_t audio_type, char* param); +uint16_t btif_get_content_control_id(btif_av_state_t audio_type); + +static void btif_update_active_device(uint16_t audio_type, char* param) { + btif_apm_device_profile_combo_t new_device_profile; + if(audio_type != BTIF_APM_AUDIO_TYPE_MEDIA) + return; + + memcpy(&new_device_profile, param, sizeof(new_device_profile)); + active_device_profile[audio_type].peer_bda = new_device_profile.peer_bda; + active_device_profile[audio_type].profile = new_device_profile.profile; + BTIF_TRACE_WARNING("%s() New Active Device: %s, Profile: %x\n", __func__, + active_device_profile[audio_type].peer_bda.ToString().c_str(), + active_device_profile[audio_type].profile); + if(active_device_profile[audio_type].profile == A2DP_PROFILE) { + btif_ahim_update_current_profile(A2DP); + } else if(active_device_profile[audio_type].profile == BROADCAST_LE) { + btif_ahim_update_current_profile(BROADCAST); + } else { + btif_ahim_update_current_profile(AUDIO_GROUP_MGR); + } +} + +void btif_get_active_device(btif_av_state_t audio_type, RawAddress* peer_bda) { + if(audio_type >= BTIF_APM_AUDIO_TYPE_SIZE) + return; + peer_bda = &active_device_profile[audio_type].peer_bda; +} + +static void btif_update_content_control(uint16_t audio_type, char* param) { + if(audio_type >= BTIF_APM_AUDIO_TYPE_SIZE) + return; + uint16_t cc_id = (uint16_t)(*param); + content_control_id[audio_type] = cc_id; + /*Update ACM here*/ +} + +uint16_t btif_get_content_control_id(btif_av_state_t audio_type) { + if(audio_type >= BTIF_APM_AUDIO_TYPE_SIZE) + return 0; + return content_control_id[audio_type]; +} + +static const bt_apm_interface_t bt_apm_interface = { + sizeof(bt_apm_interface_t), + init, + update_active_device, + set_content_control_id, + cleanup, +}; + +const bt_apm_interface_t* btif_apm_get_interface(void) { + BTIF_TRACE_EVENT("%s", __func__); + return &bt_apm_interface; +} + +static bt_status_t init(btapm_initiator_callbacks_t* callbacks) { + BTIF_TRACE_EVENT("%s", __func__); + callbacks_ = callbacks; + apm_enabled = true; + + return BT_STATUS_SUCCESS; +} + +static void cleanup() { + BTIF_TRACE_EVENT("%s", __func__); + apm_enabled = false; +} + +static bt_status_t update_active_device(const RawAddress& bd_addr, uint16_t profile, uint16_t audio_type) { + BTIF_TRACE_EVENT("%s", __func__); + CHECK_BTAPM_INIT(); + btif_apm_device_profile_combo_t new_device_profile; + new_device_profile.peer_bda = bd_addr; + new_device_profile.profile = profile; + + std::unique_lock guard(apm_mutex); + + return btif_transfer_context(btif_update_active_device, (uint8_t)audio_type, + (char *)&new_device_profile, sizeof(btif_apm_device_profile_combo_t), NULL); +} + +static bt_status_t set_content_control_id(uint16_t content_control_id, uint16_t audio_type) { + BTIF_TRACE_EVENT("%s", __func__); + CHECK_BTAPM_INIT(); + + std::unique_lock guard(apm_mutex); + + return btif_transfer_context(btif_update_content_control, + (uint8_t)audio_type, (char *)&content_control_id, sizeof(content_control_id), NULL); +} + +void call_active_profile_info(const RawAddress& bd_addr, uint16_t audio_type) { + if (apm_enabled == true) { + BTIF_TRACE_WARNING("%s", __func__); + active_profile_info = callbacks_->active_profile_cb(bd_addr, audio_type); + BTIF_TRACE_WARNING("%s: profile info is %d", __func__, active_profile_info); + } +} + +int get_active_profile(const RawAddress& bd_addr, uint16_t audio_type) { + if (apm_enabled == true) { + BTIF_TRACE_WARNING("%s: active profile is %d ", __func__, active_profile_info); + return active_profile_info; + } + else { + BTIF_TRACE_WARNING("%s: APM is not enabled, returning HFP as active profile %d ", + __func__, ACTIVE_VOICE_PROFILE_HFP); + return ACTIVE_VOICE_PROFILE_HFP; + } +} + diff --git a/le_audio/system/bt/btif/src/btif_ascs_client.cc b/le_audio/system/bt/btif/src/btif_ascs_client.cc new file mode 100644 index 00000000000..d5853e6eec2 --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_ascs_client.cc @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/****************************************************************************** + * + * Copyright 2018 The Android Open Source Project + * + * 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. + * + ******************************************************************************/ + +#include "bta_closure_api.h" +#include "bta_ascs_client_api.h" +#include "btif_common.h" +#include "btif_storage.h" + +#include +#include +#include +#include +#include +#include +#include "osi/include/thread.h" + +using base::Bind; +using base::Unretained; +using bluetooth::bap::ascs::AscsClient; +using bluetooth::bap::ascs::GattState; +using bluetooth::bap::ascs::AscsClientCallbacks; +using bluetooth::bap::ascs::AscsClientInterface; +using bluetooth::bap::ascs::AseOpId; +using bluetooth::bap::ascs::AseOpStatus; +using bluetooth::bap::ascs::AseParams; +using bluetooth::bap::ascs::AseCodecConfigOp; +using bluetooth::bap::ascs::AseQosConfigOp; +using bluetooth::bap::ascs::AseEnableOp; +using bluetooth::bap::ascs::AseDisableOp; +using bluetooth::bap::ascs::AseStartReadyOp; +using bluetooth::bap::ascs::AseStopReadyOp; +using bluetooth::bap::ascs::AseReleaseOp; +using bluetooth::bap::ascs::AseUpdateMetadataOp; + +namespace { + +class AscsClientInterfaceImpl; +std::unique_ptr AscsClientInstance; + +class AscsClientInterfaceImpl + : public AscsClientInterface, + public AscsClientCallbacks { + ~AscsClientInterfaceImpl() = default; + + void Init(AscsClientCallbacks* callbacks) override { + DVLOG(2) << __func__; + this->callbacks = callbacks; + + do_in_bta_thread( + FROM_HERE, + Bind(&AscsClient::Init, this)); + } + + void OnAscsInitialized(int status, int client_id) override { + do_in_jni_thread(FROM_HERE, Bind(&AscsClientCallbacks::OnAscsInitialized, + Unretained(callbacks), status, + client_id)); + } + + void OnConnectionState(const RawAddress& address, + GattState state) override { + DVLOG(2) << __func__ << " address: " << address; + do_in_jni_thread(FROM_HERE, Bind(&AscsClientCallbacks::OnConnectionState, + Unretained(callbacks), address, state)); + } + + void OnAseOpFailed(const RawAddress& address, AseOpId ase_op_id, + std::vector status) override { + do_in_jni_thread(FROM_HERE, + Bind(&AscsClientCallbacks::OnAseOpFailed, + Unretained(callbacks), + address, ase_op_id, status)); + } + + void OnAseState(const RawAddress& address, AseParams ase) override { + do_in_jni_thread(FROM_HERE, + Bind(&AscsClientCallbacks::OnAseState, + Unretained(callbacks), address, ase)); + } + + void OnSearchComplete(int status, + const RawAddress& address, + std::vector sink_ase_list, + std::vector src_ase_list) override { + do_in_jni_thread(FROM_HERE, Bind(&AscsClientCallbacks::OnSearchComplete, + Unretained(callbacks), + status, + address, + sink_ase_list, + src_ase_list)); + } + + void Connect(uint16_t client_id, const RawAddress& address) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::Connect, + Unretained(AscsClient::Get()), + client_id, address, false)); + } + + void Disconnect(uint16_t client_id, const RawAddress& address) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::Disconnect, + Unretained(AscsClient::Get()), + client_id, address)); + } + + void StartDiscovery(uint16_t client_id, const RawAddress& address) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::StartDiscovery, + Unretained(AscsClient::Get()), + client_id, address)); + } + + void GetAseState(uint16_t client_id, const RawAddress& address, + uint8_t ase_id) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::GetAseState, + Unretained(AscsClient::Get()), + client_id, address, ase_id)); + } + + void CodecConfig(uint16_t client_id, const RawAddress& address, + std::vector codec_configs) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::CodecConfig, + Unretained(AscsClient::Get()), + client_id, address, codec_configs)); + } + + void QosConfig(uint16_t client_id, const RawAddress& address, + std::vector qos_configs) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::QosConfig, + Unretained(AscsClient::Get()), + client_id, address, qos_configs)); + } + + void Enable(uint16_t client_id, const RawAddress& address, + std::vector enable_ops) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::Enable, + Unretained(AscsClient::Get()), + client_id, address, enable_ops)); + } + + void Disable(uint16_t client_id, const RawAddress& address, + std::vector disable_ops) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::Disable, + Unretained(AscsClient::Get()), + client_id, address, disable_ops)); + } + + void StartReady(uint16_t client_id, const RawAddress& address, + std::vector start_ready_ops) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::StartReady, + Unretained(AscsClient::Get()), + client_id, address, start_ready_ops)); + } + + void StopReady(uint16_t client_id, const RawAddress& address, + std::vector stop_ready_ops) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::StopReady, + Unretained(AscsClient::Get()), + client_id, address, stop_ready_ops)); + } + + void Release(uint16_t client_id, const RawAddress& address, + std::vector release_ops) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::Release, + Unretained(AscsClient::Get()), + client_id, address, release_ops)); + } + + void UpdateStream(uint16_t client_id, const RawAddress& address, + std::vector metadata_ops) override { + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::UpdateStream, + Unretained(AscsClient::Get()), + client_id, address, metadata_ops)); + } + + void Cleanup(uint16_t client_id) override { + DVLOG(2) << __func__; + do_in_bta_thread(FROM_HERE, Bind(&AscsClient::CleanUp, client_id)); + } + + private: + AscsClientCallbacks* callbacks; +}; + +} // namespace + +AscsClientInterface* btif_ascs_client_get_interface() { + if (!AscsClientInstance) + AscsClientInstance.reset(new AscsClientInterfaceImpl()); + + return AscsClientInstance.get(); +} diff --git a/le_audio/system/bt/btif/src/btif_bap_broadcast.cc b/le_audio/system/bt/btif/src/btif_bap_broadcast.cc new file mode 100644 index 00000000000..6b67080a600 --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_bap_broadcast.cc @@ -0,0 +1,1808 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#define LOG_TAG "btif_bap_broadcast" + +#include "btif_bap_broadcast.h" + +#include +#include +#include +#include +#include +#include + +#include "bt_common.h" +#include "bt_utils.h" +#include "btif_storage.h" +#include "btif_a2dp.h" +#include "btif_hf.h" +#include "btif_a2dp_control.h" +#include "btif_util.h" +#include "btu.h" +#include "osi/include/allocator.h" +#include "osi/include/osi.h" +#include "osi/include/properties.h" +#include "btif/include/btif_a2dp_source.h" +#include "device/include/interop.h" +#include "device/include/controller.h" +#include "btif_bat.h" +#include "btif_av.h" +#include "hcimsgs.h" +#include "btif_config.h" +#include "audio_a2dp_hw/include/audio_a2dp_hw.h" +#include +#include +#include +#include "btm_ble_api.h" +#include "btm_ble_api_types.h" +#include "ble_advertiser.h" +#if (OFF_TARGET_TEST_ENABLED == FALSE) +#include "audio_hal_interface/a2dp_encoding.h" +#endif +#include "controller.h" +#if (OFF_TARGET_TEST_ENABLED == TRUE) +#include "log/log.h" +#include "service/a2dp_hal_sim/audio_a2dp_hal_stub.h" +#endif +#include "state_machine.h" + +#define BIG_COMPILE 1 + +#define BTIF_BAP_BA_NUM_CB 1 +#define kDefaultMaxBroadcastSupported 1 +#define BTIF_BAP_BA_NUM_BMS 1 + +#define INPUT_DATAPATH 0x01 +#define OUTPUT_DATAPATH 0x02 +#define BROADCAST_SPLIT_STEREO 2 +#define BROADCAST_MONO_JOINT 1 +#define BROADCAST_MODE_HR 0x1000 +#define BROADCAST_MODE_LL 0x2000 +/***************************************************************************** + * Local type definitions + *****************************************************************************/ + typedef enum { + BIG_TERMINATED = 0, + BIG_CREATING, + BIG_CREATED, + BIG_RECONFIG, + BIG_TERMINATING, + BIG_DISABLING, + } btif_big_state_t; + +std::vector broadcast_codecs_capabilities; +btav_a2dp_codec_index_t lc3_codec_id = (btav_a2dp_codec_index_t)9; +static const btav_a2dp_codec_config_t broadcast_local_capability = + {lc3_codec_id, BTAV_A2DP_CODEC_PRIORITY_DEFAULT, + (BTAV_A2DP_CODEC_SAMPLE_RATE_48000 | + BTAV_A2DP_CODEC_SAMPLE_RATE_24000 | + BTAV_A2DP_CODEC_SAMPLE_RATE_16000), + BTAV_A2DP_CODEC_BITS_PER_SAMPLE_24, + ((btav_a2dp_codec_channel_mode_t)(BTAV_A2DP_CODEC_CHANNEL_MODE_MONO | + BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO | + BTBAP_CODEC_CHANNEL_MODE_JOINT_STEREO)), 0, 0, 0, 0}; + +static btav_a2dp_codec_config_t default_config; +static btav_a2dp_codec_config_t current_config; +static int mBisMultiplier = 0; +//static bool isSplitEnabled = false; +static bool notify_key_generated = false; +Octet16 encryption_key; +std::vector mBroadcastID(3,0); +struct keyCalculator { + Octet16 rand; +}; +uint8_t enc_keylength = 16; +char local_param[3]; +RawAddress mBapBADevice = RawAddress({0xFA, 0xCE, 0xFA, 0xCE, 0xFA, 0xCE}); +std::mutex session_wait_; +std::condition_variable session_wait_cv_; +bool mSession_wait; +bool mEncryptionEnabled = true; +bool restart_session = false; +extern int btif_max_av_clients; +extern const btgatt_interface_t* btif_gatt_get_interface(); +extern int btif_av_get_latest_device_idx_to_start(); +extern thread_t* get_worker_thread(); +int total_bises = 0; +typedef enum { + iso_unknown = 0, + setup_iso = 1, + remove_iso = 2, +}tBAP_BA_ISO_CMD; + +typedef struct { + uint32_t sdu_int; + uint16_t max_sdu; + uint16_t max_transport_latency; + uint8_t rtn; + uint8_t phy; + uint8_t packing; + uint8_t framing; +} tBAP_BA_BIG_PARAMS; + +tBAP_BA_BIG_PARAMS mBigParams = {10000, 100, 10, 2, 2/*LE 2M*/, 1/*Interleaved*/, 0/*unframed*/}; +#define PATH_ID 1 +tBAP_BA_ISO_CMD pending_cmd = iso_unknown; +int current_handle = -1; +int current_iso_index = 0; + +int config_req_handle = -1; +/** + * Local functions + */ +static void btif_bap_ba_generate_enc_key_local(int length); +static void btif_bap_ba_create_big(int adv_id); +static void btif_bap_ba_terminate_big(int adv_id, int big_handle); +static bool btif_bap_ba_setup_iso_datapath(int big_handle); +static bool btif_bap_ba_remove_iso_datapath(int big_handle); +static void btif_bap_ba_process_iso_setup(uint8_t status, uint16_t bis_handle); +static void btif_bap_ba_update_big_params(); +static void btif_bap_ba_handle_event(uint32_t event, char* p_param); +static void init_local_capabilities(); +static void btif_report_broadcast_state(int adv_id, + btbap_broadcast_state_t state); +static void btif_report_broadcast_audio_state(int adv_id, + btbap_broadcast_audio_state_t state); +static void btif_report_audio_config(int adv_id, + btav_a2dp_codec_config_t codec_config); +static void btif_report_setup_big(int setup, int adv_id, int big_handle, int num_bises); +static void btif_report_broadcast_id(); +static void btif_bap_process_request(tA2DP_CTRL_CMD cmd); +static void btif_broadcast_process_hidl_event(tA2DP_CTRL_CMD cmd); +static uint16_t btif_bap_get_transport_latency(); +static void btif_bap_ba_copy_broadcast_id(); +static void btif_bap_ba_generate_broadcast_id(); +static void btif_bap_ba_signal_session_ready() { + std::unique_lock guard(session_wait_); + if(!mSession_wait) { + mSession_wait = true; + session_wait_cv_.notify_all(); + } else { + BTIF_TRACE_WARNING("%s: already signalled ",__func__); + } +} + +const char* dump_bap_ba_sm_event_name(btif_bap_broadcast_sm_event_t event) { + switch ((int)event) { + CASE_RETURN_STR(BTIF_BAP_BROADCAST_ENABLE_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_DISABLE_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_START_STREAM_REQ_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_STOP_STREAM_REQ_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_SUSPEND_STREAM_REQ_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_SOURCE_CONFIG_REQ_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_CLEANUP_REQ_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_SET_ACTIVE_REQ_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_REMOVE_ACTIVE_REQ_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_SETUP_ISO_DATAPATH_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_REMOVE_ISO_DATAPATH_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_GENERATE_ENC_KEY_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_BISES_SETUP_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_BISES_REMOVE_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_BIG_SETUP_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_BIG_REMOVED_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_PROCESS_HIDL_REQ_EVT) + CASE_RETURN_STR(BTIF_BAP_BROADCAST_SETUP_NEXT_BIS_EVENT) + CASE_RETURN_STR(BTIF_SM_ENTER_EVT) + CASE_RETURN_STR(BTIF_SM_EXIT_EVT) + default: + return "UNKNOWN_EVENT"; + } +} + +void btif_bap_broadcast_update_source_codec(void *p_data) { + btav_a2dp_codec_config_t * codec_req = (btav_a2dp_codec_config_t*)p_data; + if (codec_req->sample_rate != current_config.sample_rate || + codec_req->channel_mode != current_config.channel_mode || + codec_req->codec_specific_1 != current_config.codec_specific_1 || + codec_req->codec_specific_2 != current_config.codec_specific_2) { + restart_session = true; + } + + if (codec_req->codec_specific_4 > 0) { + if (current_config.codec_specific_4 == BROADCAST_MODE_HR) { + mBigParams.max_transport_latency = 60; + } else if (codec_req->codec_specific_4 == BROADCAST_MODE_LL) { + mBigParams.max_transport_latency = 20; + } + } + memcpy(¤t_config, codec_req, sizeof(btav_a2dp_codec_config_t)); + BTIF_TRACE_DEBUG("[BapBroadcast]%s:sample rate: %d",__func__, current_config.sample_rate); + BTIF_TRACE_DEBUG("[BapBroadcast]%s:channel mode: %d",__func__, current_config.channel_mode); + BTIF_TRACE_DEBUG("[BapBroadcast]%s:cs1: %d",__func__, current_config.codec_specific_1); + btif_bap_ba_update_big_params(); +} + +void reverseCode(uint8_t *array) { + uint8_t *p_array = array; + for (int i = 0; i < 8; i++) { + uint8_t temp = p_array[i]; + p_array[i] = p_array[15-i]; + p_array[15-i] = temp; + } +} + +bool isUnencrypted(uint8_t *array) { + uint8_t *p_array = array; + for (int i = 0; i < 16; i++) { + if (p_array[i] != 0x00) { + return false; + } + } + BTIF_TRACE_DEBUG("[BapBroadcast]: isUnencrypted is true"); + return true; +} +class BtifBapBroadcaster; + +class BtifBapBroadcastStateMachine : public bluetooth::common::StateMachine{ + public: + enum { + kStateIdle, // Broadcast idle + kStateConfigured, // Broadcast configured + kStateStreaming, // Broadcast streaming + }; + + class StateIdle : public State { + public: + StateIdle(BtifBapBroadcastStateMachine& sm) + : State(sm, kStateIdle), bms_(sm.Bms()) {} + void OnEnter() override; + void OnExit() override; + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + BtifBapBroadcaster& bms_; + }; + + class StateConfigured : public State { + public: + StateConfigured(BtifBapBroadcastStateMachine& sm) + : State(sm, kStateConfigured), bms_(sm.Bms()) {} + void OnEnter() override; + void OnExit() override; + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + BtifBapBroadcaster& bms_; + }; + + class StateStreaming : public State { + public: + StateStreaming(BtifBapBroadcastStateMachine& sm) + : State(sm, kStateStreaming), bms_(sm.Bms()) {} + void OnEnter() override; + void OnExit() override; + bool ProcessEvent(uint32_t event, void* p_data) override; + + private: + BtifBapBroadcaster& bms_; + }; + + BtifBapBroadcastStateMachine(BtifBapBroadcaster& bms) : bms_(bms) { + state_idle_ = new StateIdle(*this); + state_configured_ = new StateConfigured(*this); + state_streaming_ = new StateStreaming(*this); + + AddState(state_idle_); + AddState(state_configured_); + AddState(state_streaming_); + SetInitialState(state_idle_); + } + BtifBapBroadcaster& Bms() { return bms_; } + private: + BtifBapBroadcaster& bms_; + StateIdle* state_idle_; + StateConfigured* state_configured_; + StateStreaming* state_streaming_; +}; + +class BtifBapBroadcaster{ + public: + enum { + kFlagBIGPending = 0x1, + kFlagISOPending = 0x2, + kFlagISOError = 0x4, + KFlagISOSetup = 0x8, + }; + + BtifBapBroadcaster(int adv_handle, int big_handle) + :adv_handle_(adv_handle), + big_handle_(big_handle), + state_machine_(*this), + flags_(0), + big_state_(BIG_TERMINATED){} + + ~BtifBapBroadcaster(); + + bt_status_t Init(); + void Cleanup(); + + bool CanBeDeleted() {return ( + (state_machine_.StateId() == BtifBapBroadcastStateMachine::kStateIdle) && + (state_machine_.PreviousStateId() != BtifBapBroadcastStateMachine::kStateInvalid)); }; + + int AdvHandle() const { return adv_handle_; } + int BIGHandle() const { return big_handle_; } + void SetBIGHandle(int handle) { big_handle_ = handle; } + void SetAdvHandle(int handle) { adv_handle_ = handle; } + BtifBapBroadcastStateMachine& StateMachine() { return state_machine_; } + const BtifBapBroadcastStateMachine& StateMachine() const { return state_machine_; } + bool CheckFlags(uint8_t bitflags_mask) const { + return ((flags_ & bitflags_mask) != 0); + } + + void ClearFlag(uint8_t bitflags_mask) { flags_ &= ~bitflags_mask;} + + void ClearAllFlags() { flags_ = 0; } + /** + * Set only the flags as specified by the bitflags mask. + * + * @param bitflags_mask the bitflags to set + */ + void SetFlags(uint8_t bitflags_mask) { flags_ |= bitflags_mask; } + + void SetNumBises(uint8_t num_bises) {num_bises_ = num_bises; } + + uint8_t NumBises() const { return num_bises_; } + + void SetBIGState(btif_big_state_t state) { big_state_ = state; } + + btif_big_state_t BIGState() const { return big_state_; } + + /*void SetMandatoryCodecPreferred(bool preferred) { + mandatory_codec_preferred_ = preferred; + } + bool IsMandatoryCodecPreferred() const { return mandatory_codec_preferred_; }*/ + + std::vector GetBISHandles() const { return bis_handle_list_;} + void SetBISHandles(std::vector handle_list) { bis_handle_list_ = handle_list; } + + private: + int adv_handle_; + int big_handle_; // SEP type of peer device + uint8_t num_bises_; + BtifBapBroadcastStateMachine state_machine_; + uint8_t flags_; + btif_big_state_t big_state_; + //bool mandatory_codec_preferred_ = false; + std::vector bis_handle_list_; +}; +//BtifBapBroadcaster::BtifBapBroadcaster(int adv_handle, int big_handle) +// :adv_handle_(adv_handle), big_handle_(big_handle){} + +class BtifBapBroadcastSource{ + public: + // The PeerId is used as AppId for BTA_AvRegister() purpose + static constexpr uint8_t kPeerIdMin = 0; + static constexpr uint8_t kPeerIdMax = BTIF_BAP_BA_NUM_BMS; + public: + enum { + kFlagIdle = 0x1, + kFlagConfigured = 0x2, + kFlagStreaming = 0x4, + KFlagDisabling = 0x8, + }; + BtifBapBroadcastSource() + : callbacks_(nullptr), + enabled_(false), + offload_enabled_(false), + max_broadcast_(kDefaultMaxBroadcastSupported) {} + ~BtifBapBroadcastSource(); + + bt_status_t Init( + btbap_broadcast_callbacks_t* callbacks, int max_broadcast, + btav_a2dp_codec_config_t codec_config,int mode); + + bt_status_t EnableBroadcast(btav_a2dp_codec_config_t codec_config); + bt_status_t DisableBroadcast(int adv_handle); + void Cleanup(); + void CleanupIdleBms(); + btbap_broadcast_callbacks_t* Callbacks() { return callbacks_; } + void SetEnabled(bool state) { enabled_ = state; } + bool Enabled() const { return enabled_; } + bool BapBroadcastOffloadEnabled() const { return offload_enabled_; } + + BtifBapBroadcaster* FindBmsFromAdvHandle(uint8_t adv_handle); +// BtifBapBroadcaster* FindEmptyBms(); + BtifBapBroadcaster* FindBmsFromBIGHandle(uint8_t big_handle); + BtifBapBroadcaster* FindStreamingBms(); + BtifBapBroadcaster* FindConfiguredBms(); + BtifBapBroadcaster* CreateBMS(int adv_handle); + //void SetDefaultConfig(btav_a2dp_codec_config_t config) { default_config_ = config; } + btav_a2dp_codec_config_t GetDefaultConfig () const { return default_config_; } + //void SetCurrentConfig (btav_a2dp_codec_config_t config) { current_config_ = config; } + btav_a2dp_codec_config_t GetCurrentConfig() const { return current_config_; } + bt_status_t SetEncryption(int length); + bt_status_t SetBroadcastActive(bool setup, uint8_t adv_id); + bool BroadcastActive() const { return ((broadcast_state_ == kFlagConfigured) + ||(broadcast_state_ == kFlagStreaming)); } + void SetBroadcastState(uint8_t flag) { broadcast_state_ = flag; } + uint8_t GetBroadcastState() { return broadcast_state_; } + bt_status_t SetUserConfig(uint8_t adv_hdl, btav_a2dp_codec_config_t codec_config); + + const std::map& Bms() const { return bms_; } + + private: + void CleanupAllBms(); + + btbap_broadcast_callbacks_t* callbacks_; + bool enabled_; + bool offload_enabled_; + int max_broadcast_; + std::map bms_; + btav_a2dp_codec_config_t default_config_; + btav_a2dp_codec_config_t current_config_; + uint8_t broadcast_state_; +}; + +static BtifBapBroadcastSource btif_bap_bms; + +bt_status_t BtifBapBroadcaster::Init() { + state_machine_.Start(); + return BT_STATUS_SUCCESS; +} + +void BtifBapBroadcaster::Cleanup() { + state_machine_.Quit(); +} + +void BtifBapBroadcastStateMachine::StateIdle::OnEnter() { + BTIF_TRACE_IMP("%s", __PRETTY_FUNCTION__); + + bms_.ClearAllFlags(); + bms_.SetAdvHandle(-1); + bms_.SetBIGHandle(-1); + bms_.SetBIGState(BIG_TERMINATED); + btif_bap_bms.SetBroadcastState(BtifBapBroadcastSource::kFlagIdle); + btif_bap_bms.SetEnabled(false); + btif_bap_bms.CleanupIdleBms(); +} + +void BtifBapBroadcastStateMachine::StateIdle::OnExit() { + BTIF_TRACE_IMP("%s", __PRETTY_FUNCTION__); +} + +bool BtifBapBroadcastStateMachine::StateIdle::ProcessEvent(uint32_t event, void* p_data) { + BTIF_TRACE_IMP("[BapBroadcast]:%s: event = %s",__func__, + dump_bap_ba_sm_event_name((btif_bap_broadcast_sm_event_t)event)); + switch (event) { + case BTIF_BAP_BROADCAST_SET_ACTIVE_REQ_EVT: + bms_.StateMachine().TransitionTo(BtifBapBroadcastStateMachine::kStateConfigured); + break; + case BTIF_BAP_BROADCAST_SOURCE_CONFIG_REQ_EVT: + //copy config + break; + default: + BTIF_TRACE_WARNING("%s: unhandled event=%s", __func__, + dump_bap_ba_sm_event_name((btif_bap_broadcast_sm_event_t)event)); + return false; + } + return true; +} + +void BtifBapBroadcastStateMachine::StateConfigured::OnEnter() { + BTIF_TRACE_IMP("%s", __PRETTY_FUNCTION__); + + // Inform the application that we are entering connecting state + btif_bap_bms.SetBroadcastState(BtifBapBroadcastSource::kFlagConfigured); + btif_bap_bms.SetEnabled(true); + if (bms_.BIGState() == BIG_TERMINATED) { +#if AHIM_ENABLED + btif_ahim_init_hal(get_worker_thread(), BROADCAST); + btif_ahim_start_session(); +#else + btif_a2dp_source_restart_session(RawAddress::kEmpty, mBapBADevice); +#endif + btif_bap_ba_signal_session_ready(); + btif_bap_ba_update_big_params(); + } else if (bms_.BIGState() == BIG_DISABLING) { + ProcessEvent(BTIF_BAP_BROADCAST_DISABLE_EVT, nullptr); + return; + } + bms_.SetBIGState(BIG_TERMINATED); + bms_.ClearAllFlags(); + btif_report_broadcast_state(bms_.AdvHandle(), BTBAP_BROADCAST_STATE_CONFIGURED); +} + +void BtifBapBroadcastStateMachine::StateConfigured::OnExit() { + BTIF_TRACE_IMP("%s", __PRETTY_FUNCTION__); +} + +bool BtifBapBroadcastStateMachine::StateConfigured::ProcessEvent(uint32_t event, + void* p_data) { + BTIF_TRACE_IMP("[BapBroadcast]:%s: event = %s",__func__, + dump_bap_ba_sm_event_name((btif_bap_broadcast_sm_event_t)event)); + switch (event) { + + case BTIF_BAP_BROADCAST_DISABLE_EVT: + BTIF_TRACE_DEBUG("[BapBroadcast]:BTIF_BAP_BROADCAST_DISABLE_EVT, moving to idle"); + if (bms_.CheckFlags(BtifBapBroadcaster::kFlagBIGPending) || + bms_.CheckFlags(BtifBapBroadcaster::kFlagISOPending)) { +#if AHIM_ENABLED + btif_ahim_ack_stream_started(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS, BROADCAST); + btif_ahim_reset_pending_command(BROADCAST); +#else + bluetooth::audio::a2dp::ack_stream_started(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + bluetooth::audio::a2dp::reset_pending_command(); +#endif + } + bms_.SetBIGState(BIG_DISABLING); + bms_.ClearFlag(BtifBapBroadcaster::kFlagISOPending); + btif_report_broadcast_state(bms_.AdvHandle(), BTBAP_BROADCAST_STATE_IDLE); + bms_.StateMachine().TransitionTo(BtifBapBroadcastStateMachine::kStateIdle); + break; + case BTIF_BAP_BROADCAST_SET_ACTIVE_REQ_EVT: + BTIF_TRACE_DEBUG("Not handled in configured state"); + break; + case BTIF_BAP_BROADCAST_START_STREAM_REQ_EVT: + if (bms_.CheckFlags(BtifBapBroadcaster::kFlagBIGPending) || + bms_.CheckFlags(BtifBapBroadcaster::kFlagISOPending)) { + BTIF_TRACE_DEBUG("[BapBroadcast]%s: BIG/ISO setup pending, dup req",__func__); +#if AHIM_ENABLED + btif_ahim_ack_stream_started(A2DP_CTRL_ACK_PENDING, BROADCAST); +#else + btif_ahim_ack_stream_started(A2DP_CTRL_ACK_PENDING, BROADCAST); +#endif + break; + } + bms_.SetFlags(BtifBapBroadcaster::kFlagBIGPending); + bms_.SetNumBises(btif_bap_broadcast_get_ch_count()); + bms_.SetBIGState(BIG_CREATING); + btif_bap_ba_create_big(bms_.AdvHandle()); + break; + case BTIF_BAP_BROADCAST_SETUP_ISO_DATAPATH_EVT: + { + if (bms_.CheckFlags(BtifBapBroadcaster::kFlagBIGPending)) + bms_.ClearFlag(BtifBapBroadcaster::kFlagBIGPending); + bms_.SetFlags(BtifBapBroadcaster::kFlagISOPending); + total_bises = bms_.NumBises(); + current_iso_index = 0; + current_handle = bms_.BIGHandle(); + btif_bap_ba_setup_iso_datapath(current_handle); + } + break; + case BTIF_BAP_BROADCAST_REMOVE_ISO_DATAPATH_EVT: + total_bises = bms_.NumBises(); + current_iso_index = 0; + current_handle = bms_.BIGHandle(); + btif_bap_ba_remove_iso_datapath(current_handle); + break; + case BTIF_BAP_BROADCAST_BISES_SETUP_EVT: + { + char *p_p = (char *) p_data; + p_p++; + uint8_t status = *p_p; + if (status != BT_STATUS_SUCCESS && + bms_.CheckFlags(BtifBapBroadcaster::kFlagISOPending)) { + BTIF_TRACE_ERROR("[BapBroadcast]%s: setup iso failed",__func__); + bms_.ClearAllFlags(); + bms_.SetFlags(BtifBapBroadcaster::kFlagISOError); +#if AHIM_ENABLED + btif_ahim_ack_stream_started(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS, BROADCAST); + btif_ahim_reset_pending_command(BROADCAST); +#else + bluetooth::audio::a2dp::ack_stream_started(A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS); + bluetooth::audio::a2dp::reset_pending_command(); +#endif + + btif_bap_ba_terminate_big(bms_.AdvHandle(), bms_.BIGHandle()); + break; + } + bms_.ClearFlag(BtifBapBroadcaster::kFlagISOPending); + bms_.SetFlags(BtifBapBroadcaster::KFlagISOSetup); +#if AHIM_ENABLED + btif_ahim_ack_stream_started(A2DP_CTRL_ACK_SUCCESS, BROADCAST); + btif_ahim_reset_pending_command(BROADCAST); +#else + bluetooth::audio::a2dp::ack_stream_started(A2DP_CTRL_ACK_SUCCESS); + bluetooth::audio::a2dp::reset_pending_command(); +#endif + btif_report_setup_big(1, bms_.AdvHandle(), bms_.BIGHandle(), bms_.NumBises()); + btif_report_broadcast_state(bms_.AdvHandle(), BTBAP_BROADCAST_STATE_STREAMING); + bms_.StateMachine().TransitionTo(BtifBapBroadcastStateMachine::kStateStreaming); + } + break; + case BTIF_BAP_BROADCAST_BISES_REMOVE_EVT: +#if AHIM_ENABLED + btif_ahim_ack_stream_started(A2DP_CTRL_ACK_SUCCESS, BROADCAST); + btif_ahim_reset_pending_command(BROADCAST); +#else + bluetooth::audio::a2dp::ack_stream_started(A2DP_CTRL_ACK_SUCCESS); + bluetooth::audio::a2dp::reset_pending_command(); +#endif + break; + case BTIF_BAP_BROADCAST_BIG_SETUP_EVT: + break; + case BTIF_BAP_BROADCAST_BIG_REMOVED_EVT: + btif_report_setup_big(0, bms_.AdvHandle(), -1, 0); + if (bms_.CheckFlags(BtifBapBroadcaster::kFlagISOError)) { + bms_.StateMachine().TransitionTo(BtifBapBroadcastStateMachine::kStateIdle); + btif_report_broadcast_state(bms_.AdvHandle(),BTBAP_BROADCAST_STATE_IDLE); + } + break; + case BTIF_BAP_BROADCAST_SOURCE_CONFIG_REQ_EVT: + btif_bap_broadcast_update_source_codec(p_data); + if (restart_session) { +#if AHIM_ENABLED + btif_ahim_end_session(); + btif_ahim_init_hal(get_worker_thread(), BROADCAST); + btif_ahim_start_session(); +#else + btif_a2dp_source_restart_session(RawAddress::kEmpty, mBapBADevice); +#endif + btif_report_audio_config(bms_.AdvHandle(), current_config); + } + break; + case BTIF_BAP_BROADCAST_SUSPEND_STREAM_REQ_EVT: +#if AHIM_ENABLED + btif_ahim_ack_stream_suspended(A2DP_CTRL_ACK_SUCCESS, BROADCAST); + btif_ahim_reset_pending_command(BROADCAST); +#else + bluetooth::audio::a2dp::ack_stream_suspended(A2DP_CTRL_ACK_SUCCESS); + bluetooth::audio::a2dp::reset_pending_command(); +#endif + BTIF_TRACE_WARNING("%s: SUSPEND_STEAM_REQ unhandled in Configured state", __func__); + break; + default: + BTIF_TRACE_WARNING("%s: unhandled event=%s", __func__, + dump_bap_ba_sm_event_name((btif_bap_broadcast_sm_event_t)event)); + return false; + } + return true; + +} + +void BtifBapBroadcastStateMachine::StateStreaming::OnEnter() { + BTIF_TRACE_IMP("%s", __PRETTY_FUNCTION__); + btif_bap_bms.SetBroadcastState(BtifBapBroadcastSource::kFlagStreaming); + btif_report_broadcast_audio_state(bms_.AdvHandle(), BTBAP_BROADCAST__AUDIO_STATE_STARTED); +} + +void BtifBapBroadcastStateMachine::StateStreaming::OnExit() { + BTIF_TRACE_IMP("%s", __PRETTY_FUNCTION__); +} + +bool BtifBapBroadcastStateMachine::StateStreaming::ProcessEvent(uint32_t event, + void* p_data) { + BTIF_TRACE_IMP("[BapBroadcast]:%s: event = %s",__func__, + dump_bap_ba_sm_event_name((btif_bap_broadcast_sm_event_t)event)); + switch (event) { + case BTIF_BAP_BROADCAST_DISABLE_EVT: + if (bms_.BIGState() == BIG_CREATED) { + bms_.SetBIGState(BIG_DISABLING); + btif_bap_bms.SetBroadcastState(BtifBapBroadcastSource::KFlagDisabling); + if (bms_.CheckFlags(BtifBapBroadcaster::KFlagISOSetup)) { + btif_bap_ba_terminate_big(bms_.AdvHandle(), bms_.BIGHandle()); + } + } else { + bms_.SetBIGState(BIG_DISABLING); + BTIF_TRACE_DEBUG("[BapBroadcast]: BIG Terminate under process"); + } + break; + case BTIF_BAP_BROADCAST_SUSPEND_STREAM_REQ_EVT: + if (bms_.BIGState() != BIG_CREATED) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s: BIG is getting terminated already",__func__); +#if AHIM_ENABLED + btif_ahim_ack_stream_suspended(A2DP_CTRL_ACK_SUCCESS, BROADCAST); + btif_ahim_reset_pending_command(BROADCAST); +#else + bluetooth::audio::a2dp::ack_stream_suspended(A2DP_CTRL_ACK_SUCCESS); + bluetooth::audio::a2dp::reset_pending_command(); +#endif + break; + } + bms_.SetFlags(BtifBapBroadcaster::kFlagISOPending); + total_bises = bms_.NumBises(); + current_iso_index = 0; + current_handle = bms_.BIGHandle(); + btif_bap_ba_remove_iso_datapath(current_handle); + + break; + case BTIF_BAP_BROADCAST_SETUP_ISO_DATAPATH_EVT: + { + char *p_p = (char *)p_data; + if (*p_p) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s: We shouldn't be in streaming state if ISO datapath is not setup yet",__func__); + } else { + if (bms_.CheckFlags(BtifBapBroadcaster::KFlagISOSetup)) { + BTIF_TRACE_WARNING("[BapBroadcast] We shouldn't be here, ISO Datapath is removed first before BIG"); + btif_bap_ba_remove_iso_datapath(bms_.BIGHandle()); + } else { + BTIF_TRACE_WARNING("[BapBroadcast]:%s:IsoDatapah is already removed",__func__); + bms_.StateMachine().TransitionTo(BtifBapBroadcastStateMachine::kStateConfigured); + } + } + } + break; + case BTIF_BAP_BROADCAST_REMOVE_ISO_DATAPATH_EVT: + btif_bap_ba_terminate_big(bms_.AdvHandle(), bms_.BIGHandle()); + break; + case BTIF_BAP_BROADCAST_BISES_REMOVE_EVT: + bms_.ClearFlag(BtifBapBroadcaster::kFlagISOPending); + if (bms_.BIGState() == BIG_CREATED) + bms_.SetBIGState(BIG_TERMINATING); + btif_bap_ba_terminate_big(bms_.AdvHandle(), bms_.BIGHandle()); + break; + case BTIF_BAP_BROADCAST_BIG_REMOVED_EVT: + if (bms_.BIGState() == BIG_DISABLING) { + btif_report_broadcast_state(bms_.AdvHandle(), BTBAP_BROADCAST_STATE_IDLE); + bms_.StateMachine().TransitionTo(BtifBapBroadcastStateMachine::kStateIdle); + } else if (bms_.BIGState() == BIG_TERMINATING) { + bms_.StateMachine().TransitionTo(BtifBapBroadcastStateMachine::kStateConfigured); + } else if (bms_.BIGState() == BIG_RECONFIG) { +#if AHIM_ENABLED + btif_ahim_end_session(); + btif_ahim_init_hal(get_worker_thread(), BROADCAST); + btif_ahim_start_session(); +#else + btif_a2dp_source_restart_session(RawAddress::kEmpty, mBapBADevice); +#endif + btif_report_audio_config(bms_.AdvHandle(), current_config); + bms_.StateMachine().TransitionTo(BtifBapBroadcastStateMachine::kStateConfigured); + break; + } +#if AHIM_ENABLED + btif_ahim_ack_stream_suspended(A2DP_CTRL_ACK_SUCCESS, BROADCAST); + btif_ahim_reset_pending_command(BROADCAST); +#else + bluetooth::audio::a2dp::ack_stream_suspended(A2DP_CTRL_ACK_SUCCESS); + bluetooth::audio::a2dp::reset_pending_command(); +#endif + + break; + case BTIF_BAP_BROADCAST_SOURCE_CONFIG_REQ_EVT: + btif_bap_broadcast_update_source_codec(p_data); + if (restart_session) { + bms_.SetBIGState(BIG_RECONFIG); + btif_bap_ba_terminate_big(bms_.AdvHandle(), bms_.BIGHandle()); + } + break; + default: + BTIF_TRACE_WARNING("%s: unhandled event=%s", __func__, + dump_bap_ba_sm_event_name((btif_bap_broadcast_sm_event_t)event)); + return false; + } + return true; +} + +static btif_ahim_client_callbacks_t sAhimBroadcastCallbacks = { + 2, // mode + btif_broadcast_process_hidl_event, + btif_bap_broadcast_get_sample_rate, + btif_bap_broadcast_get_ch_mode, + btif_bap_broadcast_get_bitrate, + btif_bap_broadcast_get_mtu, + btif_bap_broadcast_get_framelength, + btif_bap_broadcast_get_ch_count, + btif_bap_broadcast_is_simulcast_enabled, + nullptr, + nullptr, + nullptr +}; + +bt_status_t BtifBapBroadcastSource::Init(btbap_broadcast_callbacks_t* callbacks, + int max_broadcast, + btav_a2dp_codec_config_t codec_config,int mode) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s", __func__); + char value[PROPERTY_VALUE_MAX] = {'\0'}; + if (mode == 1) offload_enabled_ = true; + callbacks_ = callbacks; + default_config_ = codec_config; + memset(encryption_key.data(), 0, OCTET16_LEN); + init_local_capabilities(); + osi_property_get("persist.vendor.btstack.partial_simulcast",value,"false"); + if (strcmp(value, "true") == 0) { + BTIF_TRACE_IMP("[BapBroadcast]%s:Partial simulcast enabled",__func__); + mBisMultiplier = 2; + } else { + mBisMultiplier = 1; + } + osi_property_get("persist.vendor.btstack.transport_latency",value,"0"); + mBigParams.max_transport_latency = atoi(value); + osi_property_get("persist.vendor.btstack.bis_rtn",value,"2"); + mBigParams.rtn = atoi(value); + BTIF_TRACE_IMP("%s: transport_latency: %d, rtn: %d", + __func__, mBigParams.max_transport_latency, mBigParams.rtn); + BTIF_TRACE_IMP("%s: Fetch broadcast encryption key", __func__); + + size_t length = OCTET16_LEN; + bool ret = btif_config_get_bin("Adapter", "BAP_BA_ENC_KEY", encryption_key.data(), &length); + + if (!ret) { + btif_bap_ba_generate_enc_key_local(OCTET16_LEN); + } else { + reverseCode(encryption_key.data()); + if (isUnencrypted(encryption_key.data())) { + mEncryptionEnabled = false; + } + for (int i = 0; i < OCTET16_LEN; i++) { + BTIF_TRACE_IMP("[bapbroadcast]%s: encryption_key[%d] = %d",__func__,i,encryption_key[i]); + } + } +#if AHIM_ENABLED + reg_cb_with_ahim(BROADCAST, &sAhimBroadcastCallbacks); +#endif + return BT_STATUS_SUCCESS; +} + +bt_status_t BtifBapBroadcastSource::EnableBroadcast(btav_a2dp_codec_config_t codec_config) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s", __func__); + current_config_ = codec_config; + btif_bap_ba_generate_broadcast_id(); + return BT_STATUS_SUCCESS; +} + +bt_status_t BtifBapBroadcastSource::DisableBroadcast(int adv_handle) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s", __func__); + local_param[0] = adv_handle; + do_in_bta_thread( + FROM_HERE, base::Bind(&btif_bap_ba_handle_event, + BTIF_BAP_BROADCAST_DISABLE_EVT, local_param)); + return BT_STATUS_SUCCESS; +} + +bt_status_t BtifBapBroadcastSource::SetEncryption(int length) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s", __func__); + local_param[0] = length; + do_in_bta_thread( + FROM_HERE, base::Bind(&btif_bap_ba_handle_event, + BTIF_BAP_BROADCAST_GENERATE_ENC_KEY_EVT, local_param)); + return BT_STATUS_SUCCESS; +} + +bt_status_t BtifBapBroadcastSource::SetBroadcastActive(bool setup, uint8_t adv_id) { + if (btif_a2dp_source_is_hal_v2_supported()) { + std::unique_lock guard(session_wait_); + mSession_wait = false; + if (setup) { + do_in_bta_thread( + FROM_HERE, base::Bind(&btif_bap_ba_handle_event, + BTIF_BAP_BROADCAST_SET_ACTIVE_REQ_EVT, (char *)&adv_id)); + } else { + do_in_bta_thread( + FROM_HERE, base::Bind(&btif_bap_ba_handle_event, + BTIF_BAP_BROADCAST_REMOVE_ACTIVE_REQ_EVT,(char *)&adv_id)); + } + BTIF_TRACE_EVENT("%s: wating for signal",__func__); + session_wait_cv_.wait_for(guard, std::chrono::milliseconds(1000), + []{return mSession_wait;}); + BTIF_TRACE_EVENT("%s: done with signal",__func__); + return BT_STATUS_SUCCESS; + } + return BT_STATUS_SUCCESS; +} +void BtifBapBroadcastSource::Cleanup() { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s", __func__); + //while(!bms_.empty()) { + for (auto it = bms_.begin();it != bms_.end();){ + BtifBapBroadcaster *bms = it->second; + auto prev_it = it++; + bms->Cleanup(); + bms_.erase(prev_it); + //delete bms; + } +} + +void BtifBapBroadcastSource::CleanupIdleBms() { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s", __func__); + for (auto it = bms_.begin();it != bms_.end();){ + BtifBapBroadcaster *bms = it->second; + auto prev_it = it++; + if (bms->CanBeDeleted()) { + BTIF_TRACE_DEBUG("[BapBroadcast]%s: Cleaning up idle bms", __func__); + bms->Cleanup(); + bms_.erase(prev_it); + //delete bms; + } + //delete bms; + } + BTIF_TRACE_DEBUG("[BapBroadcast]%s:Exit",__func__); +} + +bt_status_t BtifBapBroadcastSource::SetUserConfig(uint8_t adv_handle, + btav_a2dp_codec_config_t codec_config) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s", __func__); + config_req_handle = (int) adv_handle; + do_in_bta_thread( + FROM_HERE, base::Bind(&btif_bap_ba_handle_event, + BTIF_BAP_BROADCAST_SOURCE_CONFIG_REQ_EVT, (char *)&codec_config)); + return BT_STATUS_SUCCESS; +} +BtifBapBroadcaster * BtifBapBroadcastSource::CreateBMS(int adv_handle) { + BtifBapBroadcaster *bms = new BtifBapBroadcaster(adv_handle, -1); +// bms_.insert(bms); + bms_.insert(std::make_pair(adv_handle, bms)); + bms->Init(); + return bms; +} + +BtifBapBroadcaster * BtifBapBroadcastSource::FindBmsFromAdvHandle(uint8_t adv_handle) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s: adv_handle = %d", __func__, adv_handle); + for(auto it : bms_) { + BtifBapBroadcaster *bms = it.second; + if (bms->AdvHandle() == adv_handle) + return bms; + } + return nullptr; +} + +BtifBapBroadcaster * BtifBapBroadcastSource::FindBmsFromBIGHandle(uint8_t big_handle) { + for(auto it : bms_) { + BtifBapBroadcaster *bms = it.second; + if (bms->BIGHandle() == big_handle) + return bms; + } + return nullptr; +} + +BtifBapBroadcaster * BtifBapBroadcastSource::FindStreamingBms() { + for(auto it : bms_) { + BtifBapBroadcaster *bms = it.second; + if (bms->StateMachine().StateId() == BtifBapBroadcastStateMachine::kStateStreaming) + return bms; + } + return nullptr; +} + +BtifBapBroadcaster * BtifBapBroadcastSource::FindConfiguredBms() { + for(auto it : bms_) { + BtifBapBroadcaster *bms = it.second; + if (bms->StateMachine().StateId() == BtifBapBroadcastStateMachine::kStateConfigured) + return bms; + } + return nullptr; +} +BtifBapBroadcastSource::~BtifBapBroadcastSource(){} +/***************************************************************************** + * Local event handlers + *****************************************************************************/ +void print_config(btav_a2dp_codec_config_t config) { + BTIF_TRACE_WARNING("[BapBroadcast]%d: Sampling rate = %d", __func__,config.sample_rate); + BTIF_TRACE_WARNING("[BapBroadcast]%d: channel mode = %d", __func__, config.channel_mode); + BTIF_TRACE_WARNING("[BapBroadcast]%d: codec_specific_1 = %d", __func__, config.codec_specific_1); + BTIF_TRACE_WARNING("[BapBroadcast]%d: codec_specific_2 = %d", __func__, config.codec_specific_2); +} + +static void btif_report_encyption_key() { + do_in_jni_thread(FROM_HERE, + base::Bind(btif_bap_bms.Callbacks()->enc_key_cb, + std::string(reinterpret_cast(encryption_key.data()), OCTET16_LEN))); +} + +static void btif_report_broadcast_state(int adv_id, + btbap_broadcast_state_t state) { + if (btif_bap_bms.Enabled()) { + do_in_jni_thread(FROM_HERE, + base::Bind(btif_bap_bms.Callbacks()->broadcast_state_cb, + adv_id, state)); + } +} + +static void btif_report_broadcast_audio_state(int adv_id, + btbap_broadcast_audio_state_t state) { + if (btif_bap_bms.Enabled()) { + do_in_jni_thread(FROM_HERE, + base::Bind(btif_bap_bms.Callbacks()->audio_state_cb, + adv_id, state)); + } +} + +static void btif_report_audio_config(int adv_id, + btav_a2dp_codec_config_t codec_config) { + if (btif_bap_bms.Enabled()) { + do_in_jni_thread(FROM_HERE, + base::Bind(btif_bap_bms.Callbacks()->audio_config_cb, + adv_id, codec_config, broadcast_codecs_capabilities)); + } +} + +static void btif_report_setup_big(int setup, int adv_id, int big_handle, int num_bises) { + + BtifBapBroadcaster *bms = btif_bap_bms.FindBmsFromAdvHandle(adv_id); + if (bms == nullptr) return; + if (btif_bap_bms.Enabled()) { + do_in_jni_thread(FROM_HERE, + base::Bind(btif_bap_bms.Callbacks()->create_big_cb, setup, + adv_id, big_handle, num_bises, bms->GetBISHandles())); + } + +} + +static void btif_report_broadcast_id() { + do_in_jni_thread(FROM_HERE, + base::Bind(btif_bap_bms.Callbacks()->broadcast_id_cb, mBroadcastID)); +} + +static void btif_bap_ba_handle_event(uint32_t event, char* p_param) { + int big_handle, adv_id; + big_handle = adv_id = 0; + BtifBapBroadcaster *broadcaster; + BTIF_TRACE_DEBUG("[BapBroadcast]:%s: event %s", + __func__, dump_bap_ba_sm_event_name((btif_bap_broadcast_sm_event_t)event)); + switch(event) { + case BTIF_BAP_BROADCAST_DISABLE_EVT: + adv_id = (int)*p_param; + broadcaster = btif_bap_bms.FindBmsFromAdvHandle(adv_id); + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s:invalid index, Broadcast is already disabled",__func__); + return; + } + break; + case BTIF_BAP_BROADCAST_START_STREAM_REQ_EVT: + broadcaster = btif_bap_bms.FindConfiguredBms(); + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s:cannot find empty index",__func__); + return; + } + break; + case BTIF_BAP_BROADCAST_STOP_STREAM_REQ_EVT: + case BTIF_BAP_BROADCAST_SUSPEND_STREAM_REQ_EVT: + broadcaster = btif_bap_bms.FindStreamingBms(); + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s:cannot find empty index",__func__); + return; + } + break; + case BTIF_BAP_BROADCAST_SOURCE_CONFIG_REQ_EVT: + broadcaster = btif_bap_bms.FindBmsFromAdvHandle(config_req_handle); + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s:cannot find empty index",__func__); + return; + } + break; + case BTIF_BAP_BROADCAST_CLEANUP_REQ_EVT: + broadcaster = btif_bap_bms.FindStreamingBms(); //TODO:add proper check + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s:cannot find empty index",__func__); + return; + } + break; + case BTIF_BAP_BROADCAST_SET_ACTIVE_REQ_EVT: + { + char *p_p = p_param; + int adv_handle = (int)*p_p; + BTIF_TRACE_ERROR("[BapBroadcast]:%s:adv_handle %d",__func__,adv_handle); + broadcaster = btif_bap_bms.CreateBMS((int)adv_handle); + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s: cannot find empty index",__func__); + return; + } + broadcaster->SetAdvHandle(adv_handle); + BTIF_TRACE_ERROR("[BapBroadcast]:%s:adv_id = %d, big_handle = %d",__func__, adv_handle); + } + break; + case BTIF_BAP_BROADCAST_REMOVE_ACTIVE_REQ_EVT: + BTIF_TRACE_DEBUG("[BapBroadcast]:%s:End session", __func__); +#if AHIM_ENABLED + btif_ahim_end_session(); +#else + bluetooth::audio::a2dp::end_session(); +#endif + btif_bap_ba_signal_session_ready(); + return; + case BTIF_BAP_BROADCAST_SETUP_ISO_DATAPATH_EVT: + { + char *p_p = p_param; + int adv_handle = (int)*p_p; + BTIF_TRACE_DEBUG("[BapBroadcast]:%s:adv_handle = %d",__func__,adv_handle); + broadcaster = btif_bap_bms.FindBmsFromAdvHandle(adv_handle); + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s: cannot find empty index",__func__); + return; + } + } + break; + case BTIF_BAP_BROADCAST_REMOVE_ISO_DATAPATH_EVT: + { + char *p_p = p_param; + adv_id = *p_p++; + big_handle = *p_p; + broadcaster = btif_bap_bms.FindBmsFromBIGHandle(big_handle); + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s: cannot find empty index",__func__); + return; + } + } + break; + case BTIF_BAP_BROADCAST_GENERATE_ENC_KEY_EVT: + enc_keylength = (uint8_t)(*p_param); + notify_key_generated = true; + btif_bap_ba_generate_enc_key_local(enc_keylength); + return; + case BTIF_BAP_BROADCAST_BISES_SETUP_EVT: + case BTIF_BAP_BROADCAST_BISES_REMOVE_EVT: + big_handle = *p_param; + broadcaster = btif_bap_bms.FindBmsFromBIGHandle(big_handle); + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s: cannot find empty index",__func__); + return; + } + break; + case BTIF_BAP_BROADCAST_BIG_REMOVED_EVT: + adv_id = (int)*p_param; + btif_report_setup_big(0, adv_id,-1, 0); + broadcaster = btif_bap_bms.FindBmsFromAdvHandle(adv_id); + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s: cannot find empty index",__func__); + return; + } + break; + case BTIF_BAP_BROADCAST_PROCESS_HIDL_REQ_EVT: + btif_bap_process_request((tA2DP_CTRL_CMD ) *p_param); + return; + case BTIF_BAP_BROADCAST_SETUP_NEXT_BIS_EVENT: + { + char *p = p_param; + uint8_t status = *p++; + uint16_t bis_handle = *p++; + bis_handle = (bis_handle | (*p <<8)); + btif_bap_ba_process_iso_setup(status, bis_handle); + return; + } + default: + BTIF_TRACE_ERROR("[BapBroadcast]:%s: invalid event = %d",__func__, event); + return; + } + if (broadcaster == nullptr) { + BTIF_TRACE_ERROR("[BapBroadcast]:%s:Invalid broadcaster",__func__); + } + broadcaster->StateMachine().ProcessEvent(event, (void*)p_param); +} + +static void btif_bap_process_request(tA2DP_CTRL_CMD cmd) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s",__func__); + tA2DP_CTRL_ACK status = A2DP_CTRL_ACK_FAILURE; + //BtifBapBroadcaster *broadcaster; + uint32_t event = 0; +#if AHIM_ENABLED + btif_ahim_update_pending_command(cmd, BROADCAST); +#else + bluetooth::audio::a2dp::update_pending_command(cmd); +#endif + + switch(cmd) { + case A2DP_CTRL_CMD_START: + if (!bluetooth::headset::btif_hf_is_call_vr_idle()) { + status = A2DP_CTRL_ACK_INCALL_FAILURE; + break; + } + if (btif_bap_bms.FindStreamingBms() != nullptr) { + BTIF_TRACE_DEBUG("%s: Broadcast already streaming, crash recover(?)",__func__); + status = A2DP_CTRL_ACK_SUCCESS; + break; + } + if (btif_bap_bms.FindConfiguredBms() == nullptr) { + BTIF_TRACE_DEBUG("%s: Broadcast is disabled",__func__); + status = A2DP_CTRL_ACK_DISCONNECT_IN_PROGRESS; + break; + } + btif_bap_ba_dispatch_sm_event(BTIF_BAP_BROADCAST_START_STREAM_REQ_EVT, NULL, 0); + event = BTIF_BAP_BROADCAST_START_STREAM_REQ_EVT; + //broadcaster = btif_bap_bms.FindConfiguredBms(); + status = A2DP_CTRL_ACK_PENDING; + break; + case A2DP_CTRL_CMD_STOP: + btif_bap_ba_dispatch_sm_event(BTIF_BAP_BROADCAST_STOP_STREAM_REQ_EVT, NULL, 0); + //broadcaster = btif_bap_bms.FindStreamingBms(); + status = A2DP_CTRL_ACK_SUCCESS; + break; + case A2DP_CTRL_CMD_SUSPEND: + if (btif_bap_bms.FindStreamingBms() != nullptr) { + btif_bap_ba_dispatch_sm_event(BTIF_BAP_BROADCAST_SUSPEND_STREAM_REQ_EVT, NULL, 0); + //broadcaster = btif_bap_bms.FindStreamingBms(); + status = A2DP_CTRL_ACK_PENDING; + } else { + status = A2DP_CTRL_ACK_SUCCESS; + } + break; + default: + APPL_TRACE_ERROR("UNSUPPORTED CMD (%d)", cmd); + status = A2DP_CTRL_ACK_FAILURE; + break; + } + // send the response now based on status + switch (cmd) { + case A2DP_CTRL_CMD_START: +#if AHIM_ENABLED + btif_ahim_ack_stream_started(status, BROADCAST); +#else + bluetooth::audio::a2dp::ack_stream_started(status); +#endif + break; + case A2DP_CTRL_CMD_SUSPEND: + case A2DP_CTRL_CMD_STOP: +#if AHIM_ENABLED + btif_ahim_ack_stream_suspended(status, BROADCAST); +#else + bluetooth::audio::a2dp::ack_stream_suspended(status); +#endif + break; + default: + break; + } + if (status != A2DP_CTRL_ACK_PENDING) { +#if AHIM_ENABLED + btif_ahim_reset_pending_command(BROADCAST); +#else + bluetooth::audio::a2dp::reset_pending_command(); +#endif + } +} + +static bool btif_bap_is_broadcaster_valid(uint8_t big_handle) { + BTIF_TRACE_DEBUG("[BapBroadcat]%s: handle = %d",__func__, big_handle); + BtifBapBroadcaster *bms = btif_bap_bms.FindBmsFromBIGHandle(big_handle); + if (bms == nullptr) return false; + if (pending_cmd == setup_iso) { + if (!bms->CheckFlags(BtifBapBroadcaster::kFlagISOPending) || + bms->StateMachine().StateId() != BtifBapBroadcastStateMachine::kStateConfigured) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s Broadcast disabled",__func__); + return false; + } + } else { + if (bms->BIGState() != BIG_CREATED) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s Broadcast disabled",__func__); + return false; + } + } + return true; +} + +static void btif_bap_ba_process_iso_setup(uint8_t status, uint16_t bis_handle) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s",__func__); + if (!btif_bap_is_broadcaster_valid(current_handle)) return; + if (pending_cmd == setup_iso) { + local_param[0] = current_handle; + local_param[1] = status; + if (!btif_bap_ba_setup_iso_datapath(current_handle)) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s: notify bis setup",__func__); + pending_cmd = iso_unknown; + btif_bap_ba_handle_event(BTIF_BAP_BROADCAST_BISES_SETUP_EVT, (char *)local_param); + } + } else if (pending_cmd == remove_iso) { + if (!btif_bap_ba_remove_iso_datapath(current_handle)) { + local_param[0] = current_handle; + local_param[1] = status; + pending_cmd = iso_unknown; + BTIF_TRACE_WARNING("[BapBroadcast]:%s: notify bis removed",__func__); + btif_bap_ba_handle_event(BTIF_BAP_BROADCAST_BISES_REMOVE_EVT, (char *)local_param); + } + } +} +static void btif_bap_ba_isodatapath_setup_cb(uint8_t status, uint16_t bis_handle) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s, status = %d for handle = %d",__func__, status, bis_handle); + if (!btif_bap_is_broadcaster_valid(current_handle)) return; + if (pending_cmd == setup_iso) { + memset(local_param, 0, 3); + local_param[0] = status; + local_param[1] = bis_handle & 0x00FF; + local_param[2] = (bis_handle & 0xFF00) >> 8; + if (status == 0) { + total_bises--; + btif_bap_ba_handle_event(BTIF_BAP_BROADCAST_SETUP_NEXT_BIS_EVENT, (char *)local_param); + } else { + local_param[0] = current_handle; + local_param[1] = status; + btif_bap_ba_handle_event(BTIF_BAP_BROADCAST_BISES_SETUP_EVT, (char *)local_param); + } + } else if (pending_cmd == remove_iso) { + memset(local_param, 0, 3); + local_param[0] = status; + local_param[1] = bis_handle & 0x00FF; + local_param[2] = (bis_handle & 0xFF00) >> 8; + btif_bap_ba_handle_event(BTIF_BAP_BROADCAST_SETUP_NEXT_BIS_EVENT, (char*)local_param); + } +} + +static bool btif_bap_ba_setup_iso_datapath(int big_handle) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s",__func__); + BtifBapBroadcaster *bms = btif_bap_bms.FindBmsFromBIGHandle(big_handle); + if (bms == nullptr) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s bms is null",__func__); + return false; + } + if (!btif_bap_is_broadcaster_valid(big_handle)) return false; + if (current_iso_index == bms->NumBises()) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s completed",__func__); + return false; + } + pending_cmd = setup_iso; + std::vector BisHandles = bms->GetBISHandles(); + tBTM_BLE_SET_ISO_DATA_PATH_PARAM *p_param = new tBTM_BLE_SET_ISO_DATA_PATH_PARAM; + p_param->conn_handle = BisHandles[current_iso_index++]; + p_param->data_path_dir = 0; + p_param->data_path_id = PATH_ID;//6; + p_param->codec_id[0] = 6; + p_param->cont_delay[0] = 0; + p_param->cont_delay[1] = 0; + p_param->cont_delay[2] = 0; + p_param->codec_config_length = 0; + //param.codec_config = NULL; + p_param->p_cb = (tBTM_BLE_SETUP_ISO_DATA_PATH_CMPL_CB*)&btif_bap_ba_isodatapath_setup_cb; + + BTIF_TRACE_WARNING("[BapBroadcast]:%s for handle = %d",__func__, p_param->conn_handle); + do_in_bta_thread(FROM_HERE,base::Bind(base::IgnoreResult(&BTM_BleSetIsoDataPath), std::move(p_param))); + return true; +} + +static bool btif_bap_ba_remove_iso_datapath(int big_handle) { + BTIF_TRACE_WARNING("[BapBroadcast]:%s",__func__); + BtifBapBroadcaster *bms = btif_bap_bms.FindBmsFromBIGHandle(big_handle); + if (bms == nullptr) { + BTIF_TRACE_WARNING("[BapBroadcast]%s: broadcaster not found",__func__); + return false; + } + if (current_iso_index == bms->NumBises()) { + return false; + } + pending_cmd = remove_iso; + std::vector BisHandles = bms->GetBISHandles(); + uint16_t bis_handle = BisHandles[current_iso_index++]; + do_in_bta_thread(FROM_HERE, base::Bind(base::IgnoreResult(&BTM_BleRemoveIsoDataPath), bis_handle, + INPUT_DATAPATH,&btif_bap_ba_isodatapath_setup_cb)); + return true; +} + +void btif_bap_ba_creat_big_cb(uint8_t adv_id, uint8_t status, uint8_t big_handle, + uint32_t sync_delay, uint32_t transport_latency, uint8_t phy, uint8_t nse, uint8_t bn, uint8_t pto, + uint8_t irc, uint16_t max_pdu, uint16_t iso_int, uint8_t num_bis, std::vector conn_handle_list) { + BTIF_TRACE_IMP("[BapBroadcast]%s: callback: status = %d, adv_id = %d",__func__, status, adv_id); + if (status == BT_STATUS_SUCCESS) { + BtifBapBroadcaster *bms = btif_bap_bms.FindBmsFromAdvHandle(adv_id); + if (bms == nullptr) { + BTIF_TRACE_ERROR("%s: broadcaster not found",__func__); + return; + } + if (bms->StateMachine().StateId() != BtifBapBroadcastStateMachine::kStateConfigured || + bms->BIGState() != BIG_CREATING) { + BTIF_TRACE_WARNING("[BapBroadcast]%s: Broadcast is disabling",__func__); + return; + } + bms->SetBIGHandle(big_handle); + BTIF_TRACE_DEBUG("[BapBroadcast]%s: callback: big_handle = %d",__func__, bms->BIGHandle()); + bms->SetNumBises(num_bis); + BTIF_TRACE_DEBUG("[BapBroadcast]%s: callback: num_bis = %d",__func__, bms->NumBises()); + bms->SetBISHandles(conn_handle_list); + bms->SetBIGState(BIG_CREATED); + local_param[0] = adv_id; + do_in_bta_thread(FROM_HERE, + base::Bind(&btif_bap_ba_handle_event, + BTIF_BAP_BROADCAST_SETUP_ISO_DATAPATH_EVT, local_param)); + } else { + local_param[0] = adv_id; + do_in_bta_thread(FROM_HERE, + base::Bind(&btif_bap_ba_handle_event, + BTIF_BAP_BROADCAST_DISABLE_EVT, local_param)); + } +} + +static void btif_bap_ba_create_big(int adv_handle) { + BTIF_TRACE_IMP("[BapBroadcast]:%s",__func__); + //char ba_enc[PROPERTY_VALUE_MAX] = {0}; +#if BIG_COMPILE + BtifBapBroadcaster *bms = btif_bap_bms.FindBmsFromAdvHandle(adv_handle); + if (bms == nullptr) { + BTIF_TRACE_ERROR("%s: broadcaster not found",__func__); + return; + } + CreateBIGParameters param; + param.adv_handle = adv_handle; + param.num_bis = bms->NumBises(); + param.sdu_int = mBigParams.sdu_int; + param.max_sdu = mBigParams.max_sdu; + param.max_transport_latency = btif_bap_get_transport_latency(); + param.rtn = mBigParams.rtn; + param.phy = mBigParams.phy; + param.packing = mBigParams.packing;;//0 : sequential, 1: interleaved + param.framing = mBigParams.framing; + //osi_property_get("persist.bluetooth.ba_encryption", ba_enc, "true"); + //if (!(strncmp(ba_enc,"true",4))) { + if (mEncryptionEnabled) { + //mEncryptionEnabled = true; + param.encryption = 0x01; + } else { + mEncryptionEnabled = false; + param.encryption = 0x00; + } + uint8_t code[16] = {0}; + if (mEncryptionEnabled) { + memcpy(&code[0], encryption_key.data(),encryption_key.size()); + } + for(int i =0; i < 16; i++) { + param.broadcast_code.push_back(code[15-i]); + BTIF_TRACE_VERBOSE("[BapBroadcast]%s: code[%d] = %x, bc[%d] = %d",__func__,i,code[i],i,param.broadcast_code[i]); + } + + btif_gatt_get_interface()->advertiser->CreateBIG(adv_handle, param, + base::Bind(&btif_bap_ba_creat_big_cb)); +#endif /* BIG_COMPILE */ +} + +#if BIG_COMPILE +static void btif_bap_ba_terminate_big_cb(uint8_t status, uint8_t adv_id, + uint8_t big_handle, uint8_t reason) { + BTIF_TRACE_IMP("[BapBroadcast]:%s",__func__); + local_param[0] = adv_id; + do_in_bta_thread(FROM_HERE, + base::Bind(&btif_bap_ba_handle_event, + BTIF_BAP_BROADCAST_BIG_REMOVED_EVT, /*(char*)&adv_id)*/local_param)); + +} +#endif /* BIG_COMPILE */ + +static void btif_bap_ba_terminate_big(int adv_handle, int big_handle) { + BTIF_TRACE_IMP("[BapBroadcast]:%s",__func__); +#if BIG_COMPILE + int reason = 0x16; //user terminated + btif_gatt_get_interface()->advertiser->TerminateBIG(adv_handle, big_handle, reason, + base::Bind(&btif_bap_ba_terminate_big_cb)); +#endif /* BIG_COMPILE */ +} + +void btif_bap_ba_dispatch_sm_event(btif_bap_broadcast_sm_event_t event, void* p_data, int len) { + BTIF_TRACE_DEBUG("%s: event: %d, len: %d", __FUNCTION__, event, len); + do_in_bta_thread(FROM_HERE, + base::Bind(&btif_bap_ba_handle_event, event, (char*)p_data)); + BTIF_TRACE_DEBUG("%s: event %d sent", __FUNCTION__, event); +} + +void btif_broadcast_process_hidl_event(tA2DP_CTRL_CMD cmd) { + btif_bap_ba_dispatch_sm_event(BTIF_BAP_BROADCAST_PROCESS_HIDL_REQ_EVT, + (char*)&cmd, sizeof(cmd)); +} +bool btif_bap_broadcast_is_active() { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s",__func__); + if (btif_bap_bms.BroadcastActive()) { + return true; + } + return false; +} + +void btif_bap_ba_update_big_params() { + uint32_t bitrate = btif_bap_broadcast_get_bitrate(); + mBigParams.max_sdu = btif_bap_broadcast_get_mtu(bitrate); + mBigParams.sdu_int = btif_bap_broadcast_get_framelength(); + BTIF_TRACE_DEBUG("[BapBroadcast]:%s: max_sdu = %d, sdu_int = %d",__func__, + mBigParams.max_sdu, mBigParams.sdu_int); +} +uint16_t btif_bap_broadcast_get_sample_rate() { + if (current_config.sample_rate != BTAV_A2DP_CODEC_SAMPLE_RATE_NONE) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s: sample_rate = %d",__func__, current_config.sample_rate); + return current_config.sample_rate; + } else { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s: default sample_rate = %d",__func__, default_config.sample_rate); + return default_config.sample_rate; + } +} +uint8_t btif_bap_broadcast_get_ch_mode() { + if (current_config.channel_mode != BTAV_A2DP_CODEC_CHANNEL_MODE_NONE) { + return current_config.channel_mode; + } else { + return default_config.channel_mode; + } +} +uint32_t btif_bap_broadcast_get_mtu(uint32_t bitrate) { + //based on bitrate set (100(80kbps), 120 (96kbps), 155 (128kbps)) + uint32_t mtu; + switch (bitrate) { + case 24000: + mtu = 30; + break; + case 27734: + mtu = 26; + break; + case 48000: {//HAP HQ + if (current_config.codec_specific_2 == 0) + mtu = 45; + else + mtu = 60; + break; + } + case 32000: { + if (current_config.codec_specific_2 == 0) + mtu = 30; + else + mtu = 40; + break; + } + case 64000: { + if (current_config.codec_specific_2 == 0) + mtu = 60; + else + mtu = 80; + break; + } + case 80000: { + if (current_config.codec_specific_2 == 0) + mtu = 75; + else + mtu = 100; + break; + } + case 95060: + mtu = 97; + break; + case 95550: + mtu = 130; + break; + case 96000: { + if (current_config.codec_specific_2 == 0) + mtu = 90; + else + mtu = 120; + break; + } + case 124800: + mtu = 117; + break; + case 124000: + mtu = 155; + break; + default: + mtu = 100; + } + BTIF_TRACE_DEBUG("[BapBroadcast]%s: mtu = %d",__func__,mtu); + return mtu; +} + +uint16_t btif_bap_broadcast_get_framelength() { + uint16_t frame_duration; + switch (current_config.codec_specific_2) { + case 0: + frame_duration = 7500; //7.5msec + break; + case 1: + frame_duration = 10000; //10msec + break; + default: + frame_duration = 10000; + } + BTIF_TRACE_DEBUG("[BapBroadcast]%s: bitrate = %d",__func__,frame_duration); + return frame_duration; +} + +uint32_t btif_bap_broadcast_get_bitrate() { + //based on bitrate set (100(80kbps), 120 (96kbps), 155 (128kbps)) + uint32_t bitrate = 0; + switch (current_config.codec_specific_1) { + case 1000: //32kbps + if (current_config.codec_specific_2 == 0) { + bitrate = 27734; + } else { + bitrate = 24000; + } + break; + case 1001: //32kbps + bitrate = 32000; + break; + case 1002: //48kbps + bitrate = 48000; + break; + case 1003: //64kbps + bitrate = 64000; + break; + case 1004: //80kbps + bitrate = 80000; + break; + case 1005: //955.55kbps + if (current_config.codec_specific_2 == 0) { + bitrate = 95060; + } else { + bitrate = 95550; + } + break; + case 1006: //96kbps + bitrate = 96000; + break; + case 1007: //96kbps + if (current_config.codec_specific_2 == 0) { + bitrate = 124800; + } else { + bitrate = 124000; + } + break; + default: + bitrate = 80000; + } + BTIF_TRACE_DEBUG("[BapBroadcast]%s: bitrate = %d",__func__,bitrate); + return bitrate; +} + +uint8_t btif_bap_broadcast_get_ch_count() { + if (current_config.channel_mode == BTAV_A2DP_CODEC_CHANNEL_MODE_STEREO) { + return BROADCAST_SPLIT_STEREO * mBisMultiplier; + } else if (current_config.channel_mode == BTAV_A2DP_CODEC_CHANNEL_MODE_MONO || + current_config.channel_mode == BTBAP_CODEC_CHANNEL_MODE_JOINT_STEREO) { + return BROADCAST_MONO_JOINT * mBisMultiplier; + } + return BROADCAST_SPLIT_STEREO;//default split stereo +} + +static uint16_t btif_bap_get_transport_latency() { + uint16_t latency; + if (mBigParams.max_transport_latency != 0) { + BTIF_TRACE_DEBUG("[BapBroadcast]%s: latency set by property: %d", + __func__, mBigParams.max_transport_latency); + return mBigParams.max_transport_latency; + } + switch (current_config.codec_specific_2) { + case 0: + latency = 45;//45msec for 7.5msec frame duration + break; + case 1: + default: + latency = 61;//61msec for 10msec frame duration + break; + } + BTIF_TRACE_DEBUG("[BapBroadcast]%s: transport latency = %d", + __func__, latency); + return latency; +} + +bool btif_bap_broadcast_is_simulcast_enabled() { + char value[PROPERTY_VALUE_MAX] = {'\0'}; + osi_property_get("persist.vendor.btstack.partial_simulcast",value,"false"); + if (strcmp(value, "true") == 0) { + BTIF_TRACE_IMP("[BapBroadcast]%s:Partial simulcast enabled",__func__); + return true; + } + return false; +} + +static void btif_bap_ba_copy_broadcast_id() { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s",__func__); + for(int j = 0; j < 3; j++) { + BTIF_TRACE_DEBUG("Broadcast_ID[%d] = %d",j, mBroadcastID[j]); + } + btif_report_broadcast_id(); +} + +static void btif_bap_ba_generate_broadcast_id() { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s",__func__); + btsnd_hcic_ble_rand(base::Bind([](BT_OCTET8 rand) { + for(int a = 0; a < 3; a++) { + uint8_t val = rand[a]; + BTIF_TRACE_DEBUG("val = %d", val); + mBroadcastID[a] = val; + } + btif_bap_ba_copy_broadcast_id(); + })); +} + +static void btif_bap_ba_generate_enc_key_local(int length) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s length = %d",__func__, length); + srand(time(NULL)); + int i = 0; + uint8_t random_key[OCTET16_LEN] = {0}; + while (i < length) { + uint8_t gen = (uint8_t)(rand() % 256); + uint8_t range = (gen % 75) + 48;//Alphanumeric range + if ((range > 57 && range < 65) || + (range > 90 && range < 97)) { + //Ascii range 58 to 64 + //:,;, <, =, >, ?, @] + //range 91 to 96 + //[, \, ], ^, _, ` + BTIF_TRACE_DEBUG("Generate key: Invalid character"); + continue; + } + random_key[i] = range; + i++; + } + + memset(encryption_key.data(), 0, OCTET16_LEN); + memcpy(encryption_key.data(),random_key, OCTET16_LEN); + reverseCode(random_key); + BTIF_TRACE_DEBUG("[BapBroadcast]:%s storing new excryption key of length %d",__func__, OCTET16_LEN); + if (btif_config_set_bin("Adapter", "BAP_BA_ENC_KEY", /*encryption_key.data()*/random_key, OCTET16_LEN)) { + BTIF_TRACE_DEBUG("%s: stored new key", __func__); + btif_config_flush(); + } else { + BTIF_TRACE_DEBUG("%s: failed to store new key", __func__);; + } + if (notify_key_generated) { + notify_key_generated = false; + btif_report_encyption_key(); + } +} +void init_local_capabilities() { + broadcast_codecs_capabilities.push_back(broadcast_local_capability); +} + +static bt_status_t init_broadcast( + btbap_broadcast_callbacks_t* callbacks, + int max_broadcast, btav_a2dp_codec_config_t codec_config, int mode) { + if(max_broadcast > BTIF_BAP_BA_NUM_CB) { + BTIF_TRACE_WARNING("%s: App setting maximum allowable broadcast(%d) \ + to more than limit(%d)", + __func__, max_broadcast, BTIF_BAP_BA_NUM_CB); + max_broadcast = BTIF_BAP_BA_NUM_CB; + } + return btif_bap_bms.Init(callbacks, max_broadcast, codec_config, mode); +} +static bt_status_t enable_broadcast(btav_a2dp_codec_config_t codec_config) { + BTIF_TRACE_IMP("[BapBroadcast]:%s", __func__); + current_config = codec_config; + print_config(current_config); + return btif_bap_bms.EnableBroadcast(codec_config); +} +static bt_status_t disable_broadcast(int adv_id) { + BTIF_TRACE_IMP("[BapBroadcast]:%s", __func__); + return btif_bap_bms.DisableBroadcast(adv_id); +} +static bt_status_t set_broadcast_active(bool setup, uint8_t adv_id) { + BTIF_TRACE_EVENT("[BapBroadcast]:%s", __func__); + return btif_bap_bms.SetBroadcastActive(setup, adv_id); +} +static bt_status_t config_codec(uint8_t adv_handle, btav_a2dp_codec_config_t codec_config) { + BTIF_TRACE_IMP("[BapBroadcast]:%s", __func__); + bool config_changed = false; + print_config(codec_config); + if (codec_config.sample_rate != current_config.sample_rate || + codec_config.channel_mode != current_config.channel_mode || + codec_config.codec_specific_1 != current_config.codec_specific_1 || + codec_config.codec_specific_2 != current_config.codec_specific_2 || + codec_config.codec_specific_4 > 0) { + config_changed = true; + } + + if (config_changed) { + return btif_bap_bms.SetUserConfig(adv_handle, codec_config); + } + return BT_STATUS_SUCCESS; +} +static bt_status_t set_encryption(bool enabled, uint8_t enc_length) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s: length %d", __func__, enc_length); + mEncryptionEnabled = enabled; + /*if (!mEncryptionEnabled) { + btif_config_remove("Adapter", "BAP_BA_ENC_KEY"); + return BT_STATUS_SUCCESS; + }*/ + return btif_bap_bms.SetEncryption(enc_length); +} + +static std::string get_encryption_key() { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s", __func__); + return std::string(reinterpret_cast(encryption_key.data()), OCTET16_LEN); +} + +static bt_status_t setup_audio_data_path(bool enable, uint8_t adv_id, + uint8_t big_handle, int num_bises, int *bis_handles) { + BTIF_TRACE_DEBUG("[BapBroadcast]:%s", __func__); + return BT_STATUS_SUCCESS; +} +static void cleanup_broadcast() { + BTIF_TRACE_ERROR("[BapBroadcast]:%s", __func__); + btif_bap_bms.Cleanup(); +} + +static const btbap_broadcast_interface_t bt_bap_broadcast_src_interface = { + sizeof(btbap_broadcast_interface_t), + init_broadcast, + enable_broadcast, + disable_broadcast, + set_broadcast_active, + config_codec, + setup_audio_data_path, + get_encryption_key, + set_encryption, + cleanup_broadcast, +}; + +/******************************************************************************* + * + * Function btif_bap_broadcast_get_interface + * + * Description Get the Bap broadcast callback interface + * + * Returns btbap_broadcast_interface_t + * + ******************************************************************************/ +const btbap_broadcast_interface_t* btif_bap_broadcast_get_interface(void) { + BTIF_TRACE_EVENT("%s", __func__); + return &bt_bap_broadcast_src_interface; +} + + diff --git a/le_audio/system/bt/btif/src/btif_bap_codec_utils.cc b/le_audio/system/bt/btif/src/btif_bap_codec_utils.cc new file mode 100644 index 00000000000..a33e107b03d --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_bap_codec_utils.cc @@ -0,0 +1,381 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +#include "bta_closure_api.h" +#include "bta_bap_uclient_api.h" +#include "btif_common.h" +#include "btif_storage.h" +#include "osi/include/thread.h" + +// Capabilities to be stored on CodecConfig structure : +// Supported_Sampling_Frequencies  ( codec config sampling rate ) +// Audio_Channel_Counts ( codec config channel mode ) +// Supported_Frame_Durations ( 1st byte of codec specific 1 )  +// Max_Supported_LC3_Frames_Per_SDU  ( 2nd byte of codec specific 1) +// Preferred_Audio_Contexts ( 3&4 bytes of codec specific 1 ) +// Supported_Octets_Per_Codec_Frame ( first 4 bytes codec specific 2 ) +/* Vendor_Specific Metadata ( first 4 bytes of codec specific 3) + * 1st byte conveys LC3Q support, + * 2nd byte conveys LC3Q version + */ + +// Configurations to be stored in CodecConfig structure : +// Sampling_Frequency  ( codec config sampling rate ) +// Audio_Channel_Allocation ( codec config channel mode ) +// Frame_Duration  ( 5th bye of codec specific 1 )  +// LC3_Blocks_Per_SDU ( 6th byte of codec specific 1 ) +// Preferred_Audio_Contexts ( 7&8 bytes of codec specific 1 ) +// Octets_Per_Codec_Frame ( 2 bytes  )  ( 5&6th bytes of codec specific 2 ) +// LC3Q preferred (1 byte) ( 7th byte of codec specific 2 ) +/* Vendor_Specific Metadata ( first 4 bytes of codec specific 3) + * 1st byte conveys LC3Q support, + * 2nd byte conveys LC3Q version + */ + +#include +#include +#include + +using bluetooth::bap::pacs::CodecConfig; +using bluetooth::bap::pacs::CodecChannelMode; + +constexpr uint8_t CAPA_SUP_FRAME_DUR_INDEX = 0x00; // CS1 +constexpr uint8_t CAPA_MAX_SUP_LC3_FRAMES_INDEX = 0x01; // CS1 +constexpr uint8_t CAPA_PREF_AUDIO_CONT_INDEX = 0x02; // CS1 +constexpr uint8_t CAPA_SUP_OCTS_PER_FRAME_INDEX = 0x00; // CS2 + +constexpr uint8_t CAPA_VENDOR_METADATA_LC3Q_PREF_INDEX = 0x00; // CS3 +constexpr uint8_t CAPA_VENDOR_METADATA_LC3Q_VER_INDEX = 0x01; // CS3 + +constexpr uint8_t CONFIG_FRAME_DUR_INDEX = 0x04; // CS1 +constexpr uint8_t CONFIG_LC3_BLOCKS_INDEX = 0x05; // CS1 +constexpr uint8_t CONFIG_PREF_AUDIO_CONT_INDEX = 0x06; // CS1 +constexpr uint8_t CONFIG_OCTS_PER_FRAME_INDEX = 0x04; // CS2 +constexpr uint8_t CONFIG_LC3Q_PREF_INDEX = 0x06; // CS2 +constexpr uint8_t CONFIG_VENDOR_METADATA_LC3Q_PREF_INDEX = 0x00; // CS3 +constexpr uint8_t CONFIG_VENDOR_METADATA_LC3Q_VER_INDEX = 0x01; // CS3 + +// capabilities +bool UpdateCapaSupFrameDurations(CodecConfig *config , uint8_t sup_frame) { + config->codec_specific_1 &= ~(0xFF << (CAPA_SUP_FRAME_DUR_INDEX * 8)); + config->codec_specific_1 |= sup_frame << (CAPA_SUP_FRAME_DUR_INDEX * 8); + return true; +} + +bool UpdateCapaMaxSupLc3Frames(CodecConfig *config, + uint8_t max_sup_lc3_frames) { + config->codec_specific_1 &= ~(0xFF << (CAPA_MAX_SUP_LC3_FRAMES_INDEX * 8)); + config->codec_specific_1 |= max_sup_lc3_frames << + (CAPA_MAX_SUP_LC3_FRAMES_INDEX * 8); + return true; +} + +bool UpdateCapaPreferredContexts(CodecConfig *config, uint16_t contexts) { + config->codec_specific_1 &= ~(0xFFFF << (CAPA_PREF_AUDIO_CONT_INDEX * 8)); + config->codec_specific_1 |= contexts << (CAPA_PREF_AUDIO_CONT_INDEX * 8); + return true; +} + +bool UpdateCapaSupOctsPerFrame(CodecConfig *config, + uint32_t octs_per_frame) { + config->codec_specific_2 &= ~(0xFFFFFFFF << + (CAPA_SUP_OCTS_PER_FRAME_INDEX * 8)); + config->codec_specific_2 |= octs_per_frame << + (CAPA_SUP_OCTS_PER_FRAME_INDEX * 8); + return true; +} + +bool UpdateCapaVendorMetaDataLc3QPref(CodecConfig *config, bool lc3q_pref) { + config->codec_specific_3 &= ~(0xFF << (CAPA_VENDOR_METADATA_LC3Q_PREF_INDEX * 8)); + config->codec_specific_3 |= lc3q_pref << (CAPA_VENDOR_METADATA_LC3Q_PREF_INDEX * 8); + return true; +} + +bool UpdateCapaVendorMetaDataLc3QVer(CodecConfig *config, uint8_t lc3q_ver) { + config->codec_specific_3 &= ~(0xFF << (CAPA_VENDOR_METADATA_LC3Q_VER_INDEX * 8)); + config->codec_specific_3 |= lc3q_ver << (CAPA_VENDOR_METADATA_LC3Q_VER_INDEX * 8); + return true; +} + +uint8_t GetCapaSupFrameDurations(CodecConfig *config) { + return (config->codec_specific_1 >> (8*CAPA_SUP_FRAME_DUR_INDEX)) & 0xff; +} + +uint8_t GetCapaMaxSupLc3Frames(CodecConfig *config) { + if (((config->codec_specific_1 >> + (8*CAPA_MAX_SUP_LC3_FRAMES_INDEX)) & 0xff) == 0x0) { + uint8_t max_chnl_count = 0; + LOG(ERROR) << __func__ + << ": Max Sup LC3 frames is 0, deriving based on chnl count"; + if(static_cast (config->channel_mode) & + static_cast (CodecChannelMode::CODEC_CHANNEL_MODE_STEREO)) { + max_chnl_count = 2; + } else if(static_cast (config->channel_mode) & + static_cast (CodecChannelMode::CODEC_CHANNEL_MODE_MONO)) { + max_chnl_count = 1; + } + return max_chnl_count; + } else { + return (config->codec_specific_1 >> + (8*CAPA_MAX_SUP_LC3_FRAMES_INDEX)) & 0xff; + } +} + +uint16_t GetCapaPreferredContexts(CodecConfig *config) { + return (config->codec_specific_1 >> + (8*CAPA_PREF_AUDIO_CONT_INDEX)) & 0xffff; +} + +uint32_t GetCapaSupOctsPerFrame(CodecConfig *config) { + return (config->codec_specific_2 >> + (8*CAPA_SUP_OCTS_PER_FRAME_INDEX)) & 0xffffffff; +} + +bool GetCapaVendorMetaDataLc3QPref(CodecConfig *config) { + if (((config->codec_specific_3 >> + (8*CAPA_VENDOR_METADATA_LC3Q_PREF_INDEX)) & 0xff) == 0x0) { + return false; + } else + return true; +} + +uint8_t GetCapaVendorMetaDataLc3QVer(CodecConfig *config) { + return (config->codec_specific_3 >> + (8*CAPA_VENDOR_METADATA_LC3Q_VER_INDEX)) & 0xff; +} + +// Configurations +bool UpdateFrameDuration(CodecConfig *config , uint8_t frame_dur) { + uint64_t value = 0xFF; + config->codec_specific_1 &= ~(value << (CONFIG_FRAME_DUR_INDEX*8)); + config->codec_specific_1 |= static_cast(frame_dur) << + (CONFIG_FRAME_DUR_INDEX * 8); + return true; +} + +bool UpdateLc3BlocksPerSdu(CodecConfig *config, uint8_t lc3_blocks_per_sdu) { + uint64_t value = 0xFF; + config->codec_specific_1 &= ~(value << (CONFIG_LC3_BLOCKS_INDEX * 8)); + config->codec_specific_1 |= static_cast(lc3_blocks_per_sdu) << + (CONFIG_LC3_BLOCKS_INDEX * 8); + return true; +} + +bool UpdatePreferredAudioContext(CodecConfig *config , + uint16_t pref_audio_context) { + uint64_t value = 0xFFFF; + config->codec_specific_1 &= ~(value << (CONFIG_PREF_AUDIO_CONT_INDEX*8)); + config->codec_specific_1 |= static_cast(pref_audio_context) << + (CONFIG_PREF_AUDIO_CONT_INDEX * 8); + return true; +} + +bool UpdateOctsPerFrame(CodecConfig *config , uint16_t octs_per_frame) { + uint64_t value = 0xFFFF; + config->codec_specific_2 &= ~(value << (CONFIG_OCTS_PER_FRAME_INDEX * 8)); + config->codec_specific_2 |= + static_cast(octs_per_frame) << + (CONFIG_OCTS_PER_FRAME_INDEX * 8); + return true; +} + +bool UpdateLc3QPreference(CodecConfig *config , bool lc3q_pref) { + uint64_t value = 0xFF; + config->codec_specific_2 &= ~(value << (CONFIG_LC3Q_PREF_INDEX * 8)); + config->codec_specific_2 |= + static_cast(lc3q_pref) << + (CONFIG_LC3Q_PREF_INDEX * 8); + LOG(WARNING) << __func__ + << ": lc3q_pref cs2: " << loghex(config->codec_specific_2); + return true; +} + + +bool UpdateVendorMetaDataLc3QPref(CodecConfig *config, bool lc3q_pref) { + uint64_t value = 0xFF; + config->codec_specific_3 &= ~(value << (CONFIG_VENDOR_METADATA_LC3Q_PREF_INDEX * 8)); + config->codec_specific_3 |= static_cast(lc3q_pref) << + (CONFIG_VENDOR_METADATA_LC3Q_PREF_INDEX * 8); + return true; +} + +bool UpdateVendorMetaDataLc3QVer(CodecConfig *config, uint8_t lc3q_ver) { + uint64_t value = 0xFF; + config->codec_specific_3 &= ~(value << (CONFIG_VENDOR_METADATA_LC3Q_VER_INDEX * 8)); + config->codec_specific_3 |= static_cast(lc3q_ver) << + (CONFIG_VENDOR_METADATA_LC3Q_VER_INDEX * 8); + return true; +} + +uint8_t GetFrameDuration(CodecConfig *config) { + return (config->codec_specific_1 >> (8*CONFIG_FRAME_DUR_INDEX)) & 0xff; +} + +uint8_t GetLc3BlocksPerSdu(CodecConfig *config) { + return (config->codec_specific_1 >> (8*CONFIG_LC3_BLOCKS_INDEX)) & 0xff; +} + +uint16_t GetPreferredAudioContext(CodecConfig *config) { + return (config->codec_specific_1 >> + (8*CONFIG_PREF_AUDIO_CONT_INDEX)) & 0xffff; +} + +uint16_t GetOctsPerFrame(CodecConfig *config) { + return (config->codec_specific_2 >> (8*CONFIG_OCTS_PER_FRAME_INDEX)) & 0xffff; +} + +uint8_t GetLc3QPreference(CodecConfig *config) { + LOG(WARNING) << __func__ << ": lc3q_pref cs2: " + << loghex((config->codec_specific_2 >> (8*CONFIG_LC3Q_PREF_INDEX)) & 0xff); + return (config->codec_specific_2 >> + (8*CONFIG_LC3Q_PREF_INDEX)) & 0xff; +} + +uint8_t GetVendorMetaDataLc3QPref(CodecConfig *config) { + return (config->codec_specific_3 >> + (8*CONFIG_VENDOR_METADATA_LC3Q_PREF_INDEX)) & 0xff; +} + +uint8_t GetVendorMetaDataLc3QVer(CodecConfig *config) { + return (config->codec_specific_3 >> + (8*CONFIG_VENDOR_METADATA_LC3Q_VER_INDEX)) & 0xff; +} + +bool IsCodecConfigEqual(CodecConfig *src_config, CodecConfig *dst_config) { + // first check if passed codec configs are configurations or + // capabilities using first byte of codec specific 1 + if(src_config == nullptr || dst_config == nullptr) { + return false; + } + + bool is_src_capability = src_config->codec_specific_1 & 0XFF; + bool is_dst_capability = dst_config->codec_specific_1 & 0XFF; + + // check the codec type + if(src_config->codec_type != dst_config->codec_type) { + LOG(ERROR) << __func__ << ": No match for codec type "; + return false; + } + + // check sample rate + if(src_config->sample_rate != dst_config->sample_rate) { + LOG(ERROR) << __func__ << ": No match for sample rate"; + return false; + } + + // check channel mode + if(!(static_cast(src_config->channel_mode) & + static_cast(dst_config->channel_mode))) { + LOG(ERROR) << __func__ << ": No match for channel mode "; + return false; + } + + LOG(WARNING) << __func__ + << ": is_src_capability: " << loghex(is_src_capability) + << ", is_dst_capability: " << loghex(is_dst_capability); + + if(is_src_capability && is_dst_capability) { + if(src_config->codec_specific_1 != dst_config->codec_specific_1 || + src_config->codec_specific_2 != dst_config->codec_specific_2 || + src_config->codec_specific_3 != dst_config->codec_specific_3) { + LOG(WARNING) << __func__ << ": No match for CS params. "; + return false; + } + } else if (!is_src_capability && !is_dst_capability) { + LOG(INFO) << __func__ << ": Comparison for both configs "; + uint8_t src_frame_dur = GetFrameDuration(src_config); + uint8_t src_lc3_blocks_per_sdu = GetLc3BlocksPerSdu(src_config); + uint16_t src_octs_per_frame = GetOctsPerFrame(src_config); + uint8_t src_lc3q_pref = GetLc3QPreference(src_config); + uint16_t src_pref_audio_context = GetPreferredAudioContext(src_config); + + uint8_t dst_frame_dur = GetFrameDuration(dst_config); + uint8_t dst_lc3_blocks_per_sdu = GetLc3BlocksPerSdu(dst_config); + uint16_t dst_octs_per_frame = GetOctsPerFrame(dst_config); + uint8_t dst_lc3q_pref = GetLc3QPreference(dst_config); + uint16_t dst_pref_audio_context = GetPreferredAudioContext(dst_config); + + if(src_frame_dur != dst_frame_dur) { + LOG(ERROR) << __func__ << ": Frame Dur not match with existing config "; + return false; + } + + if(src_lc3_blocks_per_sdu != dst_lc3_blocks_per_sdu) { + LOG(ERROR) << __func__ << ": Lc3 blocks not match with existing config "; + return false; + } + + if(src_octs_per_frame != dst_octs_per_frame) { + LOG(ERROR) << __func__ << ": Octs per frame not match with existing config "; + return false; + } + + if(src_lc3q_pref != dst_lc3q_pref) { + LOG(ERROR) << __func__ << ": Lc3Q pref not match with existing config "; + return false; + } + + if (!(src_pref_audio_context & dst_pref_audio_context)) { + LOG(ERROR) << __func__ << ": pref_audio_context not match with existing config "; + return false; + } + + } else if(is_src_capability || is_dst_capability) { + CodecConfig *capa_config = is_src_capability ? src_config:dst_config; + CodecConfig *oth_config = is_src_capability ? dst_config:src_config; + + uint8_t capa_sup_frames = GetCapaSupFrameDurations(capa_config); + uint8_t capa_max_sup_lc3_frames = GetCapaMaxSupLc3Frames(capa_config); + uint16_t capa_min_sup_octs = GetCapaSupOctsPerFrame(capa_config) & 0xFFFF; + uint16_t capa_max_sup_octs = (GetCapaSupOctsPerFrame(capa_config) + & 0xFFFF0000) >> 16; + bool capa_lc3q_pref = GetCapaVendorMetaDataLc3QPref(capa_config); + uint16_t capa_pref_audio_context = GetCapaPreferredContexts(capa_config); + + uint8_t frame_dur = GetFrameDuration(oth_config); + uint8_t lc3_blocks_per_sdu = GetLc3BlocksPerSdu(oth_config); + uint16_t octs_per_frame = GetOctsPerFrame(oth_config); + uint8_t lc3q_pref = GetLc3QPreference(oth_config); + uint16_t dst_pref_audio_context = GetPreferredAudioContext(oth_config); + + LOG(WARNING) << __func__ + << ": capa_sup_frames: " << loghex(capa_sup_frames) + << ", frame_dur: " << loghex(frame_dur); + + LOG(WARNING) << __func__ + << ": capa_lc3q_pref: " << capa_lc3q_pref + << ", lc3q_pref: " << loghex(lc3q_pref); + + LOG(WARNING) << __func__ + << ": capa_pref_audio_context: " << capa_pref_audio_context + << ", dst_pref_audio_context: " << dst_pref_audio_context; + + if(!(capa_sup_frames & (0x01 << frame_dur))) { + LOG(ERROR) << __func__ << ": No match for frame duration "; + return false; + } + + if(capa_max_sup_lc3_frames && lc3_blocks_per_sdu) { + if(capa_max_sup_lc3_frames < lc3_blocks_per_sdu * + static_cast (oth_config->channel_mode)) { + LOG(ERROR) << __func__ << ": blocks per sdu exceeds the capacity "; + return false; + } + } + + if( octs_per_frame < capa_min_sup_octs || + octs_per_frame > capa_max_sup_octs) { + LOG(ERROR) << __func__ << ": octs per frame not in limits "; + return true; + } + + if (!(capa_pref_audio_context & dst_pref_audio_context)) { + LOG(ERROR) << __func__ << ": No Match for Audio context"; + return false; + } + + } + return true; +} diff --git a/le_audio/system/bt/btif/src/btif_bap_config.cc b/le_audio/system/bt/btif/src/btif_bap_config.cc new file mode 100644 index 00000000000..6daeb6ad655 --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_bap_config.cc @@ -0,0 +1,860 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/****************************************************************************** + * + * Copyright (C) 2014 Google, Inc. + * + * 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 "bt_btif_bap_config" + +#include "btif_bap_config.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "bt_types.h" +#include "btcore/include/module.h" +#include "btif_api.h" +#include "btif_common.h" +#include "btif_util.h" +#include "osi/include/alarm.h" +#include "osi/include/allocator.h" +#include "osi/include/compat.h" +#include "osi/include/config.h" +#include "osi/include/log.h" +#include "osi/include/osi.h" +#include "osi/include/properties.h" +#include "btif_bap_codec_utils.h" + +#define BT_CONFIG_SOURCE_TAG_NUM 1010001 + +#define INFO_SECTION "Info" +#define FILE_TIMESTAMP "TimeCreated" +#define FILE_SOURCE "FileSource" +#define TIME_STRING_LENGTH sizeof("YYYY-MM-DD HH:MM:SS") +#define INDEX_FREE (0x00) +#define INDEX_OCCUPIED (0x01) +#define MAX_INDEX (255) +#define MAX_INDEX_LEN (0x04) +#define MAX_SECTION_LEN (255) + +using bluetooth::bap::pacs::CodecSampleRate; + +static const char* TIME_STRING_FORMAT = "%Y-%m-%d %H:%M:%S"; + +// TODO(armansito): Find a better way than searching by a hardcoded path. +#if defined(OS_GENERIC) +static const char* CONFIG_FILE_PATH = "bap_config.conf"; +static const char* CONFIG_BACKUP_PATH = "bap_config.bak"; +#else // !defined(OS_GENERIC) +static const char* CONFIG_FILE_PATH = "/data/misc/bluedroid/bap_config.conf"; +static const char* CONFIG_BACKUP_PATH = "/data/misc/bluedroid/bap_config.bak"; +#endif // defined(OS_GENERIC) +static const period_ms_t CONFIG_SETTLE_PERIOD_MS = 3000; + +static void timer_config_save_cb(void* data); +static void btif_bap_config_write(uint16_t event, char* p_param); +static bool is_factory_reset(void); +static void delete_config_files(void); +static void btif_bap_config_remove_restricted(config_t* config); +static config_t* btif_bap_config_open(const char* filename); + +static enum ConfigSource { + NOT_LOADED, + ORIGINAL, + BACKUP, + NEW_FILE, + RESET +} btif_bap_config_source = NOT_LOADED; + +//static int btif_bap_config_devices_loaded = -1; +static char btif_bap_config_time_created[TIME_STRING_LENGTH]; + +static config_t* config; +static std::recursive_mutex config_lock; // protects operations on |config|. +static alarm_t* config_timer; + +#define BAP_DIRECTION_KEY "Direction" +#define BAP_CODEC_TYPE_KEY "CodecType" + +#define BAP_RECORD_TYPE_KEY "RecordType" +#define BAP_RECORD_TYPE_CAPA "Capability" +#define BAP_RECORD_TYPE_CONF "Configuration" + +#define BAP_SAMP_FREQS_KEY "SamplingRate" +#define BAP_CONTEXT_TYPE_KEY "ContextType" + +#define BAP_SUPP_FRM_DURATIONS_KEY "SupFrameDurations" +#define BAP_SUP_MIN_OCTS_PER_FRAME_KEY "SupMinOctsPerFrame" +#define BAP_SUP_MAX_OCTS_PER_FRAME_KEY "SupMaxOctsPerFrame" +#define BAP_MAX_SUP_CODEC_FRAMES_PER_SDU "SupMaxFramesPerSDU" +#define BAP_LC3Q_SUP_KEY "LC3QSupport" +#define BAP_LC3Q_VER_KEY "LC3QVersion" + +#define BAP_CONF_FRAME_DUR_KEY "ConfiguredFrameDur" +#define BAP_CONF_OCTS_PER_FRAME_KEY "ConfiguredOctsPerFrame" +#define BAP_LC3_FRAMES_PER_SDU_KEY "Lc3FramesPerSDU" +#define BAP_CHNL_ALLOCATION_KEY "ChannelAllocation" + +#define BAP_SRC_LOCATIONS_KEY "SrcLocation" +#define BAP_SINK_LOCATIONS_KEY "SinkLocation" +#define BAP_SUP_AUDIO_CONTEXTS_KEY "SupAudioContexts" + +// Module lifecycle functions +static future_t* init(void) { + std::unique_lock lock(config_lock); + + if (is_factory_reset()) delete_config_files(); + + std::string file_source; + + config = btif_bap_config_open(CONFIG_FILE_PATH); + btif_bap_config_source = ORIGINAL; + if (!config) { + LOG_WARN(LOG_TAG, "%s unable to load config file: %s; using backup.", + __func__, CONFIG_FILE_PATH); + config = btif_bap_config_open(CONFIG_BACKUP_PATH); + btif_bap_config_source = BACKUP; + file_source = "Backup"; + } + + if (!config) { + LOG_ERROR(LOG_TAG, + "%s unable to transcode legacy file; creating empty config.", + __func__); + config = config_new_empty(); + btif_bap_config_source = NEW_FILE; + file_source = "Empty"; + } + + if (!config) { + LOG_ERROR(LOG_TAG, "%s unable to allocate a config object.", __func__); + goto error; + } + + if (!file_source.empty()) + config_set_string(config, INFO_SECTION, FILE_SOURCE, file_source.c_str()); + + // Cleanup temporary pairings if we have left guest mode + if (!is_restricted_mode()) btif_bap_config_remove_restricted(config); + + // Read or set config file creation timestamp + const char* time_str; + time_str = config_get_string(config, INFO_SECTION, FILE_TIMESTAMP, NULL); + if (time_str != NULL) { + strlcpy(btif_bap_config_time_created, time_str, TIME_STRING_LENGTH); + } else { + time_t current_time = time(NULL); + struct tm* time_created = localtime(¤t_time); + if (time_created) { + if (strftime(btif_bap_config_time_created, TIME_STRING_LENGTH, + TIME_STRING_FORMAT, time_created)) { + config_set_string(config, INFO_SECTION, FILE_TIMESTAMP, + btif_bap_config_time_created); + } + } + } + // TODO(sharvil): use a non-wake alarm for this once we have + // API support for it. There's no need to wake the system to + // write back to disk. + config_timer = alarm_new("btif_bap.config"); + if (!config_timer) { + LOG_ERROR(LOG_TAG, "%s unable to create alarm.", __func__); + goto error; + } + + LOG_EVENT_INT(BT_CONFIG_SOURCE_TAG_NUM, btif_bap_config_source); + + return future_new_immediate(FUTURE_SUCCESS); + +error: + alarm_free(config_timer); + if (config != NULL) + config_free(config); + config_timer = NULL; + config = NULL; + btif_bap_config_source = NOT_LOADED; + return future_new_immediate(FUTURE_FAIL); +} + +static config_t* btif_bap_config_open(const char* filename) { + config_t* config = config_new(filename); + if (!config) return NULL; + + return config; +} + +static void btif_bap_config_save(void) { + CHECK(config != NULL); + CHECK(config_timer != NULL); + + if (config_timer == NULL) { + LOG(WARNING) << __func__ << "config_timer is null"; + return; + } + alarm_set(config_timer, CONFIG_SETTLE_PERIOD_MS, timer_config_save_cb, NULL); +} + +static void btif_bap_config_flush(void) { + CHECK(config != NULL); + CHECK(config_timer != NULL); + + alarm_cancel(config_timer); + btif_bap_config_write(0, NULL); +} + +bool btif_bap_config_clear(void) { + CHECK(config != NULL); + CHECK(config_timer != NULL); + + alarm_cancel(config_timer); + + std::unique_lock lock(config_lock); + if (config != NULL) + config_free(config); + + config = config_new_empty(); + if (config == NULL) return false; + + bool ret = config_save(config, CONFIG_FILE_PATH); + btif_bap_config_source = RESET; + return ret; +} + +static future_t* shut_down(void) { + btif_bap_config_flush(); + return future_new_immediate(FUTURE_SUCCESS); +} + +static future_t* clean_up(void) { + btif_bap_config_flush(); + + alarm_free(config_timer); + config_timer = NULL; + + std::unique_lock lock(config_lock); + config_free(config); + config = NULL; + return future_new_immediate(FUTURE_SUCCESS); +} + +EXPORT_SYMBOL module_t btif_bap_config_module = {.name = BTIF_BAP_CONFIG_MODULE, + .init = init, + .start_up = NULL, + .shut_down = shut_down, + .clean_up = clean_up}; + +static void timer_config_save_cb(UNUSED_ATTR void* data) { + // Moving file I/O to btif context instead of timer callback because + // it usually takes a lot of time to be completed, introducing + // delays during A2DP playback causing blips or choppiness. + btif_transfer_context(btif_bap_config_write, 0, NULL, 0, NULL); +} + +static void btif_bap_config_write(UNUSED_ATTR uint16_t event, + UNUSED_ATTR char* p_param) { + CHECK(config != NULL); + CHECK(config_timer != NULL); + + std::unique_lock lock(config_lock); + rename(CONFIG_FILE_PATH, CONFIG_BACKUP_PATH); + if (config == NULL) { + LOG(WARNING) << __func__ << "config is null"; + return; + } + config_t* config_paired = config_new_clone(config); + + if (config_paired != NULL) { + //btif_bap_config_remove_unpaired(config_paired); + config_save(config_paired, CONFIG_FILE_PATH); + config_free(config_paired); + } +} + +static void btif_bap_config_remove_restricted(config_t* config) { + CHECK(config != NULL); + + if (config == NULL) { + LOG(WARNING) << __func__ << "config is null"; + return; + } + const config_section_node_t* snode = config_section_begin(config); + for(; snode != config_section_end(config); + snode = config_section_next(snode)) { + const char* section = config_section_name(snode); + // first check the address + if (config_has_key(config, section, "Restricted")) { + config_remove_section(config, section); + } + } +} + +static bool is_factory_reset(void) { + char factory_reset[PROPERTY_VALUE_MAX] = {0}; + osi_property_get("persist.bluetooth.factoryreset", factory_reset, "false"); + return strncmp(factory_reset, "true", 4) == 0; +} + +static void delete_config_files(void) { + remove(CONFIG_FILE_PATH); + remove(CONFIG_BACKUP_PATH); + osi_property_set("persist.bluetooth.factoryreset", "false"); +} + +static bool btif_bap_get_section_index(const std::string §ion, + uint16_t *index) { + char *temp = nullptr; + if (section.length() != 20) return false; + + std::vector byte_tokens = + base::SplitString(section, ":", base::TRIM_WHITESPACE, + base::SPLIT_WANT_ALL); + + LOG(WARNING) << __func__ << ": LC# codec "; + if (byte_tokens.size() != 7) return false; + + // get the last nibble + const auto& token = byte_tokens[6]; + + if (token.length() != 2) return false; + + *index = strtol(token.c_str(), &temp, 16); + + if (*temp != '\0') return false; + + return true; +} + +static bool btif_bap_get_free_section_id(const RawAddress& bd_addr, + char *section) { + uint16_t i = 0; + uint8_t index_status[MAX_INDEX] = {0}; + std::string addrstr = bd_addr.ToString(); + const char* bdstr = addrstr.c_str(); + + // reserve the first index for sink, src, locations + index_status[0] = INDEX_OCCUPIED; + + const config_section_node_t* snode = config_section_begin(config); + for(; snode != config_section_end(config); + snode = config_section_next(snode)) { + const char* section = config_section_name(snode); + uint16_t index; + + // first check the address + if(!strcasestr(section, bdstr)) { + continue; + } + + if(btif_bap_get_section_index(section, &index)) { + index_status[index] = INDEX_OCCUPIED; + } + } + + // find the unused index + for(i = 0; i < MAX_INDEX; i++) { + if(index_status[i] == INDEX_FREE) break; + } + + if(i != MAX_INDEX) { + char index_str[MAX_INDEX_LEN]; + // form the section entry ( bd address plus index) + snprintf(index_str, sizeof(index_str), ":%02x", i); + strlcpy(section, bdstr, MAX_SECTION_LEN); + strlcat(section, index_str, MAX_SECTION_LEN); + return true; + } else { + return false; + } +} + +static bool btif_bap_find_sections(const RawAddress& bd_addr, + btif_bap_record_type_t rec_type, + uint16_t context_type, + CodecDirection direction, + CodecConfig *record, + std::vector *sections) { + std::string addrstr = bd_addr.ToString(); + const char* bdstr = addrstr.c_str(); + const config_section_node_t* snode = config_section_begin(config); + for(; snode != config_section_end(config); + snode = config_section_next(snode)) { + const char *section = config_section_name(snode); + // first check the address + if(!strcasestr(section, bdstr)) { + continue; + } + + // next check the record type + const char* value_str = config_get_string(config, section, + BAP_RECORD_TYPE_KEY, NULL); + if(value_str == nullptr || + ((rec_type == REC_TYPE_CAPABILITY && + strcasecmp(value_str, BAP_RECORD_TYPE_CAPA)) || + (rec_type == REC_TYPE_CONFIGURATION && + strcasecmp(value_str, BAP_RECORD_TYPE_CONF)))) { + continue; + } + + // next check the record type + uint16_t context = config_get_uint16(config, section, + BAP_CONTEXT_TYPE_KEY, 0XFFFF); + LOG(WARNING) << __func__ << ": context " << context + << ": context_type " << context_type; + if(context != context_type) { + continue; + } + + // next check the direction + value_str = config_get_string(config, section, + BAP_DIRECTION_KEY, NULL); + if(value_str == nullptr || + ((direction == CodecDirection::CODEC_DIR_SRC && + strcasecmp(value_str, "SRC")) || + (direction == CodecDirection::CODEC_DIR_SINK && + strcasecmp(value_str, "SINK")))) { + continue; + } + + if(record == nullptr) { + sections->push_back((char*) section); + } else { + // next check codec type + value_str = config_get_string(config, section, + BAP_CODEC_TYPE_KEY, NULL); + + if(value_str == nullptr || + (record->codec_type == CodecIndex::CODEC_INDEX_SOURCE_LC3 && + strcasecmp(value_str, "LC3"))) { + continue; + } + + // next check the freqency + uint16_t value = config_get_uint16(config, section, + BAP_SAMP_FREQS_KEY, 0); + if(value == static_cast (record->sample_rate)) { + sections->push_back((char*) section); + } + } + } + + if(sections->size()) { + return true; + } else { + return false; + } +} + +static bool btif_bap_update_LC3_codec_info(char *section, CodecConfig *record, + btif_bap_record_type_t rec_type) { + if(section == nullptr || record == nullptr) { + return false; + } + + if(rec_type == REC_TYPE_CAPABILITY) { + + config_set_string(config, section, BAP_RECORD_TYPE_KEY, + BAP_RECORD_TYPE_CAPA); + + if(record->codec_type == CodecIndex::CODEC_INDEX_SOURCE_LC3) { + config_set_string(config, section, BAP_CODEC_TYPE_KEY, "LC3"); + } + // update freqs + config_set_uint16(config, section, BAP_SAMP_FREQS_KEY , + static_cast(record->sample_rate)); + + // update chnl count + config_set_uint16(config, section, BAP_CHNL_ALLOCATION_KEY, + static_cast (record->channel_mode)); + + // update supp frames + config_set_uint16(config, section, BAP_SUPP_FRM_DURATIONS_KEY , + static_cast (GetCapaSupFrameDurations(record))); + + // update chnl supp min octs per frame + config_set_uint16(config, section, BAP_SUP_MIN_OCTS_PER_FRAME_KEY , + static_cast (GetCapaSupOctsPerFrame(record) & + 0xFFFF)); + + // update chnl supp max octs per frame + config_set_uint16(config, section, BAP_SUP_MAX_OCTS_PER_FRAME_KEY, + static_cast ((GetCapaSupOctsPerFrame(record) & + 0xFFFF0000) >> 16)); + + // update max supp codec frames per sdu + config_set_uint16(config, section, BAP_MAX_SUP_CODEC_FRAMES_PER_SDU, + static_cast (GetCapaMaxSupLc3Frames(record))); + + // update LC3Q support + if (GetCapaVendorMetaDataLc3QPref(record)) { + config_set_string(config, section, BAP_LC3Q_SUP_KEY, "true"); + } else { + config_set_string(config, section, BAP_LC3Q_SUP_KEY, "false"); + } + + // update LC3Q Version + config_set_uint16(config, section, BAP_LC3Q_VER_KEY, + static_cast (GetCapaVendorMetaDataLc3QVer(record))); + } else { + + config_set_string(config, section, BAP_RECORD_TYPE_KEY, + BAP_RECORD_TYPE_CONF); + + if(record->codec_type == CodecIndex::CODEC_INDEX_SOURCE_LC3) { + config_set_string(config, section, BAP_CODEC_TYPE_KEY, "LC3"); + } + + // update freqs + config_set_uint16(config, section, BAP_SAMP_FREQS_KEY , + static_cast(record->sample_rate)); + + // update chnl count + config_set_uint16(config, section, BAP_CHNL_ALLOCATION_KEY, + static_cast (record->channel_mode)); + + // update configured frame duration + config_set_uint16(config, section, BAP_CONF_FRAME_DUR_KEY, + static_cast (GetFrameDuration(record))); + + // update configured octs per frame + config_set_uint16(config, section, BAP_CONF_OCTS_PER_FRAME_KEY, + static_cast (GetOctsPerFrame(record))); + + // update LC3 frames per SDU + config_set_uint16(config, section, BAP_LC3_FRAMES_PER_SDU_KEY, + static_cast (GetLc3BlocksPerSdu(record))); + } + + if (is_restricted_mode()) { + LOG(WARNING) << __func__ << ": records will be removed if unrestricted"; + config_set_uint16(config, section, "Restricted", 1); + } + + return true; +} + +bool btif_bap_add_record(const RawAddress& bd_addr, + btif_bap_record_type_t rec_type, + uint16_t context_type, + CodecDirection direction, + CodecConfig *record) { + // first check if same record already exists + std::unique_lock lock(config_lock); + std::vector sections; + + if(btif_bap_find_sections(bd_addr, rec_type, context_type, + direction, record, §ions)) { + for (auto it = sections.begin(); + it != sections.end(); it++) { + btif_bap_update_LC3_codec_info((*it), record , rec_type); + } + } else { + LOG(WARNING) << __func__ << ": section not found"; + char section[MAX_SECTION_LEN]; + btif_bap_get_free_section_id(bd_addr, section); + + config_set_uint16(config, section, BAP_CONTEXT_TYPE_KEY, context_type); + + if(direction == CodecDirection::CODEC_DIR_SRC) { + config_set_string(config, section, BAP_DIRECTION_KEY, "SRC"); + } else { + config_set_string(config, section, BAP_DIRECTION_KEY, "SINK"); + } + btif_bap_update_LC3_codec_info(section, record , rec_type); + } + btif_bap_config_save(); + return true; +} + +bool btif_bap_remove_record(const RawAddress& bd_addr, + btif_bap_record_type_t rec_type, + uint16_t context_type, + CodecDirection direction, + CodecConfig *record) { + // first check if same record exists + // if exists remove the record by complete section + std::unique_lock lock(config_lock); + bool record_removed = false; + std::vector sections; + + if(btif_bap_find_sections(bd_addr, rec_type, context_type, + direction, record, §ions)) { + for (auto it = sections.begin(); + it != sections.end(); it++) { + config_remove_section(config, (*it)); + } + record_removed = true; + btif_bap_config_flush(); + } + return record_removed; +} + +bool btif_bap_remove_record_by_context(const RawAddress& bd_addr, + btif_bap_record_type_t rec_type, + uint16_t context_type, + CodecDirection direction) { + // first check if same record exists + // if exists remove the record by complete section + std::unique_lock lock(config_lock); + bool record_removed = false; + std::vector sections; + + if(btif_bap_find_sections(bd_addr, rec_type, context_type, + direction, nullptr, §ions)) { + for (auto it = sections.begin(); + it != sections.end(); it++) { + config_remove_section(config, (*it)); + } + record_removed = true; + btif_bap_config_flush(); + } + return record_removed; +} + +bool btif_bap_remove_all_records(const RawAddress& bd_addr) { + // loop through the file if any record is found delete it + std::unique_lock lock(config_lock); + std::string addrstr = bd_addr.ToString(); + const char* bdstr = addrstr.c_str(); + bool record_removed = false; + const config_section_node_t* snode = config_section_begin(config); + for(; snode != config_section_end(config); + snode = config_section_next(snode)) { + const char* section = config_section_name(snode); + // first check the address + if(strcasestr(section, bdstr)) { + record_removed = true; + config_remove_section(config, section); + } + } + btif_bap_config_flush(); + return record_removed; +} + +bool btif_bap_get_records(const RawAddress& bd_addr, + btif_bap_record_type_t rec_type, + uint16_t context_type, + CodecDirection direction, + std::vector *pac_records) { + std::unique_lock lock(config_lock); + std::vector sections; + + if(btif_bap_find_sections(bd_addr, rec_type, context_type, + direction, nullptr, §ions)) { + for (auto it = sections.begin(); + it != sections.end(); it++) { + CodecConfig record; + memset(&record, 0, sizeof(record)); + + if(config_has_key(config, (*it), BAP_SAMP_FREQS_KEY)) { + record.sample_rate = static_cast + (config_get_uint16(config, (*it), + BAP_SAMP_FREQS_KEY, + 0x00)); + } + + if (config_has_key(config, (*it), BAP_LC3Q_SUP_KEY)) { + bool lc3q_sup = false; + const char* is_lc3q_sup = config_get_string(config, (*it), + BAP_LC3Q_SUP_KEY, + ""); + if(!strcmp(is_lc3q_sup, "true")) { + lc3q_sup = true; + } + LOG(WARNING) << __func__ << ": lc3q_sup: " << lc3q_sup; + if (lc3q_sup) { + UpdateCapaVendorMetaDataLc3QPref(&record, lc3q_sup); + } + } + + if(config_has_key(config, (*it), BAP_LC3Q_VER_KEY)) { + uint16_t lc3q_ver = config_get_uint16(config, (*it), + BAP_LC3Q_VER_KEY, + 0x00); + LOG(WARNING) << __func__ << ": lc3q_ver: " << lc3q_ver; + UpdateCapaVendorMetaDataLc3QVer(&record, lc3q_ver); + } + + record.codec_type = CodecIndex::CODEC_INDEX_SOURCE_LC3; + if(rec_type == REC_TYPE_CAPABILITY) { + + if(config_has_key(config, (*it), BAP_SUPP_FRM_DURATIONS_KEY)) { + uint16_t supp_frames = config_get_uint16(config, (*it), + BAP_SUPP_FRM_DURATIONS_KEY, + 0x00); + UpdateCapaSupFrameDurations(&record, supp_frames); + } + + // update chnl supp octs per frame + if(config_has_key(config, (*it), BAP_SUP_MIN_OCTS_PER_FRAME_KEY) && + config_has_key(config, (*it), BAP_SUP_MAX_OCTS_PER_FRAME_KEY)) { + uint16_t sup_min_octs = config_get_uint16(config, (*it), + BAP_SUP_MIN_OCTS_PER_FRAME_KEY, + 0x00); + uint16_t sup_max_octs = config_get_uint16(config, (*it), + BAP_SUP_MAX_OCTS_PER_FRAME_KEY, + 0x00); + UpdateCapaSupOctsPerFrame(&record, sup_min_octs | sup_max_octs << 16); + } + + // update max supp codec frames per sdu + if(config_has_key(config, (*it), BAP_MAX_SUP_CODEC_FRAMES_PER_SDU)) { + uint16_t max_sup_codec_frames_per_sdu = config_get_uint16(config, (*it), + BAP_MAX_SUP_CODEC_FRAMES_PER_SDU, + 0x00); + UpdateCapaMaxSupLc3Frames(&record, max_sup_codec_frames_per_sdu); + } + + // update preferred context type. + if(config_has_key(config, (*it), BAP_CONTEXT_TYPE_KEY)) { + uint16_t context_type = config_get_uint16(config, (*it), + BAP_CONTEXT_TYPE_KEY, + 0x00); + UpdateCapaPreferredContexts(&record, context_type); + } + } else { + + if(config_has_key(config, (*it), BAP_CONF_FRAME_DUR_KEY)) { + uint16_t conf_frames = config_get_uint16(config, (*it), + BAP_CONF_FRAME_DUR_KEY, + 0x00); + UpdateFrameDuration(&record, conf_frames); + } + + if(config_has_key(config, (*it), BAP_CONF_OCTS_PER_FRAME_KEY)) { + uint16_t conf_octs_per_frame = config_get_uint16(config, (*it), + BAP_CONF_OCTS_PER_FRAME_KEY, + 0x00); + UpdateOctsPerFrame(&record, conf_octs_per_frame); + } + + if(config_has_key(config, (*it), BAP_LC3_FRAMES_PER_SDU_KEY)) { + uint16_t lc3_frms_per_sdu = config_get_uint16(config, (*it), + BAP_LC3_FRAMES_PER_SDU_KEY, + 0x00); + UpdateLc3BlocksPerSdu(&record, lc3_frms_per_sdu); + } + } + pac_records->push_back(record); + } + } + + if(pac_records->size()) { + return true; + } else { + return false; + } +} + +bool btif_bap_add_audio_loc(const RawAddress& bd_addr, + CodecDirection direction, uint32_t audio_loc) { + // first check if same already exists + // if exists update the same entry + // audio location will always be stored @ 0th index + // form the section entry ( bd address plus index) + std::string addrstr = bd_addr.ToString(); + const char* bdstr = addrstr.c_str(); + char section[MAX_SECTION_LEN]; + char index[MAX_INDEX_LEN]; + snprintf(index, sizeof(index), ":%02x", 0); + strlcpy(section, bdstr, sizeof(section)); + strlcat(section, index, sizeof(section)); + if(direction == CodecDirection::CODEC_DIR_SRC) { + config_set_int(config, section, BAP_SRC_LOCATIONS_KEY, audio_loc); + } else { + config_set_int(config, section, BAP_SINK_LOCATIONS_KEY, audio_loc); + } + btif_bap_config_save(); + return true; +} + +bool btif_bap_rem_audio_loc(const RawAddress& bd_addr, + CodecDirection direction) { + // first check if same record already exists + // if exists remove the record by complete section + std::string addrstr = bd_addr.ToString(); + const char* bdstr = addrstr.c_str(); + char section[MAX_SECTION_LEN]; + char index[MAX_INDEX_LEN]; + snprintf(index, sizeof(index), ":%02x", 0); + strlcpy(section, bdstr, sizeof(section)); + strlcat(section, index, sizeof(section)); + if(direction == CodecDirection::CODEC_DIR_SRC) { + config_remove_key(config, section, "BAP_SRC_LOCATIONS_KEY"); + } else { + config_remove_key(config, section, "BAP_SINK_LOCATIONS_KEY"); + } + btif_bap_config_flush(); + return true; +} + +bool btif_bap_add_supp_contexts(const RawAddress& bd_addr, + uint32_t supp_contexts) { + // first check if same already exists + // if exists update the same entry + // supp contexts will always be stored @ 0th index + // form the section entry ( bd address plus index) + std::string addrstr = bd_addr.ToString(); + const char* bdstr = addrstr.c_str(); + char section[MAX_SECTION_LEN]; + char index[MAX_INDEX_LEN]; + snprintf(index, sizeof(index), ":%02x", 0); + strlcpy(section, bdstr, sizeof(section)); + strlcat(section, index, sizeof(section)); + LOG(WARNING) << __func__ << " supp_contexts " << supp_contexts; + config_set_uint64(config, section, + BAP_SUP_AUDIO_CONTEXTS_KEY, supp_contexts); + btif_bap_config_save(); + return true; +} + +bool btif_bap_get_supp_contexts(const RawAddress& bd_addr, + uint32_t *supp_contexts) { + std::string addrstr = bd_addr.ToString(); + const char* bdstr = addrstr.c_str(); + char section[MAX_SECTION_LEN]; + char index[MAX_INDEX_LEN]; + snprintf(index, sizeof(index), ":%02x", 0); + strlcpy(section, bdstr, sizeof(section)); + strlcat(section, index, sizeof(section)); + *supp_contexts = config_get_uint64(config, section, + BAP_SUP_AUDIO_CONTEXTS_KEY, 0); + return true; +} + +bool btif_bap_rem_supp_contexts(const RawAddress& bd_addr) { + std::string addrstr = bd_addr.ToString(); + const char* bdstr = addrstr.c_str(); + char section[MAX_SECTION_LEN]; + char index[MAX_INDEX_LEN]; + snprintf(index, sizeof(index), ":%02x", 0); + strlcpy(section, bdstr, sizeof(section)); + strlcat(section, index, sizeof(section)); + config_remove_key(config, section, BAP_SUP_AUDIO_CONTEXTS_KEY); + btif_bap_config_flush(); + return true; +} diff --git a/le_audio/system/bt/btif/src/btif_bap_uclient.cc b/le_audio/system/bt/btif/src/btif_bap_uclient.cc new file mode 100644 index 00000000000..7a049a5253c --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_bap_uclient.cc @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/****************************************************************************** + * + * Copyright 2018 The Android Open Source Project + * + * 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. + * + ******************************************************************************/ + +#include "bta_closure_api.h" +#include "bta_bap_uclient_api.h" +#include "btif_common.h" +#include "btif_storage.h" +#include "osi/include/thread.h" +#include "btif_bap_codec_utils.h" + +#include "osi/include/properties.h" + +extern void do_in_bta_thread(const base::Location& from_here, + const base::Closure& task); +#include +#include +#include +#include +#include +#include +#include +#include "btif_api.h" + +using base::Bind; +using base::Unretained; +using bluetooth::bap::ucast::UcastClient; +using bluetooth::bap::pacs::CodecConfig; +using bluetooth::bap::pacs::ConnectionState; +using bluetooth::bap::ucast::UcastClientCallbacks; +using bluetooth::bap::ucast::UcastClientInterface; +using bluetooth::bap::ucast::StreamConnect; +using bluetooth::bap::ucast::StreamType; +using bluetooth::bap::ucast::StreamStateInfo; +using bluetooth::bap::ucast::StreamConfigInfo; +using bluetooth::bap::ucast::StreamReconfig; + +namespace bluetooth { +namespace bap { +namespace ucast { + +class UcastClientInterfaceImpl; +static std::unique_ptr UcastClientInstance = nullptr; + +class UcastClientInterfaceImpl + : public UcastClientInterface, + public UcastClientCallbacks { + ~UcastClientInterfaceImpl() = default; + void Init(UcastClientCallbacks* client_callbacks) override { + if(is_initialized) { + LOG(WARNING) << __func__ << " Already initialized, return"; + return; + } + is_initialized = true; + callbacks = client_callbacks; + char value[PROPERTY_VALUE_MAX]; + if(property_get("persist.vendor.service.bt.bap.enable_ucast", value, "false") + && !strcmp(value, "true")) { + LOG(INFO) << __func__ << " Registering PACS UUID "; + btif_register_uuid_srvc_disc(Uuid::FromString("1850")); + btif_register_uuid_srvc_disc(Uuid::FromString("184E")); + } + do_in_bta_thread( FROM_HERE, Bind(&UcastClient::Initialize, this)); + } + + void OnStreamState(const RawAddress &address, + std::vector streams_state_info) override { + if(!is_initialized) return; + do_in_jni_thread(FROM_HERE, Bind(&UcastClientCallbacks::OnStreamState, + Unretained(callbacks), address, + streams_state_info)); + } + + void OnStreamConfig(const RawAddress &address, + std::vector streams_config_info) override { + if(!is_initialized) return; + do_in_jni_thread(FROM_HERE, Bind(&UcastClientCallbacks::OnStreamConfig, + Unretained(callbacks), + address, streams_config_info)); + } + + void OnStreamAvailable(const RawAddress &address, + uint16_t src_audio_contexts, + uint16_t sink_audio_contexts) override { + if(!is_initialized) return; + do_in_jni_thread(FROM_HERE, + Bind(&UcastClientCallbacks::OnStreamAvailable, + Unretained(callbacks), + address, src_audio_contexts, + sink_audio_contexts)); + } + + void Reconfigure(const RawAddress& address, + std::vector &streams_info) override { + LOG(INFO) << __func__ << " " << address; + if(!is_initialized) return; + do_in_bta_thread(FROM_HERE, Bind(&UcastClient::Reconfigure, + Unretained(UcastClient::Get()), + address, streams_info)); + } + + void Start(const RawAddress& address, + std::vector &streams_info) override { + LOG(INFO) << __func__ << " " << address; + if(!is_initialized) return; + do_in_bta_thread(FROM_HERE, Bind(&UcastClient::Start, + Unretained(UcastClient::Get()), + address, streams_info)); + } + + void Connect(std::vector &address, bool is_direct, + std::vector &streams_info) override { + for (auto it = address.begin(); it != address.end(); it++) { + LOG(INFO) << __func__ << " " << (*it); + } + if(!is_initialized) return; + do_in_bta_thread(FROM_HERE, Bind(&UcastClient::Connect, + Unretained(UcastClient::Get()), + address, is_direct, streams_info)); + } + + void Disconnect(const RawAddress& address, + std::vector &streams_info) override { + LOG(INFO) << __func__ << " " << address; + if(!is_initialized) return; + do_in_bta_thread(FROM_HERE, Bind(&UcastClient::Disconnect, + Unretained(UcastClient::Get()), + address, streams_info)); + } + + void Stop(const RawAddress& address, + std::vector &streams_info) override { + LOG(INFO) << __func__ << " " << address; + if(!is_initialized) return; + do_in_bta_thread(FROM_HERE, Bind(&UcastClient::Stop, + Unretained(UcastClient::Get()), + address, streams_info)); + } + + void UpdateStream(const RawAddress& address, + std::vector &update_streams) override { + LOG(INFO) << __func__ << " " << address; + if(!is_initialized) return; + do_in_bta_thread(FROM_HERE, Bind(&UcastClient::UpdateStream, + Unretained(UcastClient::Get()), + address, update_streams)); + } + + void Cleanup() override { + if(!is_initialized) return; + do_in_bta_thread(FROM_HERE, Bind(&UcastClient::CleanUp)); + is_initialized = false; + } + + private: + bool is_initialized = false;; + UcastClientCallbacks* callbacks; +}; + +UcastClientInterface* btif_bap_uclient_get_interface() { + if (!UcastClientInstance) + UcastClientInstance.reset(new UcastClientInterfaceImpl()); + return UcastClientInstance.get(); +} + +} // namespace ucast +} // namespace bap +} // namespace bluetooth diff --git a/le_audio/system/bt/btif/src/btif_bap_uclient_test.cc b/le_audio/system/bt/btif/src/btif_bap_uclient_test.cc new file mode 100644 index 00000000000..45a1a4a9a4f --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_bap_uclient_test.cc @@ -0,0 +1,2957 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + *****************************************************************************/ + +#include "bta_closure_api.h" +#include "bta_bap_uclient_api.h" +#include "btif_common.h" +#include "btif_storage.h" +#include "osi/include/thread.h" +#include "btif_bap_codec_utils.h" + +#include "osi/include/properties.h" + +extern void do_in_bta_thread(const base::Location& from_here, + const base::Closure& task); +#include +#include +#include +#include +#include +#include +#include +#include "btif_bap_config.h" + +using base::Bind; +using base::Unretained; +using bluetooth::bap::ucast::UcastClient; +using bluetooth::bap::pacs::CodecConfig; +using bluetooth::bap::pacs::ConnectionState; +using bluetooth::bap::ucast::UcastClientCallbacks; +using bluetooth::bap::ucast::UcastClientInterface; +using bluetooth::bap::ucast::StreamConnect; +using bluetooth::bap::ucast::StreamType; +using bluetooth::bap::ucast::StreamStateInfo; +using bluetooth::bap::ucast::StreamConfigInfo; +using bluetooth::bap::ucast::StreamReconfig; + +using bluetooth::bap::pacs::CodecIndex; +using bluetooth::bap::pacs::CodecPriority; +using bluetooth::bap::pacs::CodecSampleRate; +using bluetooth::bap::pacs::CodecFrameDuration; +using bluetooth::bap::pacs::CodecChannelMode; +using bluetooth::bap::ucast::CodecQosConfig; +using bluetooth::bap::ucast::CISConfig; +using bluetooth::bap::ucast::CONTENT_TYPE_MEDIA; +using bluetooth::bap::ucast::CONTENT_TYPE_CONVERSATIONAL; +using bluetooth::bap::ucast::CONTENT_TYPE_GAME; +using bluetooth::bap::ucast::CONTENT_TYPE_LIVE; +using bluetooth::bap::ucast::CONTENT_TYPE_UNSPECIFIED; +using bluetooth::bap::ucast::ASE_DIRECTION_SRC; +using bluetooth::bap::ucast::ASE_DIRECTION_SINK; +using bluetooth::bap::ucast::StreamReconfigType; +using bluetooth::bap::ucast::ASCSConfig; + +static thread_t *test_thread; +static UcastClientInterface* sUcastClientInterface = nullptr; +static RawAddress bap_bd_addr; + +extern bluetooth::bap::pacs::PacsClientInterface* btif_pacs_client_get_interface(); + +class UcastClientCallbacksImpl : public UcastClientCallbacks { + public: + ~UcastClientCallbacksImpl() = default; + void OnStreamState(const RawAddress &address, + std::vector streams_state_info) override { + for (auto it = streams_state_info.begin(); + it != streams_state_info.end(); it++) { + LOG(WARNING) << __func__ << " stream type " << (it->stream_type.type); + LOG(WARNING) << __func__ << " stream dir " << loghex(it->stream_type.direction); + LOG(WARNING) << __func__ << " stream state " << static_cast (it->stream_state); + } + } + void OnStreamConfig(const RawAddress &address, + std::vector streams_config_info) override { + LOG(WARNING) << __func__; + for (auto it = streams_config_info.begin(); + it != streams_config_info.end(); it++) { + LOG(WARNING) << __func__ << " stream type " << (it->stream_type.type); + LOG(WARNING) << __func__ << " stream dir " << loghex(it->stream_type.direction); + LOG(WARNING) << __func__ << " location " << static_cast (it->audio_location); + btif_bap_add_record(address, REC_TYPE_CONFIGURATION, + it->stream_type.type, + static_cast (it->stream_type.direction), + &it->codec_config); + + std::vector acm_pac_records; + + btif_bap_get_records(address, + REC_TYPE_CAPABILITY, + CONTENT_TYPE_MEDIA | + CONTENT_TYPE_UNSPECIFIED, + static_cast + (it->stream_type.direction), + &acm_pac_records); + + LOG(WARNING) << __func__ << " acm len to be 3" << (acm_pac_records.size()); + + std::vector config_pac_records; + btif_bap_get_records(address, + REC_TYPE_CONFIGURATION, + CONTENT_TYPE_MEDIA, + static_cast + (it->stream_type.direction), + &config_pac_records); + + LOG(WARNING) << __func__ << " configs len to be 1" << (config_pac_records.size()); + + btif_bap_remove_record_by_context (address, REC_TYPE_CONFIGURATION, + it->stream_type.type, + static_cast + (it->stream_type.direction)); + + config_pac_records.clear(); + btif_bap_get_records(address, + REC_TYPE_CONFIGURATION, + CONTENT_TYPE_MEDIA, + static_cast + (it->stream_type.direction), + &config_pac_records); + + LOG(WARNING) << __func__ << " configs len to be 0" << (config_pac_records.size()); + +#if 0 + btif_bap_remove_record_by_context (address, REC_TYPE_CAPABILITY, + CONTENT_TYPE_MEDIA | + CONTENT_TYPE_UNSPECIFIED, + static_cast + (it->stream_type.direction)); + + acm_pac_records.clear(); + btif_bap_get_records(address, + REC_TYPE_CAPABILITY, + CONTENT_TYPE_MEDIA | + CONTENT_TYPE_UNSPECIFIED, + static_cast + (it->stream_type.direction), + &acm_pac_records); + + LOG(WARNING) << __func__ << " acm len to be 0" << (acm_pac_records.size()); +#endif + } + } + void OnStreamAvailable(const RawAddress &address, + uint16_t src_audio_contexts, + uint16_t sink_audio_contexts) override { + LOG(WARNING) << __func__; + } +}; + +static UcastClientCallbacksImpl sUcastClientCallbacks; + +static void event_test_bap_uclient(UNUSED_ATTR void* context) { + LOG(INFO) << __func__ << " start " ; + sUcastClientInterface = bluetooth::bap::ucast::btif_bap_uclient_get_interface(); + sUcastClientInterface->Init(&sUcastClientCallbacks); + sleep(1); + + StreamConnect conn_info; + CodecQosConfig codec_qos_config; + codec_qos_config.codec_config.codec_type = CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_48000; + codec_qos_config.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config.codec_config, 100); + codec_qos_config.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + CISConfig cis_config_1 = { + .cis_id = 0, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + CISConfig cis_config_2 = { + .cis_id = 1, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + codec_qos_config.qos_config.cis_configs.push_back(cis_config_1); + codec_qos_config.qos_config.cis_configs.push_back(cis_config_2); + + ASCSConfig ascs_config = { + .cig_id = 1, + .cis_id = 0, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + codec_qos_config.qos_config.ascs_configs.push_back(ascs_config); + conn_info.stream_type.type = 0x0004; // media + conn_info.stream_type.direction = ASE_DIRECTION_SINK; + conn_info.stream_type.audio_context = + CONTENT_TYPE_MEDIA; + conn_info.codec_qos_config_pair.push_back(codec_qos_config); + std::vector streams; + streams.push_back(conn_info); + + ASCSConfig ascs_config_2 = { + .cig_id = 1, + .cis_id = 1, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + codec_qos_config.qos_config.ascs_configs.clear(); + codec_qos_config.qos_config.ascs_configs.push_back(ascs_config_2); + StreamConnect conn_info_2; + conn_info_2.stream_type.type = 0x0004; // media + conn_info_2.stream_type.direction = ASE_DIRECTION_SINK; + conn_info_2.stream_type.audio_context = + CONTENT_TYPE_MEDIA; + conn_info_2.codec_qos_config_pair.push_back(codec_qos_config); + std::vector streams_2; + streams_2.push_back(conn_info_2); + + // reconfig information + std::vector reconf_streams; + codec_qos_config.qos_config.ascs_configs[0].cis_id = 1; + UpdateFrameDuration(&codec_qos_config.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_7_5)); + + codec_qos_config.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x4C, 0x1D, 0x00}, + .sdu_interval_s_to_m = {0x4C, 0x1D, 0x00} + }; + + StreamReconfig reconf_info; + reconf_info.stream_type.type = 0x0004; // media + reconf_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_info.codec_qos_config_pair.push_back(codec_qos_config); + reconf_streams.push_back(reconf_info); + + std::vector reconf_qos_streams; + StreamReconfig reconf_qos_info; + reconf_qos_info.stream_type.type = 0x0004; // media + reconf_qos_info.reconf_type = StreamReconfigType::QOS_CONFIG; + reconf_qos_info.stream_type.direction = ASE_DIRECTION_SINK; + codec_qos_config.qos_config.cis_configs.clear(); + + cis_config_1.rtn_m_to_s = 1; + cis_config_1.rtn_s_to_m = 1; + cis_config_2.rtn_m_to_s = 1; + cis_config_2.rtn_s_to_m = 1; + codec_qos_config.qos_config.cis_configs.push_back(cis_config_1); + codec_qos_config.qos_config.cis_configs.push_back(cis_config_2); + reconf_qos_info.codec_qos_config_pair.push_back(codec_qos_config); + reconf_qos_streams.push_back(reconf_qos_info); + + StreamType type_1 = { .type = 0x0004, + .direction = ASE_DIRECTION_SINK + }; + + RawAddress bap_bd_addr_2; + RawAddress::FromString("00:02:5B:00:FF:01", bap_bd_addr_2); + + std::vector start_streams; + start_streams.push_back(type_1); + + char bap_test[150] = "generic"; + + property_get("persist.vendor.service.bt.bap.test", bap_test, "generic"); + + LOG(INFO) << __func__ << " property" << bap_test; + + if(!strcmp(bap_test, "generic")) { + LOG(INFO) << __func__ << " going for generic test case"; + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sUcastClientInterface->Connect( bap_bd_addr_2, true, streams_2); + + sleep(10); + + LOG(INFO) << __func__ << " going for stream start 1 "; + //sUcastClientInterface->Start( bap_bd_addr, start_streams); + + LOG(INFO) << __func__ << " going for stream start 2"; + //sUcastClientInterface->Start( bap_bd_addr_2, start_streams); + + sleep(10); + + LOG(INFO) << __func__ << "going for stream disconnect 1 "; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + LOG(INFO) << __func__ << "going for stream disconnect 2 "; + sUcastClientInterface->Disconnect( bap_bd_addr_2, start_streams); + + } else if(!strcmp(bap_test, "stereo_recording_stereo_dual_cis")) { + LOG(INFO) << __func__ << " going for rxonly test case"; + LOG(INFO) << __func__ << " going for connect"; + StreamConnect conn_info_media_recording; + + CodecQosConfig codec_qos_config_media_rx_1; + CodecQosConfig codec_qos_config_media_rx_2; + + CISConfig cis_config_1_media_rx = { + .cis_id = 0, + .max_sdu_m_to_s = 0, + .max_sdu_s_to_m = 100, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + + CISConfig cis_config_2_media_rx = { + .cis_id = 1, + .max_sdu_m_to_s = 0, + .max_sdu_s_to_m = 100, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + + CISConfig cis_config_1_media_rx_2 = { + .cis_id = 0, + .max_sdu_m_to_s = 0, + .max_sdu_s_to_m = 200, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + + ASCSConfig ascs_config_1 = { + .cig_id = 1, + .cis_id = 0, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_2 = { + .cig_id = 1, + .cis_id = 1, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + codec_qos_config_media_rx_1.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_media_rx_1.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_media_rx_1.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_48000; + codec_qos_config_media_rx_1.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_media_rx_1.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_media_rx_1.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_media_rx_1.codec_config, 100); + + + codec_qos_config_media_rx_2 = codec_qos_config_media_rx_1; + UpdateOctsPerFrame(&codec_qos_config_media_rx_2.codec_config, 200); + + codec_qos_config_media_rx_1.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_media_rx_1.qos_config.cis_configs.push_back(cis_config_1_media_rx); + codec_qos_config_media_rx_1.qos_config.cis_configs.push_back(cis_config_2_media_rx); + + codec_qos_config_media_rx_1.qos_config.ascs_configs.push_back(ascs_config_1); + codec_qos_config_media_rx_1.qos_config.ascs_configs.push_back(ascs_config_2); + + codec_qos_config_media_rx_2.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 1, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_media_rx_2.qos_config.cis_configs.push_back(cis_config_1_media_rx_2); + + codec_qos_config_media_rx_2.qos_config.ascs_configs.push_back(ascs_config_1); + + StreamType type_media_rx = { .type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + + conn_info_media_recording.stream_type.type = CONTENT_TYPE_MEDIA; + conn_info_media_recording.stream_type.audio_context = CONTENT_TYPE_LIVE; + conn_info_media_recording.stream_type.direction = ASE_DIRECTION_SRC; + conn_info_media_recording.codec_qos_config_pair.push_back(codec_qos_config_media_rx_1); + conn_info_media_recording.codec_qos_config_pair.push_back(codec_qos_config_media_rx_2); + + std::vector media_rx_streams; + media_rx_streams.push_back(conn_info_media_recording); + + std::vector streams; + streams.push_back(type_media_rx); + + + sUcastClientInterface->Connect( bap_bd_addr, true, media_rx_streams); + + sleep(15); + + LOG(INFO) << __func__ << " going for stream start 1 "; + sUcastClientInterface->Start( bap_bd_addr, streams); + + sleep(10); + + sUcastClientInterface->Stop( bap_bd_addr, streams); + + sleep(10); + + LOG(INFO) << __func__ << "going for stream disconnect 1 "; + sUcastClientInterface->Disconnect( bap_bd_addr, streams); + + } else if(!strcmp(bap_test, "dual_streams_media_tx_voice_rx")) { + StreamConnect conn_info_media; + StreamConnect conn_info_gaming; + + // reconfig information + CodecQosConfig codec_qos_config_media_tx_1; + CodecQosConfig codec_qos_config_gaming_tx; + CodecQosConfig codec_qos_config_gaming_tx_rx; + + CISConfig cis_config_1_media_tx = { + .cis_id = 0, + .max_sdu_m_to_s = 155, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + CISConfig cis_config_2_media_tx = { + .cis_id = 1, + .max_sdu_m_to_s = 155, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + CISConfig cis_config_1_gaming_tx = { + .cis_id = 0, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + CISConfig cis_config_2_gaming_tx = { + .cis_id = 1, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + CISConfig cis_config_1_gaming_tx_rx = { + .cis_id = 0, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 40, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + CISConfig cis_config_2_gaming_tx_rx = { + .cis_id = 1, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 40, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + ASCSConfig ascs_config = { + .cig_id = 1, + .cis_id = 0, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + codec_qos_config_media_tx_1.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_media_tx_1.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_media_tx_1.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_48000; + codec_qos_config_media_tx_1.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_media_tx_1.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_media_tx_1.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_media_tx_1.codec_config, 155); + codec_qos_config_media_tx_1.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_media_tx_1.qos_config.cis_configs.push_back(cis_config_1_media_tx); + codec_qos_config_media_tx_1.qos_config.cis_configs.push_back(cis_config_2_media_tx); + + codec_qos_config_media_tx_1.qos_config.ascs_configs.push_back(ascs_config); + codec_qos_config_gaming_tx.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_gaming_tx.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_gaming_tx.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_48000; + codec_qos_config_gaming_tx.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_gaming_tx.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_gaming_tx.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_gaming_tx.codec_config, 100); + codec_qos_config_gaming_tx.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_gaming_tx.qos_config.cis_configs.push_back(cis_config_1_gaming_tx); + codec_qos_config_gaming_tx.qos_config.cis_configs.push_back(cis_config_2_gaming_tx); + + codec_qos_config_gaming_tx.qos_config.ascs_configs.push_back(ascs_config); + codec_qos_config_gaming_tx_rx.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_gaming_tx_rx.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_gaming_tx_rx.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_16000; + codec_qos_config_gaming_tx_rx.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_gaming_tx_rx.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_gaming_tx_rx.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_gaming_tx_rx.codec_config, 40); + codec_qos_config_gaming_tx_rx.qos_config.cig_config = { + .cig_id = 2, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_gaming_tx_rx.qos_config.cis_configs.push_back(cis_config_1_gaming_tx_rx); + codec_qos_config_gaming_tx_rx.qos_config.cis_configs.push_back(cis_config_2_gaming_tx_rx); + ASCSConfig ascs_config_gaming = { + .cig_id = 2, + .cis_id = 0, + .bi_directional = true, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + codec_qos_config_gaming_tx_rx.qos_config.ascs_configs.push_back(ascs_config_gaming); + conn_info_media.stream_type.type = CONTENT_TYPE_MEDIA; + conn_info_media.stream_type.audio_context = CONTENT_TYPE_MEDIA; + conn_info_media.stream_type.direction = ASE_DIRECTION_SINK; + conn_info_media.codec_qos_config_pair.push_back(codec_qos_config_media_tx_1); + + conn_info_gaming.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_info_gaming.stream_type.audio_context = + CONTENT_TYPE_CONVERSATIONAL; + conn_info_gaming.stream_type.direction = ASE_DIRECTION_SRC; + conn_info_gaming.codec_qos_config_pair.push_back(codec_qos_config_gaming_tx_rx); + + std::vector dual_streams; + dual_streams.push_back(conn_info_media); + dual_streams.push_back(conn_info_gaming); + + StreamType type_media = { .type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + + StreamType type_voice = { .type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + + std::vector media_streams; + media_streams.push_back(type_media); + std::vector voice_streams; + voice_streams.push_back(type_voice); + + LOG(INFO) << __func__ << " going for generic test case"; + LOG(INFO) << __func__ << " going for connect with media tx and gaming rx"; + sUcastClientInterface->Connect( bap_bd_addr, true, dual_streams); + + //sUcastClientInterface->Connect( bap_bd_addr_2, true, streams_2); + + sleep(10); + + std::vector reconf_streams; + + StreamReconfig reconf_gaming_tx_info; + reconf_gaming_tx_info.stream_type.type = CONTENT_TYPE_MEDIA; // media + reconf_gaming_tx_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; // media + reconf_gaming_tx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_gaming_tx_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_gaming_tx_info.codec_qos_config_pair.push_back(codec_qos_config_gaming_tx); + + StreamReconfig reconf_gaming_tx_rx_info; + reconf_gaming_tx_rx_info.stream_type.type = CONTENT_TYPE_MEDIA; // media + reconf_gaming_tx_rx_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; // media + reconf_gaming_tx_rx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_gaming_tx_rx_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_gaming_tx_rx_info.codec_qos_config_pair.push_back(codec_qos_config_gaming_tx_rx); + + StreamReconfig reconf_media_info; + reconf_media_info.stream_type.type = CONTENT_TYPE_MEDIA; // media + reconf_media_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; // media + reconf_media_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_media_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_media_info.codec_qos_config_pair.push_back(codec_qos_config_media_tx_1); + + sleep(5); + LOG(INFO) << __func__ << " going for stream start 1 "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + + // switch to Gaming tx + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + + sleep(5); + // reconfig the first stream + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_gaming_tx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + + // switch to media tx + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + + // switch to Gaming tx & rx + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_gaming_tx_rx_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + sUcastClientInterface->Start( bap_bd_addr, voice_streams); + + // switch to Gaming tx (2nd time) (4th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + sUcastClientInterface->Stop( bap_bd_addr, voice_streams); + + sleep(5); + // reconfig the first stream + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_gaming_tx_info.reconf_type = StreamReconfigType::QOS_CONFIG; + reconf_streams.push_back(reconf_gaming_tx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + + + // switch to Gaming tx & rx (2nd time) (5th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_gaming_tx_rx_info.reconf_type = StreamReconfigType::QOS_CONFIG; + reconf_streams.push_back(reconf_gaming_tx_rx_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + sUcastClientInterface->Start( bap_bd_addr, voice_streams); + + // switch to media tx (6th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + sUcastClientInterface->Stop( bap_bd_addr, voice_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for stream disconnect 1 "; + sUcastClientInterface->Disconnect( bap_bd_addr, media_streams); + sUcastClientInterface->Disconnect( bap_bd_addr, voice_streams); + //LOG(INFO) << __func__ << "going for stream disconnect 2 "; + //sUcastClientInterface->Disconnect( bap_bd_addr_2, start_streams); + + } else if(!strcmp(bap_test, "tri_streams_media_tx_voice_tx_voice_rx")) { + StreamConnect conn_info_media; + StreamConnect conn_info_gaming; + StreamConnect conn_info_voice_tx; + StreamConnect conn_info_voice_rx; + + // reconfig information + CodecQosConfig codec_qos_config_media_tx_1; + CodecQosConfig codec_qos_config_gaming_tx; + CodecQosConfig codec_qos_config_gaming_tx_rx; + CodecQosConfig codec_qos_config_gaming_rx; + CodecQosConfig codec_qos_config_voice_tx_rx; + + CodecQosConfig codec_qos_config_media_tx_2; + CodecQosConfig codec_qos_config_gaming_tx_2; + CodecQosConfig codec_qos_config_gaming_tx_rx_2; + CodecQosConfig codec_qos_config_gaming_rx_2; + CodecQosConfig codec_qos_config_voice_tx_rx_2; + + CISConfig cis_config_1_media_tx = { + .cis_id = 0, + .max_sdu_m_to_s = 155, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + + CISConfig cis_config_2_media_tx = { + .cis_id = 1, + .max_sdu_m_to_s = 155, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + + CISConfig cis_config_1_gaming_tx = { + .cis_id = 0, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + + CISConfig cis_config_2_gaming_tx = { + .cis_id = 1, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + + CISConfig cis_config_1_gaming_tx_rx = { + .cis_id = 0, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 40, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + CISConfig cis_config_2_gaming_tx_rx = { + .cis_id = 1, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 40, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + + CISConfig cis_config_1_voice_tx_rx = { + .cis_id = 0, + .max_sdu_m_to_s = 40, + .max_sdu_s_to_m = 40, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + CISConfig cis_config_2_voice_tx_rx = { + .cis_id = 1, + .max_sdu_m_to_s = 40, + .max_sdu_s_to_m = 40, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + + ASCSConfig ascs_config = { + .cig_id = 1, + .cis_id = 0, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_gaming = { + .cig_id = 2, + .cis_id = 0, + .bi_directional = true, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_voice = { + .cig_id = 3, + .cis_id = 0, + .bi_directional = true, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_2 = { + .cig_id = 1, + .cis_id = 1, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_gaming_2 = { + .cig_id = 2, + .cis_id = 1, + .bi_directional = true, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_voice_2 = { + .cig_id = 3, + .cis_id = 1, + .bi_directional = true, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + codec_qos_config_media_tx_1.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_media_tx_1.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_media_tx_1.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_48000; + codec_qos_config_media_tx_1.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_media_tx_1.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_media_tx_1.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_media_tx_1.codec_config, 155); + codec_qos_config_media_tx_1.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_media_tx_1.qos_config.cis_configs.push_back(cis_config_1_media_tx); + codec_qos_config_media_tx_1.qos_config.cis_configs.push_back(cis_config_2_media_tx); + + codec_qos_config_media_tx_1.qos_config.ascs_configs.push_back(ascs_config); + + codec_qos_config_media_tx_2 = codec_qos_config_media_tx_1; + codec_qos_config_media_tx_2.qos_config.ascs_configs.clear(); + codec_qos_config_media_tx_2.qos_config.ascs_configs.push_back(ascs_config_2); + + codec_qos_config_gaming_tx.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_gaming_tx.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_gaming_tx.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_48000; + codec_qos_config_gaming_tx.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_gaming_tx.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_gaming_tx.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_gaming_tx.codec_config, 100); + codec_qos_config_gaming_tx.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_gaming_tx.qos_config.cis_configs.push_back(cis_config_1_gaming_tx); + codec_qos_config_gaming_tx.qos_config.cis_configs.push_back(cis_config_2_gaming_tx); + + codec_qos_config_gaming_tx.qos_config.ascs_configs.push_back(ascs_config); + + + codec_qos_config_gaming_tx_2 = codec_qos_config_gaming_tx; + codec_qos_config_gaming_tx_2.qos_config.ascs_configs.clear(); + codec_qos_config_gaming_tx_2.qos_config.ascs_configs.push_back(ascs_config_2); + + codec_qos_config_gaming_tx_rx.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_gaming_tx_rx.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_gaming_tx_rx.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_48000; + codec_qos_config_gaming_tx_rx.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_gaming_tx_rx.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_gaming_tx_rx.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_gaming_tx_rx.codec_config, 100); + codec_qos_config_gaming_tx_rx.qos_config.cig_config = { + .cig_id = 2, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_gaming_tx_rx.qos_config.cis_configs.push_back(cis_config_1_gaming_tx_rx); + codec_qos_config_gaming_tx_rx.qos_config.cis_configs.push_back(cis_config_2_gaming_tx_rx); + + codec_qos_config_gaming_tx_rx.qos_config.ascs_configs.push_back(ascs_config_gaming); + + codec_qos_config_gaming_tx_rx_2 = codec_qos_config_gaming_tx_rx; + codec_qos_config_gaming_tx_rx_2.qos_config.ascs_configs.clear(); + codec_qos_config_gaming_tx_rx_2.qos_config.ascs_configs.push_back(ascs_config_gaming_2); + + + codec_qos_config_gaming_rx.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_gaming_rx.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_gaming_rx.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_16000; + codec_qos_config_gaming_rx.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_gaming_rx.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_gaming_rx.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_gaming_rx.codec_config, 40); + codec_qos_config_gaming_rx.qos_config.cig_config = { + .cig_id = 2, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_gaming_rx.qos_config.cis_configs.push_back(cis_config_1_gaming_tx_rx); + codec_qos_config_gaming_rx.qos_config.cis_configs.push_back(cis_config_2_gaming_tx_rx); + + codec_qos_config_gaming_rx.qos_config.ascs_configs.push_back(ascs_config_gaming); + + codec_qos_config_gaming_rx_2 = codec_qos_config_gaming_rx; + codec_qos_config_gaming_rx_2.qos_config.ascs_configs.clear(); + codec_qos_config_gaming_rx_2.qos_config.ascs_configs.push_back(ascs_config_gaming_2); + + + // voice tx rx + codec_qos_config_voice_tx_rx.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_voice_tx_rx.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_voice_tx_rx.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_16000; + codec_qos_config_voice_tx_rx.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_voice_tx_rx.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_voice_tx_rx.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_voice_tx_rx.codec_config, 40); + codec_qos_config_voice_tx_rx.qos_config.cig_config = { + .cig_id = 3, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_voice_tx_rx.qos_config.cis_configs.push_back(cis_config_1_voice_tx_rx); + codec_qos_config_voice_tx_rx.qos_config.cis_configs.push_back(cis_config_2_voice_tx_rx); + + codec_qos_config_voice_tx_rx.qos_config.ascs_configs.push_back(ascs_config_voice); + + codec_qos_config_voice_tx_rx_2 = codec_qos_config_voice_tx_rx; + codec_qos_config_voice_tx_rx_2.qos_config.ascs_configs.clear(); + codec_qos_config_voice_tx_rx_2.qos_config.ascs_configs.push_back(ascs_config_voice_2); + + conn_info_media.stream_type.type = CONTENT_TYPE_MEDIA; + conn_info_media.stream_type.audio_context = CONTENT_TYPE_MEDIA; + conn_info_media.stream_type.direction = ASE_DIRECTION_SINK; + conn_info_media.codec_qos_config_pair.push_back(codec_qos_config_media_tx_1); + + conn_info_gaming.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_info_gaming.stream_type.audio_context = + CONTENT_TYPE_CONVERSATIONAL; + conn_info_gaming.stream_type.direction = ASE_DIRECTION_SRC; + conn_info_gaming.codec_qos_config_pair.push_back(codec_qos_config_gaming_tx_rx); + + conn_info_voice_tx.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_info_voice_tx.stream_type.audio_context = + CONTENT_TYPE_CONVERSATIONAL; + conn_info_voice_tx.stream_type.direction = ASE_DIRECTION_SINK; + conn_info_voice_tx.codec_qos_config_pair.push_back(codec_qos_config_voice_tx_rx); + + conn_info_voice_rx.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_info_voice_rx.stream_type.audio_context = + CONTENT_TYPE_CONVERSATIONAL; + conn_info_voice_rx.stream_type.direction = ASE_DIRECTION_SRC; + conn_info_voice_rx.codec_qos_config_pair.push_back(codec_qos_config_voice_tx_rx); + + std::vector tri_streams; + tri_streams.push_back(conn_info_media); + tri_streams.push_back(conn_info_voice_tx); + tri_streams.push_back(conn_info_voice_rx); + + conn_info_media.codec_qos_config_pair.clear(); + conn_info_media.codec_qos_config_pair.push_back(codec_qos_config_media_tx_2); + + conn_info_voice_tx.codec_qos_config_pair.clear(); + conn_info_voice_tx.codec_qos_config_pair.push_back(codec_qos_config_voice_tx_rx_2); + + conn_info_voice_rx.codec_qos_config_pair.clear(); + conn_info_voice_rx.codec_qos_config_pair.push_back(codec_qos_config_voice_tx_rx_2); + + std::vector tri_streams_2; + tri_streams_2.push_back(conn_info_media); + tri_streams_2.push_back(conn_info_voice_tx); + tri_streams_2.push_back(conn_info_voice_rx); + + StreamType type_media = { .type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + + StreamType type_voice_tx = { .type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + + StreamType type_voice_rx = { .type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + + + std::vector media_streams; + media_streams.push_back(type_media); + + std::vector gaming_tx_streams; + gaming_tx_streams.push_back(type_media); + + std::vector gaming_rx_streams; + gaming_rx_streams.push_back(type_voice_rx); + + std::vector voice_streams; + voice_streams.push_back(type_voice_tx); + voice_streams.push_back(type_voice_rx); + + std::vector all_three_streams; + all_three_streams.push_back(type_media); + all_three_streams.push_back(type_voice_tx); + all_three_streams.push_back(type_voice_rx); + +#if 0 + LOG(INFO) << __func__ << " going for generic test case"; + LOG(INFO) << __func__ << " going for connect with media tx and voice tx& rx"; + sUcastClientInterface->Connect( bap_bd_addr, true, tri_streams); + sUcastClientInterface->Connect( bap_bd_addr_2, true, tri_streams_2); + sleep(15); + + LOG(INFO) << __func__ << "going for stream disconnect all 3 streams "; + sUcastClientInterface->Disconnect( bap_bd_addr, all_three_streams); + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_three_streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for connect all 3 streams"; + sUcastClientInterface->Connect( bap_bd_addr, true, tri_streams); + sUcastClientInterface->Connect( bap_bd_addr_2, true, tri_streams_2); + + sleep(15); + + LOG(INFO) << __func__ << "going for stream disconnect all 3 streams "; + sUcastClientInterface->Disconnect( bap_bd_addr, all_three_streams); + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_three_streams); + + sleep(5); +#endif + + LOG(INFO) << __func__ << " going for connect all 3 streams"; + sUcastClientInterface->Connect( bap_bd_addr, true, tri_streams); + sUcastClientInterface->Connect( bap_bd_addr_2, true, tri_streams_2); + + std::vector reconf_streams; + std::vector reconf_streams_2; + + StreamReconfig reconf_gaming_tx_info; + reconf_gaming_tx_info.stream_type.type = CONTENT_TYPE_MEDIA; + reconf_gaming_tx_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; + reconf_gaming_tx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_gaming_tx_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_gaming_tx_info.codec_qos_config_pair. + push_back(codec_qos_config_gaming_tx); + + StreamReconfig reconf_gaming_tx_info_2; + reconf_gaming_tx_info_2.stream_type.type = CONTENT_TYPE_MEDIA; + reconf_gaming_tx_info_2.stream_type.audio_context = CONTENT_TYPE_MEDIA; + reconf_gaming_tx_info_2.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_gaming_tx_info_2.stream_type.direction = ASE_DIRECTION_SINK; + reconf_gaming_tx_info_2.codec_qos_config_pair. + push_back(codec_qos_config_gaming_tx_2); + + StreamReconfig reconf_gaming_tx_rx_info; + reconf_gaming_tx_rx_info.stream_type.type = CONTENT_TYPE_MEDIA; + reconf_gaming_tx_rx_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; + reconf_gaming_tx_rx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_gaming_tx_rx_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_gaming_tx_rx_info.codec_qos_config_pair. + push_back(codec_qos_config_gaming_tx_rx); + + StreamReconfig reconf_gaming_tx_rx_info_2; + reconf_gaming_tx_rx_info_2.stream_type.type = CONTENT_TYPE_MEDIA; + reconf_gaming_tx_rx_info_2.stream_type.audio_context = CONTENT_TYPE_MEDIA; + reconf_gaming_tx_rx_info_2.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_gaming_tx_rx_info_2.stream_type.direction = ASE_DIRECTION_SINK; + reconf_gaming_tx_rx_info_2.codec_qos_config_pair. + push_back(codec_qos_config_gaming_tx_rx_2); + + StreamReconfig reconf_gaming_rx_info; + reconf_gaming_rx_info.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + reconf_gaming_rx_info.stream_type.audio_context = + CONTENT_TYPE_CONVERSATIONAL; + reconf_gaming_rx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_gaming_rx_info.stream_type.direction = ASE_DIRECTION_SRC; + reconf_gaming_rx_info.codec_qos_config_pair. + push_back(codec_qos_config_gaming_rx); + + StreamReconfig reconf_gaming_rx_info_2; + reconf_gaming_rx_info_2.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + reconf_gaming_rx_info_2.stream_type.audio_context = + CONTENT_TYPE_CONVERSATIONAL; + reconf_gaming_rx_info_2.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_gaming_rx_info_2.stream_type.direction = ASE_DIRECTION_SRC; + reconf_gaming_rx_info_2.codec_qos_config_pair. + push_back(codec_qos_config_gaming_rx_2); + + StreamReconfig reconf_media_info; + reconf_media_info.stream_type.type = CONTENT_TYPE_MEDIA; + reconf_media_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; + reconf_media_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_media_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_media_info.codec_qos_config_pair.push_back(codec_qos_config_media_tx_1); + + StreamReconfig reconf_media_info_2; + reconf_media_info_2.stream_type.type = CONTENT_TYPE_MEDIA; + reconf_media_info_2.stream_type.audio_context = CONTENT_TYPE_MEDIA; + reconf_media_info_2.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_media_info_2.stream_type.direction = ASE_DIRECTION_SINK; + reconf_media_info_2.codec_qos_config_pair.push_back(codec_qos_config_media_tx_2); + + + StreamReconfig reconf_voice_tx_info; + reconf_voice_tx_info.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_tx_info.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_tx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_voice_tx_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_voice_tx_info.codec_qos_config_pair. + push_back(codec_qos_config_voice_tx_rx); + + StreamReconfig reconf_voice_tx_info_2; + reconf_voice_tx_info_2.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_tx_info_2.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_tx_info_2.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_voice_tx_info_2.stream_type.direction = ASE_DIRECTION_SINK; + reconf_voice_tx_info_2.codec_qos_config_pair. + push_back(codec_qos_config_voice_tx_rx_2); + + StreamReconfig reconf_voice_rx_info; + reconf_voice_rx_info.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_rx_info.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_rx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_voice_rx_info.stream_type.direction = ASE_DIRECTION_SRC; + reconf_voice_rx_info.codec_qos_config_pair. + push_back(codec_qos_config_voice_tx_rx); + + StreamReconfig reconf_voice_rx_info_2; + reconf_voice_rx_info_2.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_rx_info_2.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_rx_info_2.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_voice_rx_info_2.stream_type.direction = ASE_DIRECTION_SRC; + reconf_voice_rx_info_2.codec_qos_config_pair. + push_back(codec_qos_config_voice_tx_rx_2); + + sleep(15); + LOG(INFO) << __func__ << " going for stream start 1 "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + // switch to Gaming tx + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + // reconfig the first stream + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_gaming_tx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + reconf_streams_2.clear(); + reconf_streams_2.push_back(reconf_gaming_tx_info_2); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + // switch to media tx + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_streams_2.push_back(reconf_media_info_2); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + // switch to Gaming tx & rx + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_gaming_tx_rx_info); + reconf_streams.push_back(reconf_gaming_rx_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_streams_2.push_back(reconf_gaming_tx_rx_info_2); + reconf_streams_2.push_back(reconf_gaming_rx_info_2); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, gaming_tx_streams); + sUcastClientInterface->Start( bap_bd_addr, gaming_rx_streams); + sUcastClientInterface->Start( bap_bd_addr_2, gaming_tx_streams); + sUcastClientInterface->Start( bap_bd_addr_2, gaming_rx_streams); + + // switch to Gaming tx (2nd time) (4th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, gaming_tx_streams); + sUcastClientInterface->Stop( bap_bd_addr, gaming_rx_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, gaming_tx_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, gaming_rx_streams); + + sleep(5); + // reconfig the first stream + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_gaming_tx_info.reconf_type = StreamReconfigType::QOS_CONFIG; + reconf_streams.push_back(reconf_gaming_tx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_gaming_tx_info_2.reconf_type = StreamReconfigType::QOS_CONFIG; + reconf_streams_2.push_back(reconf_gaming_tx_info_2); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + // switch to Gaming tx & rx (2nd time) (5th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_gaming_tx_rx_info.reconf_type = StreamReconfigType::QOS_CONFIG; + reconf_streams.push_back(reconf_gaming_tx_rx_info); + reconf_streams.push_back(reconf_gaming_rx_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_gaming_tx_rx_info_2.reconf_type = StreamReconfigType::QOS_CONFIG; + reconf_streams_2.push_back(reconf_gaming_tx_rx_info_2); + reconf_streams_2.push_back(reconf_gaming_rx_info_2); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, gaming_tx_streams); + sUcastClientInterface->Start( bap_bd_addr, gaming_rx_streams); + sUcastClientInterface->Start( bap_bd_addr_2, gaming_tx_streams); + sUcastClientInterface->Start( bap_bd_addr_2, gaming_rx_streams); + + // switch to media tx (6th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, gaming_tx_streams); + sUcastClientInterface->Stop( bap_bd_addr, gaming_rx_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, gaming_tx_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, gaming_rx_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_streams_2.push_back(reconf_media_info_2); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + // switch to voice tx & Rx (7th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_voice_rx_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_streams_2.push_back(reconf_voice_rx_info_2); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, voice_streams); + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + // switch to gaming tx (8th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, voice_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, voice_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_gaming_tx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_streams.push_back(reconf_gaming_tx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_gaming_tx_info_2.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_streams_2.push_back(reconf_gaming_tx_info_2); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + + // switch to voice tx & Rx (9th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_voice_rx_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_streams_2.push_back(reconf_voice_rx_info_2); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, voice_streams); + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + // switch to Gaming tx & rx (10th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, voice_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, voice_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_gaming_tx_rx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_streams.push_back(reconf_gaming_tx_rx_info); + reconf_streams.push_back(reconf_gaming_rx_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_gaming_tx_rx_info_2.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_streams_2.push_back(reconf_gaming_tx_rx_info_2); + reconf_streams_2.push_back(reconf_gaming_rx_info_2); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, gaming_tx_streams); + sUcastClientInterface->Start( bap_bd_addr, gaming_rx_streams); + sUcastClientInterface->Start( bap_bd_addr_2, gaming_tx_streams); + sUcastClientInterface->Start( bap_bd_addr_2, gaming_rx_streams); + + + // switch to voice tx & Rx (11th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, gaming_tx_streams); + sUcastClientInterface->Stop( bap_bd_addr, gaming_rx_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, gaming_tx_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, gaming_rx_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_voice_rx_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_streams_2.push_back(reconf_voice_rx_info_2); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, voice_streams); + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + // switch to media tx (12th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, voice_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, voice_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_streams_2.push_back(reconf_media_info_2); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, media_streams); + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + // switch to voice tx & Rx (13th) + sleep(5); + LOG(INFO) << __func__ << " going for stream stop 1 "; + sUcastClientInterface->Stop( bap_bd_addr, media_streams); + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + // reconfig the first stream + reconf_streams.clear(); + reconf_streams.push_back(reconf_voice_rx_info); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + reconf_streams_2.clear(); + reconf_streams_2.push_back(reconf_voice_rx_info_2); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams_2); + + sleep(5); + LOG(INFO) << __func__ << " going for stream reconfgured .start "; + sUcastClientInterface->Start( bap_bd_addr, voice_streams); + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for stream disconnect 1 "; + sUcastClientInterface->Disconnect( bap_bd_addr, all_three_streams); + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_three_streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for connect all 3 streams"; + sUcastClientInterface->Connect( bap_bd_addr, true, tri_streams); + sUcastClientInterface->Connect( bap_bd_addr_2, true, tri_streams_2); + + sleep(15); + LOG(INFO) << __func__ << "going for stream disconnect all 3 streams "; + sUcastClientInterface->Disconnect( bap_bd_addr, all_three_streams); + + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_three_streams); + + sleep(5); + + } else if(!strcmp(bap_test, "stereo_headset_dual_cis")) { + StreamConnect conn_info_media; + StreamConnect conn_info_gaming; + StreamConnect conn_info_voice_tx; + StreamConnect conn_info_voice_rx; + StreamConnect conn_info_media_recording; + + // reconfig information + CodecQosConfig codec_qos_config_media_tx_1; + CodecQosConfig codec_qos_config_media_tx_2; + CodecQosConfig codec_qos_config_gaming_tx_1; + CodecQosConfig codec_qos_config_gaming_tx_2; + CodecQosConfig codec_qos_config_gaming_tx_rx; + CodecQosConfig codec_qos_config_voice_tx_rx; + CodecQosConfig codec_qos_config_voice_tx_rx_2; + CodecQosConfig codec_qos_config_media_rx_1; + CodecQosConfig codec_qos_config_media_rx_2; + + CISConfig cis_config_1_media_tx = { + .cis_id = 0, + .max_sdu_m_to_s = 155, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + CISConfig cis_config_2_media_tx = { + .cis_id = 1, + .max_sdu_m_to_s = 155, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + CISConfig cis_config_1_media_tx_2 = { + .cis_id = 0, + .max_sdu_m_to_s = 310, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + + CISConfig cis_config_1_gaming_tx = { + .cis_id = 0, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + CISConfig cis_config_2_gaming_tx = { + .cis_id = 1, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + CISConfig cis_config_1_gaming_tx_2 = { + .cis_id = 0, + .max_sdu_m_to_s = 200, + .max_sdu_s_to_m = 0, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + + CISConfig cis_config_1_gaming_tx_rx = { + .cis_id = 0, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 40, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + CISConfig cis_config_2_gaming_tx_rx = { + .cis_id = 1, + .max_sdu_m_to_s = 100, + .max_sdu_s_to_m = 40, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + CISConfig cis_config_1_voice_tx_rx = { + .cis_id = 0, + .max_sdu_m_to_s = 80, + .max_sdu_s_to_m = 80, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + CISConfig cis_config_2_voice_tx_rx = { + .cis_id = 1, + .max_sdu_m_to_s = 80, + .max_sdu_s_to_m = 80, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x02, + .rtn_s_to_m = 0x02 + }; + + CISConfig cis_config_1_media_rx = { + .cis_id = 0, + .max_sdu_m_to_s = 0, + .max_sdu_s_to_m = 100, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + + CISConfig cis_config_2_media_rx = { + .cis_id = 1, + .max_sdu_m_to_s = 0, + .max_sdu_s_to_m = 100, + .phy_m_to_s = 0x02, + .phy_s_to_m = 0x02, + .rtn_m_to_s = 0x05, + .rtn_s_to_m = 0x05 + }; + ASCSConfig ascs_config_1 = { + .cig_id = 1, + .cis_id = 0, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_2 = { + .cig_id = 1, + .cis_id = 1, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_gaming_1 = { + .cig_id = 1, + .cis_id = 0, + .bi_directional = true, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_gaming_2 = { + .cig_id = 1, + .cis_id = 1, + .bi_directional = true, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_voice_1 = { + .cig_id = 3, + .cis_id = 0, + .bi_directional = true, + .presentation_delay = {0x40, 0x9C, 0x00} + }; + + ASCSConfig ascs_config_voice_2 = { + .cig_id = 3, + .cis_id = 1, + .bi_directional = true, + .presentation_delay = {0x40, 0x9C, 0x00} + }; + + ASCSConfig ascs_config_media_rx_1 = { + .cig_id = 4, + .cis_id = 0, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + ASCSConfig ascs_config_media_rx_2 = { + .cig_id = 4, + .cis_id = 1, + .bi_directional = false, + .presentation_delay = {0x20, 0x4E, 0x00} + }; + + codec_qos_config_media_tx_1.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_media_tx_1.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_media_tx_1.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_48000; + codec_qos_config_media_tx_1.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_media_tx_1.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_media_tx_1.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_media_tx_1.codec_config, 155); + + codec_qos_config_media_tx_2 = codec_qos_config_media_tx_1; + UpdateOctsPerFrame(&codec_qos_config_media_tx_2.codec_config, 310); + + codec_qos_config_media_tx_1.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_media_tx_1.qos_config.cis_configs.push_back(cis_config_1_media_tx); + codec_qos_config_media_tx_1.qos_config.cis_configs.push_back(cis_config_2_media_tx); + + codec_qos_config_media_tx_1.qos_config.ascs_configs.push_back(ascs_config_1); + codec_qos_config_media_tx_1.qos_config.ascs_configs.push_back(ascs_config_2); + + codec_qos_config_media_tx_2.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 1, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_media_tx_2.qos_config.cis_configs.push_back(cis_config_1_media_tx_2); + + codec_qos_config_media_tx_2.qos_config.ascs_configs.push_back(ascs_config_1); + + codec_qos_config_gaming_tx_1.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_gaming_tx_1.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_gaming_tx_1.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_48000; + codec_qos_config_gaming_tx_1.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_gaming_tx_1.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_gaming_tx_1.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_gaming_tx_1.codec_config, 100); + + codec_qos_config_gaming_tx_2 = codec_qos_config_gaming_tx_1; + UpdateOctsPerFrame(&codec_qos_config_gaming_tx_2.codec_config, 200); + + codec_qos_config_gaming_tx_1.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_gaming_tx_1.qos_config.cis_configs.push_back(cis_config_1_gaming_tx); + codec_qos_config_gaming_tx_1.qos_config.cis_configs.push_back(cis_config_2_gaming_tx); + + codec_qos_config_gaming_tx_1.qos_config.ascs_configs.push_back(ascs_config_gaming_1); + codec_qos_config_gaming_tx_1.qos_config.ascs_configs.push_back(ascs_config_gaming_2); + + codec_qos_config_gaming_tx_2.qos_config.cig_config = { + .cig_id = 1, + .cis_count = 1, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_gaming_tx_2.qos_config.cis_configs.push_back(cis_config_1_gaming_tx_2); + + codec_qos_config_gaming_tx_2.qos_config.ascs_configs.push_back(ascs_config_gaming_1); + + codec_qos_config_gaming_tx_rx.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_gaming_tx_rx.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_gaming_tx_rx.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_16000; + codec_qos_config_gaming_tx_rx.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_gaming_tx_rx.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_gaming_tx_rx.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_gaming_tx_rx.codec_config, 40); + codec_qos_config_gaming_tx_rx.qos_config.cig_config = { + .cig_id = 2, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_gaming_tx_rx.qos_config.cis_configs.push_back(cis_config_1_gaming_tx_rx); + codec_qos_config_gaming_tx_rx.qos_config.cis_configs.push_back(cis_config_2_gaming_tx_rx); + + codec_qos_config_gaming_tx_rx.qos_config.ascs_configs.push_back(ascs_config_gaming_1); + + // voice tx rx + codec_qos_config_voice_tx_rx.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_voice_tx_rx.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_voice_tx_rx.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_32000; + codec_qos_config_voice_tx_rx.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_voice_tx_rx.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_voice_tx_rx.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_voice_tx_rx.codec_config, 80); + codec_qos_config_voice_tx_rx.qos_config.cig_config = { + .cig_id = 3, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_voice_tx_rx.qos_config.cis_configs.push_back(cis_config_1_voice_tx_rx); + codec_qos_config_voice_tx_rx.qos_config.cis_configs.push_back(cis_config_2_voice_tx_rx); + + codec_qos_config_voice_tx_rx.qos_config.ascs_configs.push_back(ascs_config_voice_1); + codec_qos_config_voice_tx_rx.qos_config.ascs_configs.push_back(ascs_config_voice_2); + + + codec_qos_config_media_rx_1.codec_config.codec_type = + CodecIndex::CODEC_INDEX_SOURCE_LC3; + codec_qos_config_media_rx_1.codec_config.codec_priority = + CodecPriority::CODEC_PRIORITY_DEFAULT; + codec_qos_config_media_rx_1.codec_config.sample_rate = + CodecSampleRate::CODEC_SAMPLE_RATE_48000; + codec_qos_config_media_rx_1.codec_config.channel_mode = + CodecChannelMode::CODEC_CHANNEL_MODE_MONO; + //Frame_Duration + UpdateFrameDuration(&codec_qos_config_media_rx_1.codec_config, + static_cast(CodecFrameDuration::FRAME_DUR_10)); + // LC3_Blocks_Per_SDU + UpdateLc3BlocksPerSdu(&codec_qos_config_media_rx_1.codec_config, 1); + UpdateOctsPerFrame(&codec_qos_config_media_rx_1.codec_config, 100); + + codec_qos_config_media_rx_1.qos_config.cig_config = { + .cig_id = 4, + .cis_count = 2, + .packing = 0x01, // interleaved + .framing = 0x00, // unframed + .max_tport_latency_m_to_s = 0x000a, + .max_tport_latency_s_to_m = 0x000a, + .sdu_interval_m_to_s = {0x10, 0x27, 0x00}, + .sdu_interval_s_to_m = {0x10, 0x27, 0x00} + }; + + codec_qos_config_media_rx_1.qos_config.cis_configs.push_back(cis_config_1_media_rx); + codec_qos_config_media_rx_1.qos_config.cis_configs.push_back(cis_config_2_media_rx); + + codec_qos_config_media_rx_1.qos_config.ascs_configs.push_back(ascs_config_media_rx_1); + codec_qos_config_media_rx_1.qos_config.ascs_configs.push_back(ascs_config_media_rx_2); + + conn_info_media.stream_type.type = CONTENT_TYPE_MEDIA; + conn_info_media.stream_type.audio_context = CONTENT_TYPE_MEDIA; + conn_info_media.stream_type.direction = ASE_DIRECTION_SINK; + conn_info_media.codec_qos_config_pair.push_back(codec_qos_config_media_tx_1); + conn_info_media.codec_qos_config_pair.push_back(codec_qos_config_media_tx_2); + + conn_info_gaming.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_info_gaming.stream_type.audio_context = CONTENT_TYPE_GAME; + conn_info_gaming.stream_type.direction = ASE_DIRECTION_SRC; + conn_info_gaming.codec_qos_config_pair.push_back(codec_qos_config_gaming_tx_1); + + conn_info_voice_tx.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_info_voice_tx.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + conn_info_voice_tx.stream_type.direction = ASE_DIRECTION_SINK; + conn_info_voice_tx.codec_qos_config_pair.push_back(codec_qos_config_voice_tx_rx); + + conn_info_voice_rx.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + conn_info_voice_rx.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + conn_info_voice_rx.stream_type.direction = ASE_DIRECTION_SRC; + conn_info_voice_rx.codec_qos_config_pair.push_back(codec_qos_config_voice_tx_rx); + + conn_info_media_recording.stream_type.type = CONTENT_TYPE_MEDIA; + conn_info_media_recording.stream_type.audio_context = CONTENT_TYPE_LIVE; + conn_info_media_recording.stream_type.direction = ASE_DIRECTION_SRC; + conn_info_media_recording.codec_qos_config_pair.push_back(codec_qos_config_media_rx_1); + + std::vector four_streams; + four_streams.push_back(conn_info_media); + four_streams.push_back(conn_info_media_recording); + four_streams.push_back(conn_info_voice_tx); + four_streams.push_back(conn_info_voice_rx); + + std::vector voice_conn_streams; + voice_conn_streams.push_back(conn_info_voice_tx); + voice_conn_streams.push_back(conn_info_voice_rx); + + std::vector media_conn_streams; + media_conn_streams.push_back(conn_info_media); + + StreamType type_media = { .type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_MEDIA, + .direction = ASE_DIRECTION_SINK + }; + + StreamType type_gaming_tx = { .type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_GAME, + .direction = ASE_DIRECTION_SINK + }; + + StreamType type_voice_tx = { .type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SINK + }; + + StreamType type_voice_rx = { .type = CONTENT_TYPE_CONVERSATIONAL, + .audio_context = CONTENT_TYPE_CONVERSATIONAL, + .direction = ASE_DIRECTION_SRC + }; + + StreamType type_media_rx = { .type = CONTENT_TYPE_MEDIA, + .audio_context = CONTENT_TYPE_LIVE, + .direction = ASE_DIRECTION_SRC + }; + + std::vector media_streams; + media_streams.push_back(type_media); + + std::vector gaming_tx_streams; + gaming_tx_streams.push_back(type_gaming_tx); + + std::vector media_rx_streams; + media_rx_streams.push_back(type_media_rx); + + std::vector voice_streams; + voice_streams.push_back(type_voice_tx); + voice_streams.push_back(type_voice_rx); + + std::vector all_four_streams; + all_four_streams.push_back(type_media); + all_four_streams.push_back(type_voice_tx); + all_four_streams.push_back(type_voice_rx); + all_four_streams.push_back(type_media_rx); + + std::vector reconf_streams; + + StreamReconfig reconf_media_info; + reconf_media_info.stream_type.type = CONTENT_TYPE_MEDIA; + reconf_media_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; + reconf_media_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_media_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_media_info.codec_qos_config_pair.push_back(codec_qos_config_media_tx_1); + reconf_media_info.codec_qos_config_pair.push_back(codec_qos_config_media_tx_2); + + StreamReconfig reconf_gaming_tx_info; + reconf_gaming_tx_info.stream_type.type = CONTENT_TYPE_MEDIA; + reconf_gaming_tx_info.stream_type.audio_context = CONTENT_TYPE_MEDIA; + reconf_gaming_tx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_gaming_tx_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_gaming_tx_info.codec_qos_config_pair.push_back(codec_qos_config_gaming_tx_1); + reconf_gaming_tx_info.codec_qos_config_pair.push_back(codec_qos_config_gaming_tx_2); + + StreamReconfig reconf_voice_tx_info; + reconf_voice_tx_info.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_tx_info.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_tx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_voice_tx_info.stream_type.direction = ASE_DIRECTION_SINK; + reconf_voice_tx_info.codec_qos_config_pair. + push_back(codec_qos_config_voice_tx_rx); + + StreamReconfig reconf_voice_rx_info; + reconf_voice_rx_info.stream_type.type = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_rx_info.stream_type.audio_context = CONTENT_TYPE_CONVERSATIONAL; + reconf_voice_rx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_voice_rx_info.stream_type.direction = ASE_DIRECTION_SRC; + reconf_voice_rx_info.codec_qos_config_pair. + push_back(codec_qos_config_voice_tx_rx); + + StreamReconfig reconf_media_rx_info; + reconf_media_rx_info.stream_type.type = CONTENT_TYPE_MEDIA; + reconf_media_rx_info.stream_type.audio_context = CONTENT_TYPE_LIVE; + reconf_media_rx_info.reconf_type = StreamReconfigType::CODEC_CONFIG; + reconf_media_rx_info.stream_type.direction = ASE_DIRECTION_SRC; + reconf_media_rx_info.codec_qos_config_pair.push_back(codec_qos_config_media_rx_1); + + LOG(INFO) << __func__ << " going for generic test case"; + LOG(INFO) << __func__ << " going for connect with voice tx and rx"; + sUcastClientInterface->Connect( bap_bd_addr_2, true, four_streams); + + sleep(15); + +#if 0 + // Recording to music switch (12) + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for music stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + + LOG(INFO) << __func__ << "going for music stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + //sleep(5); + LOG(INFO) << __func__ << "going for stream disconnect voice streams "; + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_four_streams); + + sleep(10); + + LOG(INFO) << __func__ << " going for connect with voice tx and rx"; + sUcastClientInterface->Connect( bap_bd_addr_2, true, four_streams); + + sleep(15); + + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for music stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + //sleep(5); + LOG(INFO) << __func__ << "going for stream disconnect voice streams "; + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_four_streams); + + sleep(10); + + + LOG(INFO) << __func__ << " going for connect with voice tx and rx"; + sUcastClientInterface->Connect( bap_bd_addr_2, true, four_streams); + + sleep(15); + reconf_streams.clear(); + reconf_streams.push_back(reconf_voice_tx_info); + reconf_streams.push_back(reconf_voice_rx_info); + LOG(INFO) << __func__ << " going for voice stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for voice stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + + // Call Tx Rx to Gaming switch (4) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, voice_streams); + sleep(5); + + LOG(INFO) << __func__ << " going for voice stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + sleep(5); + + LOG(INFO) << __func__ << "going for voice stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + + // Call Tx Rx to Gaming switch (4) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, voice_streams); + + //sleep(5); + LOG(INFO) << __func__ << "going for stream disconnect voice streams "; + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_four_streams); + + sleep(10); + + + LOG(INFO) << __func__ << " going for connect with voice tx and rx"; + sUcastClientInterface->Connect( bap_bd_addr_2, true, four_streams); + sleep(15); + + reconf_streams.clear(); + reconf_streams.push_back(reconf_voice_tx_info); + reconf_streams.push_back(reconf_voice_rx_info); + LOG(INFO) << __func__ << " going for voice stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for voice stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + //sleep(5); + LOG(INFO) << __func__ << "going for stream disconnect voice streams "; + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_four_streams); + + sleep(10); + + sUcastClientInterface->Connect( bap_bd_addr_2, true, four_streams); + + sleep(1); + + LOG(INFO) << __func__ << "going for stream disconnect voice streams "; + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_four_streams); + + sleep(10); + + sUcastClientInterface->Connect( bap_bd_addr_2, true, four_streams); + + sleep(15); + +#endif +#if 1 + reconf_streams.clear(); + reconf_streams.push_back(reconf_voice_tx_info); + reconf_streams.push_back(reconf_voice_rx_info); + LOG(INFO) << __func__ << " going for voice stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + //usleep(200000); + + sleep(5); + + LOG(INFO) << __func__ << "going for voice stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + + // Call Tx Rx to Gaming switch (4) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, voice_streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for voice stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + + LOG(INFO) << __func__ << "going for voice stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + + // Call Tx Rx to Gaming switch (4) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, voice_streams); + + sleep(5); + + LOG(INFO) << __func__ << "going for voice stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + sleep(5); + //usleep(200000); + LOG(INFO) << __func__ << "going for stream disconnect voice streams "; + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_four_streams); + + sleep(10); + + sUcastClientInterface->Connect( bap_bd_addr_2, true, four_streams); +#endif + + sleep(15); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + + // Music streaming + LOG(INFO) << __func__ << "going for media stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + // music to gaming Tx switch (1) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_gaming_tx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for gaming stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, gaming_tx_streams); + + // Gaming Tx to music switch (2) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, gaming_tx_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for gaming stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + // music to Call Tx Rx switch (3) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + reconf_streams.clear(); + reconf_streams.push_back(reconf_voice_tx_info); + reconf_streams.push_back(reconf_voice_rx_info); + LOG(INFO) << __func__ << " going for voice stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for voice stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + + // Call Tx Rx to Gaming switch (4) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, voice_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_gaming_tx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for gaming stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, gaming_tx_streams); + + + // Gaming to Cal Audio switch (5) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, gaming_tx_streams); + + sleep(5); + reconf_streams.clear(); + reconf_streams.push_back(reconf_voice_tx_info); + reconf_streams.push_back(reconf_voice_rx_info); + LOG(INFO) << __func__ << " going for voice stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for voice stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + + + // Cal Audio to music switch (6) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, voice_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for music stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + + // music to Recording switch (7) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_rx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for recording stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, media_rx_streams); + + + // Recording to Gaming switch (8) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, media_rx_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_gaming_tx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for gaming stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, gaming_tx_streams); + + + // Gaming to Recording switch (9) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, gaming_tx_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_rx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for recording stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, media_rx_streams); + + + // Recording to Call Audio switch (10) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, media_rx_streams); + + sleep(5); + reconf_streams.clear(); + reconf_streams.push_back(reconf_voice_tx_info); + reconf_streams.push_back(reconf_voice_rx_info); + LOG(INFO) << __func__ << " going for voice stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for voice stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, voice_streams); + + + // Call Audio to Recording switch (11) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, voice_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_rx_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for recording stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, media_rx_streams); + + + // Recording to music switch (12) + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, media_rx_streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream codec reconfig"; + reconf_streams.clear(); + reconf_streams.push_back(reconf_media_info); + sUcastClientInterface->Reconfigure( bap_bd_addr_2, reconf_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for music stream start "; + sUcastClientInterface->Start( bap_bd_addr_2, media_streams); + + sleep(5); + LOG(INFO) << __func__ << "going for media stream stop "; + sUcastClientInterface->Stop( bap_bd_addr_2, media_streams); + + //sleep(5); + LOG(INFO) << __func__ << "going for stream disconnect voice streams "; + sUcastClientInterface->Disconnect( bap_bd_addr_2, all_four_streams); + + sleep(10); + + } else if(!strcmp(bap_test, "connect")) { + LOG(INFO) << __func__ << " going for connect test case"; + + for ( uint8_t i = 0; i < 5; i++) { + LOG(INFO) << __func__ << " iteration " << loghex(i); + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + usleep( i* 1000 * 1000); + LOG(INFO) << __func__ << " going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + } + } else if(!strcmp(bap_test, "release_in_streaming")) { + LOG(INFO) << __func__ << " going for release_in_streaming test case"; + + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(10); + + LOG(INFO) << __func__ << " going for stream start"; + sUcastClientInterface->Start( bap_bd_addr, start_streams); + + sleep(10); + + LOG(INFO) << __func__ << "going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + + + } else if(!strcmp(bap_test, "release_in_enabling")) { + LOG(INFO) << __func__ << " going for release_in_enabling test case"; + + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(10); + + LOG(INFO) << __func__ << " going for stream start"; + sUcastClientInterface->Start( bap_bd_addr, start_streams); + + usleep(150 *1000); + + LOG(INFO) << __func__ << "going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + + + } else if(!strcmp(bap_test, "release_in_disabling")) { + LOG(INFO) << __func__ << " going for release_in_disabling test case"; + + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(10); + + LOG(INFO) << __func__ << " going for stream start"; + sUcastClientInterface->Start( bap_bd_addr, start_streams); + + sleep(10); + + LOG(INFO) << __func__ << " going for stream stop"; + sUcastClientInterface->Stop( bap_bd_addr, start_streams); + + usleep(100 * 1000); + LOG(INFO) << __func__ << "going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + + } else if(!strcmp(bap_test, "disable_in_enabling")) { + LOG(INFO) << __func__ << " going for disable_in_enabling test case"; + + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(10); + + LOG(INFO) << __func__ << " going for stream start"; + sUcastClientInterface->Start( bap_bd_addr, start_streams); + + usleep(200 * 1000); + LOG(INFO) << __func__ << "going for stream stop"; + sUcastClientInterface->Stop( bap_bd_addr, start_streams); + + } else if(!strcmp(bap_test, "disable_in_streaming")) { + LOG(INFO) << __func__ << " going for disable_in_streaming test case"; + + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(10); + + LOG(INFO) << __func__ << " going for stream start"; + sUcastClientInterface->Start( bap_bd_addr, start_streams); + + sleep(5); + + LOG(INFO) << __func__ << "going for stream stop"; + sUcastClientInterface->Stop( bap_bd_addr, start_streams); + + } else if(!strcmp(bap_test, "release_in_codec_configured")) { + LOG(INFO) << __func__ << " going for release_in_codec_configured test case"; + + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + usleep(2600 * 1000); + LOG(INFO) << __func__ << "going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + + } else if(!strcmp(bap_test, "release_in_qos_configured")) { + LOG(INFO) << __func__ << " going for release_in_qos_configured test case"; + + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(10); + + LOG(INFO) << __func__ << "going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + + } else if(!strcmp(bap_test, "enable_in_qos_configured")) { + LOG(INFO) << __func__ << " going for enable_in_qos_configured test case"; + + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(10); + + LOG(INFO) << __func__ << "going for stream start"; + sUcastClientInterface->Start( bap_bd_addr, start_streams); + + } else if(!strcmp(bap_test, "start")) { + LOG(INFO) << __func__ << " going for start test case"; + + LOG(INFO) << __func__ << " going for stop while starting test case"; + + + for ( uint8_t i = 0; i < 5; i++) { + LOG(INFO) << __func__ << " iteration " << loghex(i); + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(5); + LOG(INFO) << __func__ << " going for stream start"; + sUcastClientInterface->Start( bap_bd_addr, start_streams); + usleep( i* 200 * 1000); + LOG(INFO) << __func__ << " going for stream stop"; + sUcastClientInterface->Stop( bap_bd_addr, start_streams); + sleep(5); + + LOG(INFO) << __func__ << " going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + } + + LOG(INFO) << __func__ << " going for disconnect while starting test case"; + + for ( uint8_t i = 0; i < 5; i++) { + LOG(INFO) << __func__ << " iteration " << loghex(i); + LOG(INFO) << __func__ << " going for connect"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for stream start"; + sUcastClientInterface->Start( bap_bd_addr, start_streams); + usleep( i* 200 * 1000); + LOG(INFO) << __func__ << " going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + sleep(5); + } + + } else if(!strcmp(bap_test, "stop")) { + LOG(INFO) << __func__ << " going for stop test case"; + LOG(INFO) << __func__ << " going for connect"; + + for ( uint8_t i = 0; i < 5; i++) { + LOG(INFO) << __func__ << " iteration " << loghex(i); + LOG(INFO) << __func__ << " going for disconnect while stopping"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for stream start"; + sUcastClientInterface->Start( bap_bd_addr, start_streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for stream stop"; + sUcastClientInterface->Stop( bap_bd_addr, start_streams); + usleep( i* 200 * 1000); + + LOG(INFO) << __func__ << " going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + } + + } else if(!strcmp(bap_test, "stop")) { + LOG(INFO) << __func__ << " going for stop test case"; + LOG(INFO) << __func__ << " going for connect"; + + for ( uint8_t i = 0; i < 5; i++) { + LOG(INFO) << __func__ << " iteration " << loghex(i); + LOG(INFO) << __func__ << " going for disconnect while stopping"; + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for stream start"; + sUcastClientInterface->Start( bap_bd_addr, start_streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for stream stop"; + sUcastClientInterface->Stop( bap_bd_addr, start_streams); + usleep( i* 200 * 1000); + + LOG(INFO) << __func__ << " going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + } + } else if(!strcmp(bap_test, "reconfigure")) { + LOG(INFO) << __func__ << " going for reconfigure test case"; + LOG(INFO) << __func__ << " going for connect"; + + sUcastClientInterface->Connect( bap_bd_addr, true, streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for stream codec reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for stream qos reconfig"; + sUcastClientInterface->Reconfigure( bap_bd_addr, reconf_qos_streams); + + sleep(5); + + LOG(INFO) << __func__ << " going for stream disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr, start_streams); + + } + + LOG(INFO) << __func__ << " Test completed"; +#if 0 + sleep(2); + LOG(INFO) << __func__ << "going for connect"; + sUcastClientInterface->Connect( bap_bd_addr); + sleep(7); + LOG(INFO) << __func__ << "going for discovery"; + sUcastClientInterface->StartDiscovery( bap_bd_addr); + sleep(2); + LOG(INFO) << __func__ << "going for getAudiocontexts"; + sUcastClientInterface->GetAvailableAudioContexts( bap_bd_addr); + sleep(2); + LOG(INFO) << __func__ << "going for disconnect"; + sUcastClientInterface->Disconnect( bap_bd_addr); + sleep(1); + sUcastClientInterface->Cleanup(); + sleep(1); + sUcastClientInterface->Init(&sUcastClientCallbacks); + sleep(1); + LOG(INFO) << __func__ << "going for connect 2 "; + sUcastClientInterface->Connect( bap_bd_addr); + sleep(7); + LOG(INFO) << __func__ << "going for discovery 2"; + sUcastClientInterface->StartDiscovery( bap_bd_addr); + sleep(2); + LOG(INFO) << __func__ << "going for getAudiocontexts 2"; + sUcastClientInterface->GetAvailableAudioContexts( bap_bd_addr); + sleep(2); + LOG(INFO) << __func__ << "going for disconnect 2"; + sUcastClientInterface->Disconnect( bap_bd_addr); + sleep(1); + sUcastClientInterface->Cleanup(); +#endif +} + +bool test_bap_uclient () { + RawAddress::FromString("00:02:5b:00:ff:00", bap_bd_addr); + test_thread = thread_new("test_bap_uclient"); + LOG(INFO) << __func__ << "going for test setup"; + thread_post(test_thread, event_test_bap_uclient, NULL); + return true; +} + +// PACS related test code +using bluetooth::bap::pacs::PacsClientInterface; +using bluetooth::bap::pacs::PacsClientCallbacks; + +static PacsClientInterface* sPacsClientInterface = nullptr; +static uint16_t pacs_client_id = 0; +static RawAddress pac_bd_addr; + +class PacsClientCallbacksImpl : public PacsClientCallbacks { + public: + ~PacsClientCallbacksImpl() = default; + void OnInitialized(int status, + int client_id) override { + LOG(WARNING) << __func__ << client_id; + pacs_client_id = client_id; + } + void OnConnectionState(const RawAddress& bd_addr, + ConnectionState state) override { + LOG(WARNING) << __func__; + if(state == ConnectionState::CONNECTED) { + LOG(WARNING) << __func__ << "connected"; + } else if(state == ConnectionState::DISCONNECTED) { + LOG(WARNING) << __func__ << "Disconnected"; + } + + } + void OnAudioContextAvailable(const RawAddress& bd_addr, + uint32_t available_contexts) override { + LOG(INFO) << __func__; + } + void OnSearchComplete(int status, const RawAddress& address, + std::vector sink_pac_records, + std::vector src_pac_records, + uint32_t sink_locations, + uint32_t src_locations, + uint32_t available_contexts, + uint32_t supported_contexts) override { + LOG(WARNING) << __func__; + } +}; + +static PacsClientCallbacksImpl sPacsClientCallbacks; + +static void event_test_pacs(UNUSED_ATTR void* context) { + sPacsClientInterface = btif_pacs_client_get_interface(); + sPacsClientInterface->Init(&sPacsClientCallbacks); + sleep(1); + LOG(INFO) << __func__ << "going for connect"; + sPacsClientInterface->Connect(pacs_client_id, pac_bd_addr); + sleep(7); + LOG(INFO) << __func__ << "going for discovery"; + sPacsClientInterface->StartDiscovery(pacs_client_id, pac_bd_addr); + sleep(2); + LOG(INFO) << __func__ << "going for getAudiocontexts"; + sPacsClientInterface->GetAvailableAudioContexts(pacs_client_id, pac_bd_addr); + sleep(2); + LOG(INFO) << __func__ << "going for disconnect"; + sPacsClientInterface->Disconnect(pacs_client_id, pac_bd_addr); + + sleep(2); + LOG(INFO) << __func__ << "going for connect"; + sPacsClientInterface->Connect(pacs_client_id, pac_bd_addr); + sleep(7); + LOG(INFO) << __func__ << "going for discovery"; + sPacsClientInterface->StartDiscovery(pacs_client_id, pac_bd_addr); + sleep(2); + LOG(INFO) << __func__ << "going for getAudiocontexts"; + sPacsClientInterface->GetAvailableAudioContexts(pacs_client_id, pac_bd_addr); + sleep(2); + LOG(INFO) << __func__ << "going for disconnect"; + sPacsClientInterface->Disconnect(pacs_client_id, pac_bd_addr); + + sleep(1); + sPacsClientInterface->Cleanup(pacs_client_id); + + sleep(1); + + sPacsClientInterface->Init(&sPacsClientCallbacks); + sleep(1); + LOG(INFO) << __func__ << "going for connect 2 "; + sPacsClientInterface->Connect(pacs_client_id, pac_bd_addr); + sleep(7); + LOG(INFO) << __func__ << "going for discovery 2"; + sPacsClientInterface->StartDiscovery(pacs_client_id, pac_bd_addr); + sleep(2); + LOG(INFO) << __func__ << "going for getAudiocontexts 2"; + sPacsClientInterface->GetAvailableAudioContexts(pacs_client_id, pac_bd_addr); + sleep(2); + LOG(INFO) << __func__ << "going for disconnect 2"; + sPacsClientInterface->Disconnect(pacs_client_id, pac_bd_addr); + + sleep(1); + sPacsClientInterface->Cleanup(pacs_client_id); + +} + +bool test_pacs (RawAddress& bd_addr) { + + test_thread = thread_new("test_pacs"); + pac_bd_addr = bd_addr; + thread_post(test_thread, event_test_pacs, NULL); + return true; +} diff --git a/le_audio/system/bt/btif/src/btif_cc.cc b/le_audio/system/bt/btif/src/btif_cc.cc new file mode 100644 index 00000000000..59c355da1b5 --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_cc.cc @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * 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. + */ + +/* CC Interface */ +#define LOG_TAG "bt_btif_cc" + +#include "bt_target.h" +#include "bta_closure_api.h" +#include "btif_common.h" +#include "btif_storage.h" +#include "bta_cc_api.h" + +#include +#include +#include +#include +#include +#include +#include + +using base::Bind; +using base::Unretained; +using base::Owned; +using bluetooth::Uuid; +using std::vector; + +using base::Unretained; +using bluetooth::call_control::CallControllerInterface; +using bluetooth::call_control::CallControllerCallbacks; + +namespace { +class CallControllerInterfaceImpl; +std::unique_ptr CallControllerInstance; + +class CallControllerInterfaceImpl + : public CallControllerInterface, public CallControllerCallbacks { + ~CallControllerInterfaceImpl() = default; + + bt_status_t Init(CallControllerCallbacks* callbacks, Uuid uuid, int max_ccs_clients, + bool inband_ringing_enabled) override { + + LOG(INFO) << __func__ ; + this->callbacks = callbacks; + do_in_bta_thread(FROM_HERE,Bind(&CallController::Initialize, + this, uuid,max_ccs_clients, inband_ringing_enabled)); + return BT_STATUS_SUCCESS; + } + + bt_status_t UpdateBearerName(uint8_t* operator_str) { + + LOG(INFO) << __func__ << ": bearer name " << operator_str; + uint8_t* bName = (uint8_t*)malloc(sizeof(uint8_t)*strlen((char*)operator_str)+1); + if (bName != NULL) { + memcpy(bName, operator_str, strlen((char*)operator_str)+1); + do_in_bta_thread(FROM_HERE,Bind(&CallController::BearerInfoName, + Unretained(CallController::Get()), bName)); + free(bName); + } + return BT_STATUS_SUCCESS; + } + + void Cleanup() { + LOG(INFO) << __func__ ; + do_in_bta_thread(FROM_HERE,Bind(&CallController::CleanUp)); + } + +bt_status_t UpdateBearerTechnology(int bearer_tech) { + + LOG(INFO) << __func__ << ": " << bearer_tech; + do_in_bta_thread(FROM_HERE,Bind(&CallController::UpdateBearerTechnology, + Unretained(CallController::Get()), bearer_tech)); + return BT_STATUS_SUCCESS; +} + +bt_status_t UpdateSupportedBearerList(uint8_t* supportedbearer_list) { + + LOG(INFO) << __func__ << ": " << supportedbearer_list; + uint8_t* sList = (uint8_t*)malloc(sizeof(uint8_t)*strlen((char*)supportedbearer_list)+1); + if (sList != NULL) { + memcpy(sList, supportedbearer_list, strlen((char*)supportedbearer_list)+1); + do_in_bta_thread(FROM_HERE,Bind(&CallController::UpdateSupportedBearerList, + Unretained(CallController::Get()), sList)); + free(sList); + } + return BT_STATUS_SUCCESS; + +} +bt_status_t UpdateStatusFlags(uint8_t status_flag) { + LOG(INFO) << __func__ << ": " << status_flag; + do_in_bta_thread(FROM_HERE,Bind(&CallController::UpdateStatusFlags, + Unretained(CallController::Get()), status_flag)); + return BT_STATUS_SUCCESS; +} + +bt_status_t UpdateSignalStatus(int signal) { + + LOG(INFO) << __func__ << ": " << signal; + do_in_bta_thread(FROM_HERE,Bind(&CallController::UpdateBearerSignalStrength, + Unretained(CallController::Get()), signal)); + return BT_STATUS_SUCCESS; + +} + +bt_status_t CallControlOptionalOpSupported(int feature) { + + LOG(INFO) << __func__ << ": " << feature; + do_in_bta_thread(FROM_HERE,Bind(&CallController::CallControlOptionalOpSupported, + Unretained(CallController::Get()), feature)); + return BT_STATUS_SUCCESS; +} + +bt_status_t CallState(int len, std::vector call_state_list) { + + LOG(INFO) << __func__ << ": "; + do_in_bta_thread(FROM_HERE,Bind(&CallController::CallState, + Unretained(CallController::Get()), len, std::move(call_state_list))); + return BT_STATUS_SUCCESS; + +} + +void UpdateIncomingCall(int index, uint8_t* Uri) { + LOG(INFO) << __func__ << ": " < +#include +#include +#include +#include +#include +#include +#include +#include +#include "device/include/controller.h" +#include "bta_csip_api.h" + +#include "btif_api.h" +#include "btif_common.h" +#include "btif_util.h" + +#include + +using base::Bind; +using bluetooth::Uuid; + +static btcsip_callbacks_t* bt_csip_callbacks = NULL; + +void btif_new_set_found_cb(tBTA_CSIP_NEW_SET_FOUND params) { + HAL_CBACK(bt_csip_callbacks, new_set_found_cb, params.set_id, params.addr, + params.size, params.sirk, params.including_srvc_uuid, + params.lock_support); +} + +void btif_conn_state_changed_cb(tBTA_CSIP_CONN_STATE_CHANGED params) { + HAL_CBACK(bt_csip_callbacks, conn_state_cb, params.app_id, params.addr, + params.state, params.status); +} + +void btif_new_set_member_found_cb(tBTA_SET_MEMBER_FOUND params) { + HAL_CBACK(bt_csip_callbacks, new_set_member_cb, params.set_id, params.addr); +} + +void btif_lock_status_changed_cb(tBTA_LOCK_STATUS_CHANGED params) { + HAL_CBACK(bt_csip_callbacks, lock_status_cb, params.app_id, params.set_id, + params.value, params.status, params.addr); +} + +void btif_lock_available_cb(tBTA_LOCK_AVAILABLE params) { + HAL_CBACK(bt_csip_callbacks, lock_available_cb, params.app_id, params.set_id, + params.addr); +} + +void btif_set_size_changed_cb (tBTA_CSIP_SET_SIZE_CHANGED params) { + HAL_CBACK(bt_csip_callbacks, size_changed_cb, params.set_id, params.size, + params.addr); +} + +void btif_set_sirk_changed_cb (tBTA_CSIP_SET_SIRK_CHANGED params) { + HAL_CBACK(bt_csip_callbacks, sirk_changed_cb, params.set_id, params.sirk, + params.addr); +} + +const char* btif_csip_get_event_name(tBTA_CSIP_EVT event) { + switch(event) { + case BTA_CSIP_LOCK_STATUS_CHANGED_EVT: + return "BTA_CSIP_LOCK_STATUS_CHANGED_EVT"; + case BTA_CSIP_SET_MEMBER_FOUND_EVT: + return "BTA_CSIP_SET_MEMBER_FOUND_EVT"; + case BTA_CSIP_LOCK_AVAILABLE_EVT: + return "BTA_CSIP_LOCK_AVAILABLE_EVT"; + case BTA_CSIP_NEW_SET_FOUND_EVT: + return "BTA_CSIP_NEW_SET_FOUND_EVT"; + case BTA_CSIP_CONN_STATE_CHG_EVT: + return "BTA_CSIP_CONN_STATE_CHG_EVT"; + case BTA_CSIP_SET_SIZE_CHANGED: + return "BTA_CSIP_SET_SIZE_CHANGED"; + case BTA_CSIP_SET_SIRK_CHANGED: + return "BTA_CSIP_SET_SIRK_CHANGED"; + default: + return "UNKNOWN_EVENT"; + } +} + +void btif_csip_evt (tBTA_CSIP_EVT event, tBTA_CSIP_DATA* p_data) { + BTIF_TRACE_EVENT("%s: Event = %02x (%s)", __func__, event, btif_csip_get_event_name(event)); + + switch (event) { + case BTA_CSIP_LOCK_STATUS_CHANGED_EVT: { + tBTA_LOCK_STATUS_CHANGED lock_status_params = p_data->lock_status_param; + do_in_jni_thread(Bind(btif_lock_status_changed_cb, lock_status_params)); + } + break; + + case BTA_CSIP_LOCK_AVAILABLE_EVT: { + tBTA_LOCK_AVAILABLE lock_avl_param = p_data->lock_available_param; + do_in_jni_thread(Bind(btif_lock_available_cb, lock_avl_param)); + } + break; + + case BTA_CSIP_NEW_SET_FOUND_EVT: { + tBTA_CSIP_NEW_SET_FOUND new_set_params = p_data->new_set_params; + memcpy(new_set_params.sirk, p_data->new_set_params.sirk, SIRK_SIZE); + do_in_jni_thread(Bind(btif_new_set_found_cb, new_set_params)); + } + break; + + case BTA_CSIP_SET_MEMBER_FOUND_EVT: { + tBTA_SET_MEMBER_FOUND new_member_params = p_data->set_member_param; + do_in_jni_thread(Bind(btif_new_set_member_found_cb, new_member_params)); + } + break; + + case BTA_CSIP_CONN_STATE_CHG_EVT: { + tBTA_CSIP_CONN_STATE_CHANGED conn_params = p_data->conn_params; + do_in_jni_thread(Bind(btif_conn_state_changed_cb, conn_params)); + } + break; + + case BTA_CSIP_SET_SIZE_CHANGED: { + tBTA_CSIP_SET_SIZE_CHANGED size_chg_param = p_data->size_chg_params; + do_in_jni_thread(Bind(btif_set_size_changed_cb, size_chg_param)); + } + break; + + case BTA_CSIP_SET_SIRK_CHANGED: { + tBTA_CSIP_SET_SIRK_CHANGED sirk_chg_param = p_data->sirk_chg_params; + do_in_jni_thread(Bind(btif_set_sirk_changed_cb, sirk_chg_param)); + } + break; + + default: + BTIF_TRACE_ERROR("%s: Unknown event %d", __func__, event); + } +} + +/* Initialization of CSIP module on BT ON*/ +bt_status_t btif_csip_init( btcsip_callbacks_t* callbacks ) { + bt_csip_callbacks = callbacks; + + do_in_jni_thread(Bind(BTA_CsipEnable, btif_csip_evt)); + btif_register_uuid_srvc_disc(Uuid::FromString("1846")); + + return BT_STATUS_SUCCESS; +} + +/* Connect call from upper layer for GATT Connecttion to a given Set Member */ +bt_status_t btif_csip_connect (uint8_t app_id, RawAddress *bd_addr) { + BTIF_TRACE_EVENT("%s: Address: %s", __func__, bd_addr->ToString().c_str()); + + do_in_jni_thread(Bind(BTA_CsipConnect, app_id, *bd_addr)); + + return BT_STATUS_SUCCESS; +} + +/* Call from upper layer to disconnect GATT Connection for given Set Member */ +bt_status_t btif_csip_disconnect (uint8_t app_id, RawAddress *bd_addr ) { + BTIF_TRACE_EVENT("%s", __func__); + + do_in_jni_thread(Bind(BTA_CsipDisconnect, app_id, *bd_addr)); + + return BT_STATUS_SUCCESS; +} + +/** register app/module with CSIP profile */ +bt_status_t btif_csip_app_register (const bluetooth::Uuid& uuid) { + BTIF_TRACE_EVENT("%s", __func__); + return do_in_jni_thread(Bind( + [](const Uuid& uuid) { + BTA_RegisterCsipApp( + btif_csip_evt, + base::Bind( + [](const Uuid& uuid, uint8_t status, uint8_t app_id) { + do_in_jni_thread(Bind( + [](const Uuid& uuid, uint8_t status, uint8_t app_id) { + HAL_CBACK(bt_csip_callbacks, app_registered_cb, + status, app_id, uuid); + }, + uuid, status, app_id)); + }, + uuid)); + }, uuid)); +} + +/** unregister csip App/Module */ +bt_status_t btif_csip_app_unregister (uint8_t app_id) { + BTIF_TRACE_EVENT("%s", __func__); + return do_in_jni_thread(Bind(BTA_UnregisterCsipApp, app_id)); +} + +/** change lock value */ +bt_status_t btif_csip_set_lock_value (uint8_t app_id, uint8_t set_id, uint8_t lock_value, + std::vector devices) { + BTIF_TRACE_EVENT("%s appId = %d setId = %d Lock Value = %02x ", __func__, + app_id, set_id, lock_value); + tBTA_SET_LOCK_PARAMS lock_params = {app_id, set_id, lock_value, devices}; + do_in_jni_thread(Bind(BTA_CsipSetLockValue, lock_params)); + return BT_STATUS_SUCCESS; +} + +void btif_csip_cleanup() { + BTIF_TRACE_EVENT("%s", __func__); + do_in_jni_thread(Bind(BTA_CsipDisable)); +} + +const btcsip_interface_t btcsipInterface = { + sizeof(btcsipInterface), + btif_csip_init, + btif_csip_connect, + btif_csip_disconnect, + btif_csip_app_register, + btif_csip_app_unregister, + btif_csip_set_lock_value, + btif_csip_cleanup, +}; + +/******************************************************************************* + * + * Function btif_csip_get_interface + * + * Description Get the csip callback interface + * + * Returns btcsip_interface_t + * + ******************************************************************************/ +const btcsip_interface_t* btif_csip_get_interface() { + BTIF_TRACE_EVENT("%s", __func__); + return &btcsipInterface; +} diff --git a/le_audio/system/bt/btif/src/btif_dm_adv_audio.cc b/le_audio/system/bt/btif/src/btif_dm_adv_audio.cc new file mode 100644 index 00000000000..e151851a4b3 --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_dm_adv_audio.cc @@ -0,0 +1,678 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + **************************************************************************/ + +#define LOG_TAG "bt_btif_dm" + +#include "btif_dm.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "hardware/vendor.h" + +#include +#include + +#include "advertise_data_parser.h" +#include "bt_common.h" +#include "bta_closure_api.h" +#include "bta_csip_api.h" +#include "bta_gatt_api.h" +#include "btif_api.h" +#include "btif_bqr.h" +#include "btif_config.h" +#include "btif_dm.h" +#include "btif_hh.h" +#include "btif_sdp.h" +#include "btif_storage.h" +#include "btif_util.h" +#include "btu.h" +#include "bta/include/bta_dm_api.h" +#include "device/include/controller.h" +#include "device/include/interop.h" +#include "internal_include/stack_config.h" +#include "osi/include/allocator.h" +#include "osi/include/log.h" +#include "osi/include/metrics.h" +#include "osi/include/osi.h" +#include "osi/include/properties.h" +#include "stack/btm/btm_int.h" +#include "stack_config.h" +#include "stack/sdp/sdpint.h" +#include "btif_tws_plus.h" +#include "device/include/device_iot_config.h" +#include "btif_bap_config.h" +#include "bta_dm_adv_audio.h" +#include "btif_dm_adv_audio.h" + +using bluetooth::Uuid; + +/****************************************************************************** + * Constants & Macros + *****************************************************************************/ +#define BTIF_DM_GET_REMOTE_PROP(b,t,v,l,p) \ + {p.type=t;p.val=v;p.len=l;btif_storage_get_remote_device_property(b,&p);} + +extern std::vector uuid_srv_disc_search; +std::unordered_map adv_audio_device_db; +extern void bta_dm_adv_audio_gatt_conn(RawAddress p_bd_addr); +extern void bta_dm_adv_audio_close(RawAddress p_bd_addr); +extern bool btif_has_ble_keys(const char* bdstr); +bt_status_t btif_storage_get_remote_device_property( + const RawAddress* remote_bd_addr, bt_property_t* property); +extern tBTA_LEA_PAIRING_DB bta_lea_pairing_cb; +extern void search_services_copy_cb(uint16_t event, char* p_dest, char* p_src); + +extern void bond_state_changed(bt_status_t status, const RawAddress& bd_addr, + bt_bond_state_t state); + +#define BTIF_STORAGE_GET_REMOTE_PROP(b, t, v, l, p) \ + do { \ + (p).type = (t); \ + (p).val = (v); \ + (p).len = (l); \ + btif_storage_get_remote_device_property((b), &(p)); \ + } while (0) + +extern bool check_adv_audio_cod(uint32_t cod); +extern bool is_remote_support_adv_audio(const RawAddress remote_bdaddr); +extern bool is_le_audio_service(Uuid uuid); +extern void bta_adv_audio_update_bond_db(RawAddress p_bd_addr, uint8_t transport); + +#define BTIF_DM_MAX_SDP_ATTEMPTS_AFTER_PAIRING 2 + + +tBTA_TRANSPORT btif_dm_get_adv_audio_transport(const RawAddress& bd_addr) { + tBTM_INQ_INFO* p_inq_info; + + p_inq_info = BTM_InqDbRead(bd_addr); + if (p_inq_info != NULL) { + BTIF_TRACE_DEBUG("%s, inq_result_type %x", + __func__, p_inq_info->results.inq_result_type); + if (p_inq_info->results.inq_result_type & BTM_INQ_RESULT_BLE) { + return BT_TRANSPORT_LE; + } + } + return BT_TRANSPORT_BR_EDR; +} + +/******************************************************************************* + * + * Function btif_set_remote_device_uuid_property + * + * Description Store the remote LEA services in config file + * + * Returns void + * + ******************************************************************************/ +static void btif_set_remote_device_uuid_property(RawAddress p_addr, + int num_uuids, + bluetooth::Uuid *new_uuids) { + + Uuid remote_uuids[BT_MAX_NUM_UUIDS]; + bt_property_t prop; + + for (int j = 0; j < num_uuids; j++) { + remote_uuids[j] = new_uuids[j]; + BTIF_TRACE_EVENT("%s: UUID %s index %d ", __func__, + remote_uuids[j].ToString().c_str(), j); + } + prop.type = (bt_property_type_t)BT_PROPERTY_ADV_AUDIO_UUIDS; + prop.val = &remote_uuids[0]; + prop.len = (num_uuids) * (Uuid::kNumBytes128); + Uuid* tmp = (Uuid*)(prop.val); + BTIF_TRACE_EVENT("%s: Checking it %s", __func__, tmp->ToString().c_str()); + int ret = btif_storage_set_remote_device_property(&p_addr, &prop); + ASSERTC(ret == BT_STATUS_SUCCESS, "storing remote services failed", + ret); +} + +/******************************************************************************* + * + * Function btif_dm_lea_search_services_evt + * + * Description Executes search services event in btif context + * + * Returns void + * + ******************************************************************************/ +void btif_dm_lea_search_services_evt(uint16_t event, char* p_param) { + tBTA_DM_SEARCH* p_data = (tBTA_DM_SEARCH*)p_param; + + bt_bond_state_t pairing_state = BT_BOND_STATE_NONE; + uint8_t sdp_attempts = 0; + RawAddress pairing_bd_addr; + RawAddress static_bd_addr; + btif_get_pairing_cb_info(&pairing_state, &sdp_attempts, + &pairing_bd_addr, &static_bd_addr); + + BTIF_TRACE_EVENT("%s: event = %d", __func__, event); + switch (event) { + case BTA_DM_DISC_RES_EVT: { + uint32_t i = 0, j = 0; + bt_property_t prop[2]; + int num_properties = 0; + bt_status_t ret; + Uuid remote_uuids[BT_MAX_NUM_UUIDS]; + Uuid missed_remote_uuids[BT_MAX_NUM_UUIDS]; + uint8_t missing_uuids_len = 0; + bt_property_t remote_uuid_prop; + + RawAddress& bd_addr = p_data->disc_res.bd_addr; + + BTIF_TRACE_DEBUG("%s:(result=0x%x, services 0x%x)", __func__, + p_data->disc_res.result, p_data->disc_res.services); + + /* retry sdp service search, if sdp fails for pairing bd address, + ** report sdp results to APP immediately for non pairing addresses + */ + if ((p_data->disc_res.result != BTA_SUCCESS) && + (pairing_state == BT_BOND_STATE_BONDED) && + ((p_data->disc_res.bd_addr == pairing_bd_addr) || + (p_data->disc_res.bd_addr == static_bd_addr)) && + (sdp_attempts > 0)) { + if (sdp_attempts < BTIF_DM_MAX_SDP_ATTEMPTS_AFTER_PAIRING) { + BTIF_TRACE_WARNING("%s:SDP failed after bonding re-attempting", + + __func__); + btif_inc_sdp_attempts(); + btif_dm_get_remote_services_by_transport(&bd_addr, BT_TRANSPORT_BR_EDR); + return; + } else { + BTIF_TRACE_WARNING( + "%s: SDP reached to maximum attempts, sending bond fail to upper layers", + __func__); + btif_reset_sdp_attempts(); + if (bta_remote_device_is_dumo(bd_addr)) { + auto itr = bta_lea_pairing_cb.dev_addr_map.find(bd_addr); + if (itr != bta_lea_pairing_cb.dev_addr_map.end()) { + if ((itr->first != itr->second)) { + bta_lea_pairing_cb.is_sdp_discover = false; + bond_state_changed(BT_STATUS_FAIL, + bd_addr, BT_BOND_STATE_NONE); + } else { + btif_reset_pairing_cb(); + BTIF_TRACE_WARNING("%s: Skipping BOND_NONE for %s", __func__, + bd_addr.ToString().c_str()); + } + } else { + BTIF_TRACE_ERROR("%s: SDP shouldnt on random address. Wrong path %s", __func__, + bd_addr.ToString().c_str()); + btif_reset_pairing_cb(); + bond_state_changed(BT_STATUS_FAIL, + bd_addr, BT_BOND_STATE_NONE); + btif_storage_remove_bonded_device(&bd_addr); + BTA_DmRemoveDevice(bd_addr); + } + return; + } else { + BTIF_TRACE_ERROR("%s: SDP shouldnt called. Wrong path %s", __func__, + bd_addr.ToString().c_str()); + } + } + } + prop[0].type = BT_PROPERTY_UUIDS; + prop[0].len = 0; + if ((p_data->disc_res.result == BTA_SUCCESS) && + (p_data->disc_res.num_uuids > 0)) { + prop[0].val = p_data->disc_res.p_uuid_list; + prop[0].len = p_data->disc_res.num_uuids * Uuid::kNumBytes128; + + for (i = 0; i < p_data->disc_res.num_uuids; i++) { + std::string temp = ((p_data->disc_res.p_uuid_list + i))->ToString(); + LOG_INFO(LOG_TAG, "%s index:%d uuid:%s", __func__, i, temp.c_str()); + } + } + + /* onUuidChanged requires getBondedDevices to be populated. + ** bond_state_changed needs to be sent prior to remote_device_property + */ + if ((pairing_state == BT_BOND_STATE_BONDED && sdp_attempts) && + (p_data->disc_res.bd_addr == pairing_bd_addr || + p_data->disc_res.bd_addr == static_bd_addr)) { + LOG_INFO(LOG_TAG, "%s: SDP search done for %s", __func__, + bd_addr.ToString().c_str()); + btif_reset_sdp_attempts(); + BTA_DmResetPairingflag(bd_addr); + btif_reset_pairing_cb(); + + // Send one empty UUID to Java to unblock pairing intent when SDP failed + // or no UUID is discovered + if (p_data->disc_res.result != BTA_SUCCESS || + p_data->disc_res.num_uuids == 0) { + LOG_INFO(LOG_TAG, + "%s: SDP failed, send empty UUID to unblock bonding %s", + __func__, bd_addr.ToString().c_str()); + bt_property_t prop; + + Uuid uuid = {}; + //Updating in lea_pairing_database + if (bta_remote_device_is_dumo(bd_addr)) { + tBTA_DEV_PAIRING_CB *p_lea_pair_cb = NULL; + + p_lea_pair_cb = bta_get_lea_pair_cb(bd_addr); + if (p_lea_pair_cb) { + p_lea_pair_cb->sdp_disc_status = false; + } + } + + if (btif_dm_get_adv_audio_transport(bd_addr) == BT_TRANSPORT_BR_EDR) + { + prop.type = BT_PROPERTY_UUIDS; + } else { + prop.type = (bt_property_type_t)BT_PROPERTY_ADV_AUDIO_UUIDS; + } + prop.val = &uuid; + prop.len = Uuid::kNumBytes128; + + /* Send the event to the BTIF */ + HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, + BT_STATUS_SUCCESS, &bd_addr, 1, &prop); + break; + } + } + + // updates extra uuids which are discovered during + // new sdp search to existing uuid list present in conf file. + // If conf file has more UUIDs than the sdp search, it will + // update the conf file UUIDs as the final UUIDs + BTIF_STORAGE_FILL_PROPERTY(&remote_uuid_prop, BT_PROPERTY_UUIDS, + sizeof(remote_uuids), remote_uuids); + btif_storage_get_remote_device_property(&bd_addr, + &remote_uuid_prop); + if(remote_uuid_prop.len && p_data->disc_res.result == BTA_SUCCESS) { + // compare now + bool uuid_found = false; + uint8_t uuid_len = remote_uuid_prop.len / sizeof(Uuid); + for (i = 0; i < p_data->disc_res.num_uuids; i++) { + uuid_found = false; + Uuid* disc_uuid = reinterpret_cast (p_data->disc_res.p_uuid_list + i); + for (j = 0; j < uuid_len; j++) { + Uuid* base_uuid = reinterpret_cast (remote_uuid_prop.val) + j; + if(*disc_uuid == *base_uuid) { + uuid_found = true; + break; + } + } + if(!uuid_found) { + BTIF_TRACE_WARNING("%s:new uuid found ", __func__); + memcpy(&missed_remote_uuids[missing_uuids_len++], disc_uuid, sizeof(Uuid)); + } + } + + // add the missing uuids now + if(missing_uuids_len) { + BTIF_TRACE_WARNING("%s :missing_uuids_len = %d ", __func__, missing_uuids_len); + for (j = 0; j < missing_uuids_len && + (unsigned long)remote_uuid_prop.len < BT_MAX_NUM_UUIDS * sizeof(Uuid); j++) { + memcpy(&remote_uuids[uuid_len + j], &missed_remote_uuids[j], sizeof(Uuid)); + remote_uuid_prop.len += sizeof(Uuid); + } + } + + prop[0].type = BT_PROPERTY_UUIDS; + prop[0].val = remote_uuids; + prop[0].len = remote_uuid_prop.len; + ret = btif_storage_set_remote_device_property(&bd_addr, &prop[0]); + ASSERTC(ret == BT_STATUS_SUCCESS, "storing remote services failed", + ret); + //Send the UUID values to upper layer as BT_PROPERTY_ADV_AUDIO_UUIDS + num_properties++; + + if (bta_is_bredr_primary_transport(bd_addr)) { + BTIF_TRACE_WARNING("%s: Initiating LE connection ", __func__); + adv_audio_device_db[bd_addr] = MAJOR_LE_AUDIO_VENDOR_COD; + bta_le_audio_dev_cb.bond_progress = true; + bta_dm_adv_audio_gatt_conn(bd_addr); + } + } else if (p_data->disc_res.num_uuids != 0) { + /* Also write this to the NVRAM */ + ret = btif_storage_set_remote_device_property(&bd_addr, &prop[0]); + ASSERTC(ret == BT_STATUS_SUCCESS, "storing remote services failed", + ret); + num_properties++; + } + + /* Remote name update */ + if (strlen((const char *) p_data->disc_res.bd_name)) { + prop[1].type = BT_PROPERTY_BDNAME; + prop[1].val = p_data->disc_res.bd_name; + prop[1].len = strlen((char *)p_data->disc_res.bd_name); + + ret = btif_storage_set_remote_device_property(&bd_addr, &prop[1]); + ASSERTC(ret == BT_STATUS_SUCCESS, "failed to save remote device property", ret); + num_properties++; + } + + if (num_properties > 0) { + if (btif_dm_get_adv_audio_transport(bd_addr) == BT_TRANSPORT_BR_EDR) + { + prop[0].type = BT_PROPERTY_UUIDS; + } else { + prop[0].type = (bt_property_type_t)BT_PROPERTY_ADV_AUDIO_UUIDS; + } + /* Send the event to the BTIF */ + HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, BT_STATUS_SUCCESS, + &bd_addr, num_properties, prop); + } + + int validAddr = 1; + bt_property_t rem_prop; + BTIF_STORAGE_GET_REMOTE_PROP(&bd_addr, (bt_property_type_t)BT_PROPERTY_REM_DEViCE_VALID_ADDR, + &validAddr, sizeof(int), + rem_prop); + + if (validAddr != 0) { + bt_property_t prop_addr; + int is_valid = bta_is_adv_audio_valid_bdaddr(bd_addr); + prop_addr.type = (bt_property_type_t)BT_PROPERTY_REM_DEViCE_VALID_ADDR; + prop_addr.val = (void *)&is_valid; + prop_addr.len = sizeof(int); + ret = btif_storage_set_remote_device_property(&bd_addr, &prop_addr); + ASSERTC(ret == BT_STATUS_SUCCESS, "failed to save remote device property", ret); + } + + bt_device_type_t dev_type; + dev_type = (bt_device_type_t)BT_DEVICE_TYPE_DUMO; + bt_property_t prop_dev; + BTIF_STORAGE_FILL_PROPERTY(&prop_dev, + BT_PROPERTY_TYPE_OF_DEVICE, sizeof(dev_type), + &dev_type); + ret = btif_storage_set_remote_device_property(&bd_addr, &prop_dev); + ASSERTC(ret == BT_STATUS_SUCCESS, "failed to save remote device type", + ret); + + HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, BT_STATUS_SUCCESS, + &bd_addr, 1, &prop_dev); + + /* If below condition is true, it means LE random advertising + * has no ADV audio uuids, but identity address contains adv audio bit + * As per current design, if pairing initiated through non adv audio + * address then we dont need to fetch ADV audio role and services + */ + if ((btif_get_is_adv_audio_pair_info(bd_addr) == 0)) { + prop_dev.type = (bt_property_type_t)BT_PROPERTY_ADV_AUDIO_ACTION_UUID; + prop_dev.val = (void *)&validAddr; + prop_dev.len = sizeof(uint8_t); + HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, BT_STATUS_SUCCESS, + &bd_addr, 1, &prop_dev); + } + + } break; + + case BTA_DM_DISC_CMPL_EVT: + /* fixme */ + break; + + case BTA_DM_SEARCH_CANCEL_CMPL_EVT: + /* no-op */ + break; + + case BTA_DM_DISC_BLE_RES_EVT: { + BTIF_TRACE_DEBUG("%s: service %s", __func__, + p_data->disc_ble_res.service.ToString().c_str()); + bt_property_t prop; + bt_status_t ret; + RawAddress& bd_addr = p_data->disc_ble_res.bd_addr; + /* Remote name update */ + if (strnlen((const char*)p_data->disc_ble_res.bd_name, BD_NAME_LEN)) { + prop.type = BT_PROPERTY_BDNAME; + prop.val = p_data->disc_ble_res.bd_name; + prop.len = + strnlen((char*)p_data->disc_ble_res.bd_name, BD_NAME_LEN); + + ret = btif_storage_set_remote_device_property(&bd_addr, &prop); + ASSERTC(ret == BT_STATUS_SUCCESS, + "failed to save remote device property", ret); + /* Send the event to the BTIF */ + HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, BT_STATUS_SUCCESS, + &bd_addr, 1, &prop); + } + } break; + + case BTA_DM_LE_AUDIO_SEARCH_CMPL_EVT: + { + tBTA_DEV_PAIRING_CB *p_lea_pair_cb = NULL; + p_lea_pair_cb = bta_get_lea_pair_cb(p_data->disc_ble_res.bd_addr); + if (p_lea_pair_cb != NULL) { + btif_reset_pairing_cb(); + bt_property_t prop[5], prop_tmp[2]; + RawAddress& bd_addr = p_data->disc_ble_res.bd_addr; + int num_properties = 0; + bool id_addr_action_uuid = false; + RawAddress id_addr = bta_get_rem_dev_id_addr(bd_addr); + + prop[num_properties].type = (bt_property_type_t)BT_PROPERTY_ADV_AUDIO_UUIDS; + prop[num_properties].val = p_data->adv_audio_disc_cmpl.adv_audio_uuids; + prop[num_properties].len = p_data->adv_audio_disc_cmpl.num_uuids * + Uuid::kNumBytes128; + /* Also write this to the NVRAM */ + bt_property_t cod_prop1; + uint32_t cod_p; + + BTIF_STORAGE_FILL_PROPERTY(&cod_prop1, + BT_PROPERTY_CLASS_OF_DEVICE, sizeof(cod_p), &cod_p); + btif_storage_get_remote_device_property(&bd_addr, + &cod_prop1); + int ret; + cod_p |= MAJOR_LE_AUDIO_VENDOR_COD; + BTIF_STORAGE_FILL_PROPERTY(&cod_prop1, + BT_PROPERTY_CLASS_OF_DEVICE, sizeof(cod_p), &cod_p); + ret = btif_storage_set_remote_device_property(&bd_addr, &cod_prop1); + ASSERTC(ret == BT_STATUS_SUCCESS, + "failed to save remote device property", ret); + + if (bta_remote_device_is_dumo(bd_addr) + && (id_addr != bd_addr) && (bta_lea_pairing_cb.is_sdp_discover == true)) { + if(id_addr != RawAddress::kEmpty) { + BTIF_TRACE_DEBUG("%s: Found BT_PROPERTY_ADV_AUDIO_UUIDS %s", + p_data->adv_audio_disc_cmpl.adv_audio_uuids[0].ToString().c_str(), + id_addr.ToString().c_str()); + + bt_property_t cod_prop; + uint32_t cod; + + BTIF_STORAGE_FILL_PROPERTY(&cod_prop, + BT_PROPERTY_CLASS_OF_DEVICE, sizeof(cod), &cod); + btif_storage_get_remote_device_property(&id_addr, + &cod_prop); + BTIF_TRACE_DEBUG("%s: Cod is %x", __func__, cod); + cod |= MAJOR_LE_AUDIO_VENDOR_COD; + BTIF_STORAGE_FILL_PROPERTY(&cod_prop, + BT_PROPERTY_CLASS_OF_DEVICE, sizeof(cod), &cod); + ret = btif_storage_set_remote_device_property(&id_addr, &cod_prop); + ASSERTC(ret == BT_STATUS_SUCCESS, + "failed to save remote device property", ret); + num_properties++; + prop[num_properties].type = BT_PROPERTY_CLASS_OF_DEVICE; + prop[num_properties].val = (void *) &cod; + prop[num_properties].len = sizeof(cod); + + num_properties ++; + + BTIF_STORAGE_FILL_PROPERTY(&prop[num_properties], + (bt_property_type_t)BT_PROPERTY_REM_DEV_IDENT_BD_ADDR, sizeof(RawAddress), &bd_addr); + ret = btif_storage_set_remote_device_property(&id_addr, &prop[num_properties]); + ASSERTC(ret == BT_STATUS_SUCCESS, + "failed to save remote device property", ret); + + id_addr_action_uuid = true; + HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, BT_STATUS_SUCCESS, + &id_addr, 3, &prop[0]); + } + } + num_properties = 1; + + BTIF_STORAGE_FILL_PROPERTY(&prop[num_properties], + (bt_property_type_t)BT_PROPERTY_REM_DEV_IDENT_BD_ADDR, sizeof(RawAddress), &id_addr); + + ret = btif_storage_set_remote_device_property(&bd_addr, &prop[num_properties]); + ASSERTC(ret == BT_STATUS_SUCCESS, + "failed to save remote device property", ret); + + HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, BT_STATUS_SUCCESS, + &bd_addr, 2, &prop[0]); + + int validAddr = 1; + bt_property_t rem_prop; + BTIF_STORAGE_GET_REMOTE_PROP(&bd_addr, (bt_property_type_t)BT_PROPERTY_REM_DEViCE_VALID_ADDR, + &validAddr, sizeof(int), + rem_prop); + validAddr = bta_is_adv_audio_valid_bdaddr(bd_addr); + BTIF_TRACE_DEBUG("%s: is Valid Address Check value %d bd_addr %s", __func__, validAddr, bd_addr.ToString().c_str()); + prop_tmp[0].type = (bt_property_type_t)BT_PROPERTY_REM_DEViCE_VALID_ADDR; + prop_tmp[0].val = (void *)&validAddr; + prop_tmp[0].len = sizeof(int); + ret = btif_storage_set_remote_device_property(&bd_addr, &prop_tmp[0]); + ASSERTC(ret == BT_STATUS_SUCCESS, "failed to save remote device property", ret); + + prop_tmp[1].type = (bt_property_type_t)BT_PROPERTY_ADV_AUDIO_ACTION_UUID; + prop_tmp[1].val = (void *)&validAddr; + prop_tmp[1].len = sizeof(uint8_t); + + if (id_addr_action_uuid) { + BTIF_TRACE_DEBUG("%s: IDENTITY ADDR ACTION UUID %s ", __func__, + id_addr.ToString().c_str()); + HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, BT_STATUS_SUCCESS, + &id_addr, 1, &prop_tmp[1]); + } + + HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, BT_STATUS_SUCCESS, + &bd_addr, 2, &prop_tmp[0]); + if (id_addr_action_uuid) { + btif_set_remote_device_uuid_property(id_addr, + p_data->adv_audio_disc_cmpl.num_uuids, &p_data->adv_audio_disc_cmpl.adv_audio_uuids[0]); + } + btif_set_remote_device_uuid_property(bd_addr, + p_data->adv_audio_disc_cmpl.num_uuids, &p_data->adv_audio_disc_cmpl.adv_audio_uuids[0]); + + bta_dm_adv_audio_close(bd_addr); + bta_dm_reset_lea_pairing_info(bd_addr); + } else { + BTIF_TRACE_DEBUG("%s: ONCE AGAIN WRITING IDENTITY", __func__); + } + } + break; + default: { ASSERTC(0, "unhandled search services event", event); } break; + } +} + +/**************************************************************************** + * + * Function btif_register_uuid_srvc_disc + * + * Description Add to UUID to the service search queue + * + * Returns void + * + ****************************************************************************/ +void btif_register_uuid_srvc_disc(bluetooth::Uuid uuid) { + + uuid_srv_disc_search.push_back(uuid); + BTIF_TRACE_DEBUG("btif_register_uuid_srvc_disc, no of uuids %d %s", + uuid_srv_disc_search.size(), uuid.ToString().c_str()); +} + +void btif_dm_release_action_uuid(RawAddress bd_addr) { + + bt_property_t prop_dev; + int status = 1; + prop_dev.type = (bt_property_type_t)BT_PROPERTY_ADV_AUDIO_ACTION_UUID; + prop_dev.val = (void *)&status; + prop_dev.len = sizeof(uint8_t); + HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, BT_STATUS_SUCCESS, + &bd_addr, 1, &prop_dev); +} + +/**************************************************************************** + * + * Function check_adv_audio_cod + * + * Description This API is used to check whether COD contains LE Audio + * COD or not? + * + * Returns bool + * + ****************************************************************************/ +bool check_adv_audio_cod(uint32_t cod) { + + BTIF_TRACE_DEBUG("check_adv_audio_cod "); + + if (cod & MAJOR_LE_AUDIO_VENDOR_COD) { + return true; + } + return false; +} + +/******************************************************************************* + * + * Function is_remote_support_adv_audio + * + * Description is remote device is supporting LE audio or not + * + * Returns bool + * + ******************************************************************************/ + +bool is_remote_support_adv_audio(const RawAddress p_addr) { + if (adv_audio_device_db.find(p_addr) + != adv_audio_device_db.end()) { + BTIF_TRACE_DEBUG("%s %s LE AUDIO Support ", __func__, + p_addr.ToString().c_str()); + return true; + } + + bool status = bta_is_remote_support_lea(p_addr); + if (status) return true; + + bt_property_t cod_prop; + uint32_t cod_p; + + BTIF_STORAGE_FILL_PROPERTY(&cod_prop, + BT_PROPERTY_CLASS_OF_DEVICE, sizeof(cod_p), &cod_p); + btif_storage_get_remote_device_property(&p_addr, + &cod_prop); + + if ((cod_p & MAJOR_LE_AUDIO_VENDOR_COD) + == MAJOR_LE_AUDIO_VENDOR_COD) { + BTIF_TRACE_DEBUG("%s ADV AUDIO COD is matched ", __func__); + return true; + } + + return false; +} + +void bte_dm_adv_audio_search_services_evt(tBTA_DM_SEARCH_EVT event, + tBTA_DM_SEARCH* p_data) { + BTIF_TRACE_DEBUG(" %s ", __func__); + uint16_t param_len = 0; + if (p_data) param_len += sizeof(tBTA_DM_SEARCH); + switch (event) { + case BTA_DM_DISC_RES_EVT: { + if ((p_data && p_data->disc_res.result == BTA_SUCCESS) && + (p_data->disc_res.num_uuids > 0)) { + param_len += (p_data->disc_res.num_uuids * Uuid::kNumBytes128); + } + } break; + } + /* TODO: The only other member that needs a deep copy is the p_raw_data. But + * * not sure + * * if raw_data is needed. */ + btif_transfer_context( + btif_dm_lea_search_services_evt, event, (char*)p_data, param_len, + (param_len > sizeof(tBTA_DM_SEARCH)) ? search_services_copy_cb : NULL); +} + diff --git a/le_audio/system/bt/btif/src/btif_mcp.cc b/le_audio/system/bt/btif/src/btif_mcp.cc new file mode 100644 index 00000000000..07ed2f04d77 --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_mcp.cc @@ -0,0 +1,171 @@ +/****************************************************************************** + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +/* MCP Interface */ +#define LOG_TAG "bt_btif_mcp" + +#include "bt_target.h" +#include "bta_closure_api.h" +#include "bta_mcp_api.h" +#include "btif_common.h" +#include "btif_storage.h" + +#include +#include +#include +#include + +using base::Bind; +using base::Unretained; +using base::Owned; +using bluetooth::Uuid; +using std::vector; +using base::Bind; +using base::Unretained; + +using bluetooth::mcp_server::McpServerCallbacks; +using bluetooth::mcp_server::McpServerInterface; + +namespace { +class McpServerInterfaceImpl; +std::unique_ptr McpServerInstance; + +class McpServerInterfaceImpl + : public McpServerInterface, public McpServerCallbacks { + ~McpServerInterfaceImpl() = default; + + void Init(McpServerCallbacks* callback, Uuid bt_uuid) override { + LOG(INFO) << __func__ ; + this->callbacks = callback; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::Initialize, this, bt_uuid)); + } + + void MediaState(uint8_t state) override { + LOG(INFO) << __func__ << ": state " << state; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::MediaState, Unretained(McpServer::Get()), state)); + } + + void MediaPlayerName(uint8_t* name) override { + LOG(INFO) << __func__ << ": name" << name; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::MediaPlayerName, Unretained(McpServer::Get()), name)); + } + + void MediaControlPointOpcodeSupported(uint32_t feature) override { + LOG(INFO) << __func__ << ": feature" << feature; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::MediaControlPointOpcodeSupported, Unretained(McpServer::Get()), feature)); + } + + void MediaControlPoint(uint8_t value) override { + LOG(INFO) << __func__ << ": value" << value; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::MediaControlPoint, Unretained(McpServer::Get()), value)); + } + + void TrackChanged(bool status) override { + LOG(INFO) << __func__ << ": status" << status; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::TrackChanged, Unretained(McpServer::Get()), status)); + } + + void TrackTitle(uint8_t* title) override { + LOG(INFO) << __func__ << ": title" << title; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::TrackTitle, Unretained(McpServer::Get()), title)); + } + + void TrackPosition(int32_t position) override { + LOG(INFO) << __func__ << ": position" << position; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::TrackPosition, Unretained(McpServer::Get()), position)); + } + + void TrackDuration(int32_t duration) override { + LOG(INFO) << __func__ << ": duration" << duration; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::TrackDuration, Unretained(McpServer::Get()), duration)); + } + + void ContentControlId(uint8_t ccid) override { + LOG(INFO) << __func__ << ": ccid" << ccid; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::ContentControlId, Unretained(McpServer::Get()), ccid)); + } + + void PlayingOrderSupported(uint16_t order) override { + LOG(INFO) << __func__ << ": order" << order; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::PlayingOrderSupported, Unretained(McpServer::Get()), order)); + } + + void PlayingOrder(uint8_t value) override { + LOG(INFO) << __func__ << ": value" << value; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::PlayingOrder, Unretained(McpServer::Get()), value)); + } + + void SetActiveDevice(const RawAddress& address, int set_id, int profile) override { + LOG(INFO) << __func__ << ": set_id" << set_id<< ": device"<< address; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::SetActiveDevice, Unretained(McpServer::Get()), address, set_id, profile)); + } + + void DisconnectMcp(const RawAddress& address) override { + LOG(INFO) << __func__ << ": device"<< address; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::DisconnectMcp, Unretained(McpServer::Get()), address)); + } + + void BondStateChange(const RawAddress& address, int state) override { + LOG(INFO) << __func__ << ": device"<< address << " state : " << state; + do_in_bta_thread(FROM_HERE, + Bind(&McpServer::BondStateChange, Unretained(McpServer::Get()), address, state)); + } + + void Cleanup(void) override { + LOG(INFO) << __func__; + do_in_bta_thread(FROM_HERE, Bind(&McpServer::CleanUp)); + } + + void OnConnectionStateChange(int status, + const RawAddress& address) override { + LOG(INFO) << __func__ << ": device=" << address << " state=" << (int)status; + do_in_jni_thread(FROM_HERE, Bind(&McpServerCallbacks::OnConnectionStateChange, + Unretained(callbacks), status, address)); + } + + void MediaControlPointChangeReq(uint8_t state, + const RawAddress& address) override { + LOG(INFO) << __func__ << ": device=" << address << " state=" << (int)state; + do_in_jni_thread(FROM_HERE, Bind(&McpServerCallbacks::MediaControlPointChangeReq, + Unretained(callbacks), state, address)); + } + + void TrackPositionChangeReq(int32_t position) override { + LOG(INFO) << __func__ << " position=" << (int)position; + do_in_jni_thread(FROM_HERE, Bind(&McpServerCallbacks::TrackPositionChangeReq, + Unretained(callbacks), position)); + } + + void PlayingOrderChangeReq(uint32_t order) override { + LOG(INFO) << __func__ << ": order=" << order; + do_in_jni_thread(FROM_HERE, Bind(&McpServerCallbacks::PlayingOrderChangeReq, + Unretained(callbacks), order)); + } + + private: + McpServerCallbacks* callbacks; + }; +}//namespace + +const McpServerInterface* btif_mcp_server_get_interface(void) { + LOG(INFO) << __func__; + if (!McpServerInstance) + McpServerInstance.reset(new McpServerInterfaceImpl()); + return McpServerInstance.get(); +} diff --git a/le_audio/system/bt/btif/src/btif_pacs_client.cc b/le_audio/system/bt/btif/src/btif_pacs_client.cc new file mode 100644 index 00000000000..8c0867a6562 --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_pacs_client.cc @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/****************************************************************************** + * + * Copyright 2018 The Android Open Source Project + * + * 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. + * + ******************************************************************************/ + +#include "bta_closure_api.h" +#include "bta_pacs_client_api.h" +#include "btif_common.h" +#include "btif_storage.h" + +#include +#include +#include +#include +#include +#include +#include "osi/include/thread.h" + +using base::Bind; +using base::Unretained; +using bluetooth::bap::pacs::PacsClient; +using bluetooth::bap::pacs::CodecConfig; +using bluetooth::bap::pacs::ConnectionState; +using bluetooth::bap::pacs::PacsClientCallbacks; +using bluetooth::bap::pacs::PacsClientInterface; + + +namespace { + +class PacsClientInterfaceImpl; +std::unique_ptr PacsClientInstance; + +class PacsClientInterfaceImpl + : public PacsClientInterface, + public PacsClientCallbacks { + ~PacsClientInterfaceImpl() = default; + + void Init(PacsClientCallbacks* callbacks) override { + DVLOG(2) << __func__; + this->callbacks = callbacks; + + do_in_bta_thread( + FROM_HERE, + Bind(&PacsClient::Initialize, this)); + } + + void OnInitialized(int status, int client_id) override { + do_in_jni_thread(FROM_HERE, Bind(&PacsClientCallbacks::OnInitialized, + Unretained(callbacks), status, + client_id)); + } + + void OnConnectionState(const RawAddress& address, + ConnectionState state) override { + DVLOG(2) << __func__ << " address: " << address; + do_in_jni_thread(FROM_HERE, Bind(&PacsClientCallbacks::OnConnectionState, + Unretained(callbacks), address, state)); + } + + void OnAudioContextAvailable(const RawAddress& address, + uint32_t available_contexts) override { + do_in_jni_thread(FROM_HERE, + Bind(&PacsClientCallbacks::OnAudioContextAvailable, + Unretained(callbacks), + address, available_contexts)); + } + + void OnSearchComplete(int status, + const RawAddress& address, + std::vector sink_pac_records, + std::vector src_pac_records, + uint32_t sink_locations, + uint32_t src_locations, + uint32_t available_contexts, + uint32_t supported_contexts) override { + do_in_jni_thread(FROM_HERE, Bind(&PacsClientCallbacks::OnSearchComplete, + Unretained(callbacks), + status, + address, + sink_pac_records, + src_pac_records, + sink_locations, + src_locations, + available_contexts, + supported_contexts)); + } + + void Connect(uint16_t client_id, const RawAddress& address) override { + do_in_bta_thread(FROM_HERE, Bind(&PacsClient::Connect, + Unretained(PacsClient::Get()), + client_id, address, false)); + } + + void Disconnect(uint16_t client_id, const RawAddress& address) override { + do_in_bta_thread(FROM_HERE, Bind(&PacsClient::Disconnect, + Unretained(PacsClient::Get()), + client_id, address)); + } + + void StartDiscovery(uint16_t client_id, + const RawAddress& address) override { + do_in_bta_thread(FROM_HERE, Bind(&PacsClient::StartDiscovery, + Unretained(PacsClient::Get()), + client_id, address)); + } + + void GetAvailableAudioContexts(uint16_t client_id, + const RawAddress& address) override { + do_in_bta_thread(FROM_HERE, Bind(&PacsClient::GetAudioAvailability, + Unretained(PacsClient::Get()), + client_id, address)); + } + + void Cleanup(uint16_t client_id) override { + DVLOG(2) << __func__; + do_in_bta_thread(FROM_HERE, Bind(&PacsClient::CleanUp, client_id)); + } + + private: + PacsClientCallbacks* callbacks; +}; + +} // namespace + +PacsClientInterface* btif_pacs_client_get_interface() { + if (!PacsClientInstance) + PacsClientInstance.reset(new PacsClientInterfaceImpl()); + + return PacsClientInstance.get(); +} diff --git a/le_audio/system/bt/btif/src/btif_vcp_controller.cc b/le_audio/system/bt/btif/src/btif_vcp_controller.cc new file mode 100644 index 00000000000..10ba2acab16 --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_vcp_controller.cc @@ -0,0 +1,131 @@ +/* + *Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/****************************************************************************** + * + * Copyright 2018 The Android Open Source Project + * + * 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. + * + ******************************************************************************/ + +/* Volume Control Profile Interface */ + +#include "bt_target.h" +#include "bta_closure_api.h" +#include "bta_vcp_controller_api.h" +#include "btif_common.h" +#include "btif_storage.h" + +#include +#include +#include +#include +#include +#include + +using base::Bind; +using base::Unretained; +using bluetooth::vcp_controller::ConnectionState; +using bluetooth::vcp_controller::VcpControllerCallbacks; +using bluetooth::vcp_controller::VcpControllerInterface; + +namespace { +class VcpControllerInterfaceImpl; +std::unique_ptr VcpControllerInstance; + +class VcpControllerInterfaceImpl + : public VcpControllerInterface, public VcpControllerCallbacks { + ~VcpControllerInterfaceImpl() = default; + + void Init(VcpControllerCallbacks* callbacks) override { + LOG(INFO) << __func__ ; + this->callbacks = callbacks; + + do_in_bta_thread( + FROM_HERE, + Bind(&VcpController::Initialize, this)); + } + + void OnConnectionState(ConnectionState state, + const RawAddress& address) override { + LOG(INFO) << __func__ << ": device=" << address << " state=" << (int)state; + do_in_jni_thread(FROM_HERE, Bind(&VcpControllerCallbacks::OnConnectionState, + Unretained(callbacks), state, address)); + } + + void OnVolumeStateChange(uint8_t volume, uint8_t mute, + const RawAddress& address) override { + LOG(INFO) << __func__ << ": device=" << address << " volume=" << loghex(volume) + << " mute=" << (int)mute; + do_in_jni_thread(FROM_HERE, Bind(&VcpControllerCallbacks::OnVolumeStateChange, + Unretained(callbacks), volume, mute, address)); + } + + void OnVolumeFlagsChange(uint8_t flags, + const RawAddress& address) override { + LOG(INFO) << __func__ << ": device=" << address << " flags=" << loghex(flags); + do_in_jni_thread(FROM_HERE, Bind(&VcpControllerCallbacks::OnVolumeFlagsChange, + Unretained(callbacks), flags, address)); + } + + void Connect(const RawAddress& address, bool isDirect) override { + LOG(INFO) << __func__ << ": device=" << address; + do_in_bta_thread(FROM_HERE, Bind(&VcpController::Connect, + Unretained(VcpController::Get()), address, isDirect)); + } + + void Disconnect(const RawAddress& address) override { + LOG(INFO) << __func__ << ": device=" << address; + do_in_bta_thread(FROM_HERE, Bind(&VcpController::Disconnect, + Unretained(VcpController::Get()), address)); + } + + void SetAbsVolume(uint8_t volume, const RawAddress& address) override { + LOG(INFO) << __func__ << ": device=" << address << " volume=" << loghex(volume); + do_in_bta_thread(FROM_HERE, Bind(&VcpController::SetAbsVolume, + Unretained(VcpController::Get()), address, volume)); + } + + void Mute(const RawAddress& address) override { + LOG(INFO) << __func__ << ": device=" << address; + do_in_bta_thread(FROM_HERE, Bind(&VcpController::Mute, + Unretained(VcpController::Get()), address)); + } + + void Unmute(const RawAddress& address) override { + LOG(INFO) << __func__ << ": device=" << address; + do_in_bta_thread(FROM_HERE, Bind(&VcpController::Unmute, + Unretained(VcpController::Get()), address)); + } + + void Cleanup(void) override { + LOG(INFO) << __func__; + do_in_bta_thread(FROM_HERE, Bind(&VcpController::CleanUp)); + } + + private: + VcpControllerCallbacks* callbacks; +}; + +} // namespace + +VcpControllerInterface* btif_vcp_get_controller_interface() { + LOG(INFO) << __func__; + if (!VcpControllerInstance) + VcpControllerInstance.reset(new VcpControllerInterfaceImpl()); + + return VcpControllerInstance.get(); +} + diff --git a/le_audio/system/bt/btif/src/btif_vmcp.cc b/le_audio/system/bt/btif/src/btif_vmcp.cc new file mode 100644 index 00000000000..ca1c09762ea --- /dev/null +++ b/le_audio/system/bt/btif/src/btif_vmcp.cc @@ -0,0 +1,762 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + * + ******************************************************************************/ + +#include +#include +#include +#include +#include + +#include "bt_types.h" +#include "bt_trace.h" + +#include +#include "btif_bap_codec_utils.h" +#include "btif_vmcp.h" +#include "btif_api.h" +#include + +using namespace std; + +unsigned long voice_codec_count, media_codec_count, qos_settings_count; + +// holds the value of current profile being parsed from xml +uint8_t current_profile = 1; + +std::vectorvmcp_voice_codec; +std::vectorvmcp_media_codec; +std::vectorvmcp_qos_low_lat_voice; +std::vectorvmcp_qos_low_lat_media; +std::vectorvmcp_qos_high_rel_media; + +std::vectorbap_voice_codec; +std::vectorbap_media_codec; +std::vectorbap_qos_low_lat_voice; +std::vectorbap_qos_low_lat_media; +std::vectorbap_qos_high_rel_media; + +std::vectorgcp_voice_codec; +std::vectorgcp_media_codec; +std::vectorgcp_qos_low_lat_voice; +std::vectorgcp_qos_low_lat_media; + +std::vectorwmcp_media_codec; +std::vectorwmcp_qos_high_rel_media; + +vector get_all_codec_configs(uint8_t profile, uint8_t context) +{ + vector ret_config; + CodecConfig temp_config; + vector *vptr = NULL; + + if (profile == VMCP) { + if (context == VOICE_CONTEXT) { + vptr = &vmcp_voice_codec; + } + else if(context == MEDIA_CONTEXT) { + vptr = &vmcp_media_codec; + } else { + // if no valid context is provided, use voice context + vptr = &vmcp_voice_codec; + } + } else if (profile == BAP) { + if (context == VOICE_CONTEXT) { + vptr = &bap_voice_codec; + } + else if(context == MEDIA_CONTEXT) { + vptr = &bap_media_codec; + } else { + // if no valid context is provided, use voice context + vptr = &bap_voice_codec; + } + } else if (profile == GCP) { + if (context == VOICE_CONTEXT) { + vptr = &gcp_voice_codec; + } + else if(context == MEDIA_CONTEXT) { + vptr = &gcp_media_codec; + } else { + // if no valid context is provided, use media context + vptr = &gcp_media_codec; + } + } else if (profile == WMCP) { + if(context == MEDIA_CONTEXT) { + vptr = &wmcp_media_codec; + } else { + // if no valid context is provided, use media context + vptr = &wmcp_media_codec; + } + } + + if (!vptr){ + return { }; + } + + for (uint8_t i = 0; i < (uint8_t)vptr->size(); i++) { + memset(&temp_config, 0, sizeof(CodecConfig)); + + temp_config.codec_type = CodecIndex::CODEC_INDEX_SOURCE_LC3; + + switch (vptr->at(i).freq_in_hz) + { + case SAMPLE_RATE_8000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_8000; + break; + case SAMPLE_RATE_16000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_16000; + break; + case SAMPLE_RATE_24000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_24000; + break; + case SAMPLE_RATE_32000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_32000; + break; + case SAMPLE_RATE_44100: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_44100; + break; + case SAMPLE_RATE_48000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_48000; + break; + default: + break; + } + + if (vptr->at(i).frame_dur_msecs == FRM_DURATION_7_5_MS) + UpdateFrameDuration(&temp_config, static_cast(CodecFrameDuration::FRAME_DUR_7_5)); + else if (vptr->at(i).frame_dur_msecs == FRM_DURATION_10_MS) + UpdateFrameDuration(&temp_config, static_cast(CodecFrameDuration::FRAME_DUR_10)); + + UpdateOctsPerFrame(&temp_config, vptr->at(i).oct_per_codec_frm); + + ret_config.push_back(temp_config); + } + + return ret_config; +} + +vector get_preferred_codec_configs(uint8_t profile, uint8_t context) +{ + vector ret_config; + CodecConfig temp_config; + vector *vptr = NULL; + + if (profile == VMCP) { + if (context == VOICE_CONTEXT) { + vptr = &vmcp_voice_codec; + } + else if(context == MEDIA_CONTEXT) { + vptr = &vmcp_media_codec; + } else { + // if no valid context is provided, use voice context + vptr = &vmcp_voice_codec; + } + } else if (profile == BAP) { + if (context == VOICE_CONTEXT) { + vptr = &bap_voice_codec; + } + else if(context == MEDIA_CONTEXT) { + vptr = &bap_media_codec; + } else { + // if no valid context is provided, use voice context + vptr = &bap_voice_codec; + } + } else if (profile == GCP) { + if (context == VOICE_CONTEXT) { + vptr = &gcp_voice_codec; + } + else if(context == MEDIA_CONTEXT) { + vptr = &gcp_media_codec; + } else { + // if no valid context is provided, use media context + vptr = &gcp_media_codec; + } + } else if (profile == WMCP) { + if(context == MEDIA_CONTEXT) { + vptr = &wmcp_media_codec; + } else { + // if no valid context is provided, use media context + vptr = &wmcp_media_codec; + } + } + + if (!vptr) { + return {}; + } + + for (uint8_t i = 0; i < (uint8_t)vptr->size(); i++) { + if (vptr->at(i).mandatory == 1) { + memset(&temp_config, 0, sizeof(CodecConfig)); + + temp_config.codec_type = CodecIndex::CODEC_INDEX_SOURCE_LC3; + + switch (vptr->at(i).freq_in_hz) + { + case SAMPLE_RATE_8000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_8000; + break; + case SAMPLE_RATE_16000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_16000; + break; + case SAMPLE_RATE_24000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_24000; + break; + case SAMPLE_RATE_32000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_32000; + break; + case SAMPLE_RATE_44100: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_44100; + break; + case SAMPLE_RATE_48000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_48000; + break; + default: + break; + } + + if (vptr->at(i).frame_dur_msecs == FRM_DURATION_7_5_MS) + UpdateFrameDuration(&temp_config, static_cast(CodecFrameDuration::FRAME_DUR_7_5)); + else if (vptr->at(i).frame_dur_msecs == FRM_DURATION_10_MS) + UpdateFrameDuration(&temp_config, static_cast(CodecFrameDuration::FRAME_DUR_10)); + + UpdateOctsPerFrame(&temp_config, vptr->at(i).oct_per_codec_frm); + + ret_config.push_back(temp_config); + } + } + + return ret_config; +} + +vector get_all_qos_params(uint8_t profile, uint8_t context) +{ + vector ret_config; + QoSConfig temp_config; + vector *vptr = NULL; + + if (profile == VMCP) { + if (context == VOICE_CONTEXT) + vptr = &vmcp_qos_low_lat_voice; + else if (context == MEDIA_LL_CONTEXT) + vptr = &vmcp_qos_low_lat_media; + else if (context == MEDIA_HR_CONTEXT) + vptr = &vmcp_qos_high_rel_media; + } else if (profile == BAP) { + if (context == VOICE_CONTEXT) + vptr = &bap_qos_low_lat_voice; + else if (context == MEDIA_LL_CONTEXT) + vptr = &bap_qos_low_lat_media; + else if (context == MEDIA_HR_CONTEXT) + vptr = &bap_qos_high_rel_media; + } else if (profile == GCP) { + if (context == VOICE_CONTEXT) + vptr = &gcp_qos_low_lat_voice; + else if (context == MEDIA_LL_CONTEXT) + vptr = &gcp_qos_low_lat_media; + } else if (profile == WMCP) { + if (context == MEDIA_HR_CONTEXT) + vptr = &wmcp_qos_high_rel_media; + } + + if (!vptr) { + return {}; + } + + for (uint8_t i = 0; i < (uint8_t)vptr->size(); i++) { + memset(&temp_config, 0, sizeof(QoSConfig)); + + switch (vptr->at(i).freq_in_hz) + { + case SAMPLE_RATE_8000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_8000; + break; + case SAMPLE_RATE_16000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_16000; + break; + case SAMPLE_RATE_24000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_24000; + break; + case SAMPLE_RATE_32000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_32000; + break; + case SAMPLE_RATE_44100: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_44100; + break; + case SAMPLE_RATE_48000: + temp_config.sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_48000; + break; + default: + break; + } + + temp_config.sdu_int_micro_secs = vptr->at(i).sdu_int_micro_secs; + temp_config.framing = vptr->at(i).framing; + temp_config.max_sdu_size = vptr->at(i).max_sdu_size; + temp_config.retrans_num = vptr->at(i).retrans_num; + temp_config.max_trans_lat = vptr->at(i).max_trans_lat; + temp_config.presentation_delay = vptr->at(i).presentation_delay; + temp_config.mandatory = vptr->at(i).mandatory; + + ret_config.push_back(temp_config); + } + + return ret_config; +} + +vector get_qos_params_for_codec(uint8_t profile, uint8_t context, CodecSampleRate freq, uint8_t frame_dur, uint8_t octets) +{ + vector ret_config; + QoSConfig temp_config; + vector *vptr = NULL; + uint32_t frame_dur_micro_sec = 0; + uint32_t local_freq = 0; + + if (frame_dur == static_cast(CodecFrameDuration::FRAME_DUR_7_5)) + frame_dur_micro_sec = FRM_DURATION_7_5_MS * 1000; + else if (frame_dur == static_cast(CodecFrameDuration::FRAME_DUR_10)) + frame_dur_micro_sec = FRM_DURATION_10_MS * 1000; + + switch (freq) + { + case CodecSampleRate::CODEC_SAMPLE_RATE_8000: + local_freq = SAMPLE_RATE_8000; + break; + case CodecSampleRate::CODEC_SAMPLE_RATE_16000: + local_freq = SAMPLE_RATE_16000; + break; + case CodecSampleRate::CODEC_SAMPLE_RATE_24000: + local_freq = SAMPLE_RATE_24000; + break; + case CodecSampleRate::CODEC_SAMPLE_RATE_32000: + local_freq = SAMPLE_RATE_32000; + break; + case CodecSampleRate::CODEC_SAMPLE_RATE_44100: + local_freq = SAMPLE_RATE_44100; + break; + case CodecSampleRate::CODEC_SAMPLE_RATE_48000: + local_freq = SAMPLE_RATE_48000; + break; + default: + break; + } + + if (profile == VMCP) { + if (context == VOICE_CONTEXT) + vptr = &vmcp_qos_low_lat_voice; + else if (context == MEDIA_LL_CONTEXT) + vptr = &vmcp_qos_low_lat_media; + else if (context == MEDIA_HR_CONTEXT) + vptr = &vmcp_qos_high_rel_media; + } else if (profile == BAP) { + if (context == VOICE_CONTEXT) + vptr = &bap_qos_low_lat_voice; + else if (context == MEDIA_LL_CONTEXT) { + BTIF_TRACE_IMP("%s: filling BAP LL vptr", __func__); + vptr = &bap_qos_low_lat_media; + } else if (context == MEDIA_HR_CONTEXT) { + BTIF_TRACE_IMP("%s: filling BAP HR vptr", __func__); + vptr = &bap_qos_high_rel_media; + } + } else if (profile == GCP) { + if (context == VOICE_CONTEXT) + vptr = &gcp_qos_low_lat_voice; + else if (context == MEDIA_LL_CONTEXT) + vptr = &gcp_qos_low_lat_media; + } else if (profile == WMCP) { + if (context == MEDIA_HR_CONTEXT) { + BTIF_TRACE_IMP("%s: filling WMCP HR vptr", __func__); + vptr = &wmcp_qos_high_rel_media; + } + } + + if (!vptr) { + return { }; + } + + BTIF_TRACE_IMP("%s: vptr size: %d", __func__, (uint8_t)vptr->size()); + BTIF_TRACE_IMP("%s: local_freq: %d, frame_dur_micro_sec: %d, octets: %d", + __func__, local_freq, frame_dur_micro_sec, octets); + + for (uint8_t i = 0; i < (uint8_t)vptr->size(); i++) { + BTIF_TRACE_IMP("%s: freq_in_hz: %d, sdu_int_micro_secs: %d, max_sdu_size: %d", + __func__, vptr->at(i).freq_in_hz, vptr->at(i).sdu_int_micro_secs, + vptr->at(i).max_sdu_size); + if (vptr->at(i).freq_in_hz == local_freq && + vptr->at(i).sdu_int_micro_secs == frame_dur_micro_sec && + vptr->at(i).max_sdu_size == octets) { + BTIF_TRACE_IMP("%s: Local and vptr matched.", __func__); + memset(&temp_config, 0, sizeof(QoSConfig)); + + temp_config.sample_rate = freq; + temp_config.sdu_int_micro_secs = vptr->at(i).sdu_int_micro_secs; + temp_config.framing = vptr->at(i).framing; + temp_config.max_sdu_size = vptr->at(i).max_sdu_size; + temp_config.retrans_num = vptr->at(i).retrans_num; + temp_config.max_trans_lat = vptr->at(i).max_trans_lat; + temp_config.presentation_delay = vptr->at(i).presentation_delay; + temp_config.mandatory = vptr->at(i).mandatory; + + ret_config.push_back(temp_config); + } + } + + return ret_config; +} + +bool is_leaf(xmlNode *node) +{ + xmlNode *child = node->children; + while(child) + { + if(child->type == XML_ELEMENT_NODE) + return false; + + child = child->next; + } + + return true; +} + +void parseCodecConfigs(xmlNode *input_node, int context) +{ + stack profile_node_stack; + unsigned int TempCodecCount = 0; + unsigned int TempFieldsCount = 0; + xmlNode *FirstChild = xmlFirstElementChild(input_node); + unsigned long CodecFields = xmlChildElementCount(FirstChild); + codec_config temp_codec_config; + memset(&temp_codec_config, 0, sizeof(codec_config)); + + + BTIF_TRACE_IMP("codec Fields count is %ld \n", CodecFields); + for (xmlNode *node = input_node->children; node != NULL || !profile_node_stack.empty(); node = node->children) + { + if (node == NULL) + { + node = profile_node_stack.top(); + profile_node_stack.pop(); + } + + if(node) + { + if(node->type == XML_ELEMENT_NODE) + { + if((is_leaf(node))) + { + string content = (const char*)(xmlNodeGetContent(node)); + if (content[0] == '\0') { + return; + } + if(!xmlStrcmp(node->name,(const xmlChar*)"SamplingFrequencyInHz")) + { + temp_codec_config.freq_in_hz = atoi(content.c_str()); + TempFieldsCount++; + } + else if(!xmlStrcmp(node->name, (const xmlChar*)"FrameDurationInMicroSecs")) + { + temp_codec_config.frame_dur_msecs = (float)atoi(content.c_str())/1000; + TempFieldsCount++; + } + else if(!xmlStrcmp(node->name, (const xmlChar*)"OctetsPerCodecFrame")) + { + temp_codec_config.oct_per_codec_frm = atoi(content.c_str()); + TempFieldsCount++; + } + else if(!xmlStrcmp(node->name, (const xmlChar*)"Mandatory")) + { + temp_codec_config.mandatory = atoi(content.c_str()); + TempFieldsCount++; + } + } + if(TempFieldsCount == CodecFields) + { + if (current_profile == VMCP) { + if (context == VOICE_CONTEXT) { + vmcp_voice_codec.push_back(temp_codec_config); + } else if (context == MEDIA_CONTEXT) { + vmcp_media_codec.push_back(temp_codec_config); + } + } else if (current_profile == BAP) { + if (context == VOICE_CONTEXT) { + bap_voice_codec.push_back(temp_codec_config); + } else if (context == MEDIA_CONTEXT) { + bap_media_codec.push_back(temp_codec_config); + } + } else if (current_profile == GCP) { + if (context == VOICE_CONTEXT) { + gcp_voice_codec.push_back(temp_codec_config); + } else if (context == MEDIA_CONTEXT) { + gcp_media_codec.push_back(temp_codec_config); + } + } else if (current_profile == WMCP) { + if (context == MEDIA_CONTEXT) { + BTIF_TRACE_IMP("%s: parsed codec config for wmcp", __func__); + wmcp_media_codec.push_back(temp_codec_config); + } + } + + TempFieldsCount = 0; + TempCodecCount++; + } + } + + if(node->next != NULL) + { + profile_node_stack.push(node->next); + node = node->next; + } + } // end of if (node) + } // end of for + if(context == VOICE_CONTEXT && TempCodecCount == voice_codec_count) + { + if (current_profile < GCP) { + BTIF_TRACE_IMP("All %ld CG codecs are parsed successfully\n", voice_codec_count); + } else { + BTIF_TRACE_IMP("All %ld GAT Rx codecs are parsed successfully\n", voice_codec_count); + } + } + else if(context == MEDIA_CONTEXT && TempCodecCount == media_codec_count) + { + if (current_profile < GCP) { + BTIF_TRACE_IMP("All %ld UMS codecs are parsed successfully\n", media_codec_count); + } else if (current_profile == GCP) { + BTIF_TRACE_IMP("All %ld GAT Tx codecs are parsed successfully\n", media_codec_count); + } else if (current_profile == WMCP) { + BTIF_TRACE_IMP("All %ld WM Rx codecs are parsed successfully\n", media_codec_count); + } + } +} + +void parseQoSConfigs(xmlNode *QoSInputNode, int context) +{ + stack QoS_Stack; + unsigned int TempQoSCodecCount = 0; + unsigned int TempQoSFieldsCount = 0; + xmlNode * FirstChild = xmlFirstElementChild(QoSInputNode); + unsigned long QoSCodecFields = xmlChildElementCount(FirstChild); + qos_config temp_qos_config ; + memset(&temp_qos_config, 0, sizeof(qos_config)); + + BTIF_TRACE_IMP("QoS Fields count %ld \n", QoSCodecFields); + for (xmlNode *node = QoSInputNode->children; node != NULL || !QoS_Stack.empty(); node = node->children) + { + if (node == NULL) + { + node = QoS_Stack.top(); + QoS_Stack.pop(); + } + if(node) + { + if(node->type == XML_ELEMENT_NODE) + { + if(is_leaf(node)) + { + string content = (const char*)(xmlNodeGetContent(node)); + if (content[0] == '\0') { + return; + } + if(!xmlStrcmp(node->name,(const xmlChar*)"SamplingFrequencyInHz")) + { + temp_qos_config.freq_in_hz = atoi(content.c_str()); + TempQoSFieldsCount++; + } + else if(!xmlStrcmp(node->name, (const xmlChar*)"SDUIntervalInMicroSecs")) + { + temp_qos_config.sdu_int_micro_secs = atoi(content.c_str()); + TempQoSFieldsCount++; + } + else if(!xmlStrcmp(node->name, (const xmlChar*)"Framing")) + { + temp_qos_config.framing = atoi(content.c_str()); + TempQoSFieldsCount++; + } + else if(!xmlStrcmp(node->name, (const xmlChar*)"MaxSDUSize")) + { + temp_qos_config.max_sdu_size = atoi(content.c_str()); + TempQoSFieldsCount++; + } + else if(!xmlStrcmp(node->name, (const xmlChar*)"RetransmissionNumber")) + { + temp_qos_config.retrans_num = atoi(content.c_str()); + TempQoSFieldsCount++; + } + else if(!xmlStrcmp(node->name, (const xmlChar*)"MaxTransportLatency")) + { + temp_qos_config.max_trans_lat = atoi(content.c_str()); + TempQoSFieldsCount++; + } + else if(!xmlStrcmp(node->name, (const xmlChar*)"PresentationDelay")) + { + temp_qos_config.presentation_delay = atoi(content.c_str()); + TempQoSFieldsCount++; + } + else if(!xmlStrcmp(node->name, (const xmlChar*)"Mandatory")) + { + temp_qos_config.mandatory = atoi(content.c_str()); + TempQoSFieldsCount++; + } + } + if(TempQoSFieldsCount == QoSCodecFields) + { + if(current_profile == VMCP) { + if (context == VOICE_CONTEXT) { + vmcp_qos_low_lat_voice.push_back(temp_qos_config); + } else if (context == MEDIA_LL_CONTEXT) { + vmcp_qos_low_lat_media.push_back(temp_qos_config); + } else if (context == MEDIA_HR_CONTEXT) { + vmcp_qos_high_rel_media.push_back(temp_qos_config); + } + } else if(current_profile == BAP) { + if (context == VOICE_CONTEXT) { + bap_qos_low_lat_voice.push_back(temp_qos_config); + } else if (context == MEDIA_LL_CONTEXT) { + bap_qos_low_lat_media.push_back(temp_qos_config); + } else if (context == MEDIA_HR_CONTEXT) { + bap_qos_high_rel_media.push_back(temp_qos_config); + } + } else if(current_profile == GCP) { + if (context == VOICE_CONTEXT) { + gcp_qos_low_lat_voice.push_back(temp_qos_config); + } else if (context == MEDIA_LL_CONTEXT) { + gcp_qos_low_lat_media.push_back(temp_qos_config); + } + } else if(current_profile == WMCP) { + if (context == MEDIA_HR_CONTEXT) { + BTIF_TRACE_IMP("%s: parsed qos config for wmcp", __func__); + wmcp_qos_high_rel_media.push_back(temp_qos_config); + } + } + + TempQoSFieldsCount = 0; + TempQoSCodecCount++; + } + } + if(node->next != NULL) + { + QoS_Stack.push(node->next); + node = node->next; + } + + } + } + if(TempQoSCodecCount == qos_settings_count) + { + if(context == VOICE_CONTEXT) + { + if (current_profile < GCP) { + BTIF_TRACE_IMP("All %ld CG Qos Config are parsed successfully\n", qos_settings_count); + } else { + BTIF_TRACE_IMP("All %ld GAT Rx Qos Config are parsed successfully\n", qos_settings_count); + } + } + else if(context == MEDIA_CONTEXT) + { + if (current_profile < GCP) { + BTIF_TRACE_IMP("All %ld UMS Qos Config are parsed successfully\n", qos_settings_count); + } else if (current_profile == GCP) { + BTIF_TRACE_IMP("All %ld GAT Tx Qos Config are parsed successfully\n", qos_settings_count); + } else if (current_profile == WMCP) { + BTIF_TRACE_IMP("All %ld WM Rx Qos Config are parsed successfully\n", qos_settings_count); + } + } + } +} + +void parse_xml(xmlNode *inputNode) +{ + stack S; + for (xmlNode *node = inputNode; node != NULL || !S.empty(); node = node->children) + { + if (node == NULL) + { + node = S.top(); + S.pop(); + } + if (node) + { + if (node->type == XML_ELEMENT_NODE) + { + if (!(is_leaf(node))) + { + string content = (const char *) (xmlNodeGetContent (node)); + if (content[0] == '\0') { + return; + } + if (!xmlStrcmp (node->name, (const xmlChar *) "VMCP")) + { + BTIF_TRACE_IMP("VMCP configs being parsed\n"); + current_profile = VMCP; + } + if (!xmlStrcmp (node->name, (const xmlChar *) "BAP")) + { + BTIF_TRACE_IMP("BAP configs being parsed\n"); + current_profile = BAP; + } + if (!xmlStrcmp (node->name, (const xmlChar *) "GCP")) + { + BTIF_TRACE_IMP("GCP configs being parsed\n"); + current_profile = GCP; + } + if (!xmlStrcmp (node->name, (const xmlChar *) "WMCP")) + { + BTIF_TRACE_IMP("WMCP configs being parsed\n"); + current_profile = WMCP; + } + + if (!xmlStrcmp (node->name, (const xmlChar *) "CodecCapabilitiesForVoice")) + { + voice_codec_count = xmlChildElementCount(node); + parseCodecConfigs(node, VOICE_CONTEXT); + } + else if (!xmlStrcmp (node->name, (const xmlChar *) "CodecCapabilitiesForMedia")) + { + media_codec_count = xmlChildElementCount(node); + parseCodecConfigs(node, MEDIA_CONTEXT); + } + else if (!xmlStrcmp (node->name, (const xmlChar *) "QosSettingsForLowLatencyVoice")) + { + qos_settings_count = xmlChildElementCount(node); + parseQoSConfigs(node, VOICE_CONTEXT); + } + else if (!xmlStrcmp (node->name, (const xmlChar *) "QosSettingsForLowLatencyMedia")) + { + qos_settings_count = xmlChildElementCount(node); + parseQoSConfigs(node, MEDIA_LL_CONTEXT); + } + else if (!xmlStrcmp (node->name, (const xmlChar *) "QosSettingsForHighReliabilityMedia")) + { + qos_settings_count = xmlChildElementCount(node); + parseQoSConfigs(node, MEDIA_HR_CONTEXT); + } + } + } + if(node->next != NULL) + { + S.push(node -> next); + } + } + } +} + +void btif_vmcp_init() { + xmlDoc *doc = NULL; + xmlNode *root_element = NULL; + + doc = xmlReadFile(LEAUDIO_CONFIG_PATH, NULL, 0); + if (doc == NULL) { + BTIF_TRACE_ERROR("Could not parse the XML file"); + } + + root_element = xmlDocGetRootElement(doc); + parse_xml(root_element); + xmlFreeDoc(doc); + xmlCleanupParser(); + + //Register Audio Gaming Service UUID (GCP) with Gattc + btif_register_uuid_srvc_disc(bluetooth::Uuid::FromString("12994b7e-6d47-4215-8c9e-aae9a1095ba3")); + + //Register Wireless Microphone Configuration Service UUID (WMCP) with Gattc + btif_register_uuid_srvc_disc(bluetooth::Uuid::FromString("2587db3c-ce70-4fc9-935f-777ab4188fd7")); +} diff --git a/le_audio/system/bt/common/state_machine.h b/le_audio/system/bt/common/state_machine.h new file mode 100644 index 00000000000..62d92d2636f --- /dev/null +++ b/le_audio/system/bt/common/state_machine.h @@ -0,0 +1,214 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * 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. + */ + +#pragma once + +#include +#include + +#include + +namespace bluetooth { + +namespace common { + +/** + * State machine used by Bluetooth native stack. + */ +class StateMachine { + public: + enum { kStateInvalid = -1 }; + + /** + * A class to represent the state in the State Machine. + */ + class State { + friend class StateMachine; + + public: + /** + * Constructor. + * + * @param sm the State Machine to use + * @param state_id the unique State ID. It should be a non-negative number. + */ + State(StateMachine& sm, int state_id) : sm_(sm), state_id_(state_id) {} + + virtual ~State() = default; + + /** + * Process an event. + * TODO: The arguments are wrong - used for backward compatibility. + * Will be replaced later. + * + * @param event the event type + * @param p_data the event data + * @return true if the processing was completed, otherwise false + */ + virtual bool ProcessEvent(uint32_t event, void* p_data) = 0; + + /** + * Get the State ID. + * + * @return the State ID + */ + int StateId() const { return state_id_; } + + protected: + /** + * Called when a state is entered. + */ + virtual void OnEnter() {} + + /** + * Called when a state is exited. + */ + virtual void OnExit() {} + + /** + * Transition the State Machine to a new state. + * + * @param dest_state_id the state ID to transition to. It must be one + * of the unique state IDs when the corresponding state was created. + */ + void TransitionTo(int dest_state_id) { sm_.TransitionTo(dest_state_id); } + + /** + * Transition the State Machine to a new state. + * + * @param dest_state the state to transition to. It cannot be nullptr. + */ + void TransitionTo(StateMachine::State* dest_state) { + sm_.TransitionTo(dest_state); + } + + private: + StateMachine& sm_; + int state_id_; + }; + + StateMachine() + : initial_state_(nullptr), + previous_state_(nullptr), + current_state_(nullptr) {} + ~StateMachine() { + for (auto& kv : states_) delete kv.second; + } + + /** + * Start the State Machine operation. + */ + void Start() { TransitionTo(initial_state_); } + + /** + * Quit the State Machine operation. + */ + void Quit() { previous_state_ = current_state_ = nullptr; } + + /** + * Get the current State ID. + * + * @return the current State ID + */ + int StateId() const { + if (current_state_ != nullptr) { + return current_state_->StateId(); + } + return kStateInvalid; + } + + /** + * Get the previous current State ID. + * + * @return the previous State ID + */ + int PreviousStateId() const { + if (previous_state_ != nullptr) { + return previous_state_->StateId(); + } + return kStateInvalid; + } + + /** + * Process an event. + * TODO: The arguments are wrong - used for backward compatibility. + * Will be replaced later. + * + * @param event the event type + * @param p_data the event data + * @return true if the processing was completed, otherwise false + */ + bool ProcessEvent(uint32_t event, void* p_data) { + if (current_state_ == nullptr) return false; + return current_state_->ProcessEvent(event, p_data); + } + + /** + * Transition the State Machine to a new state. + * + * @param dest_state_id the state ID to transition to. It must be one + * of the unique state IDs when the corresponding state was created. + */ + void TransitionTo(int dest_state_id) { + auto it = states_.find(dest_state_id); + + CHECK(it != states_.end()) << "Unknown State ID: " << dest_state_id; + State* dest_state = it->second; + TransitionTo(dest_state); + } + + /** + * Transition the State Machine to a new state. + * + * @param dest_state the state to transition to. It cannot be nullptr. + */ + void TransitionTo(StateMachine::State* dest_state) { + if (current_state_ != nullptr) { + current_state_->OnExit(); + } + previous_state_ = current_state_; + current_state_ = dest_state; + current_state_->OnEnter(); + } + + /** + * Add a state to the State Machine. + * The state machine takes ownership on the state - i.e., the state will + * be deleted by the State Machine itself. + * + * @param state the state to add + */ + void AddState(State* state) { + states_.insert(std::make_pair(state->StateId(), state)); + } + + /** + * Set the initial state of the State Machine. + * + * @param initial_state the initial state + */ + void SetInitialState(State* initial_state) { initial_state_ = initial_state; } + + private: + State* initial_state_; + State* previous_state_; + State* current_state_; + std::map states_; +}; + +} // namespace common + +} // namespace bluetooth diff --git a/le_audio/vhal/include/hardware/bluetooth_callcontrol_callbacks.h b/le_audio/vhal/include/hardware/bluetooth_callcontrol_callbacks.h new file mode 100644 index 00000000000..7137867898e --- /dev/null +++ b/le_audio/vhal/include/hardware/bluetooth_callcontrol_callbacks.h @@ -0,0 +1,62 @@ +/* + *Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2017 The Android Open Source Project + * + * 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. + */ + +#pragma once + +namespace bluetooth { +namespace call_control { + +/** + * CCS/GTBS related callbacks invoked from from the Bluetooth native stack + * All callbacks are invoked on the JNI thread + */ +class CallControllerCallbacks { + public: + virtual ~CallControllerCallbacks() = default; + /** + * Callback for notifying the CCS initialization status. + * + * @param state success if zero, failure otherwise + */ + virtual void CallControlInitializedCallback(uint8_t state + ) = 0; + /** + * Callback for connection state change. + * + * @param state one of the values from btcc_connection_state_t + * @param bd_addr remote device address + */ + virtual void ConnectionStateCallback(uint8_t state, + const RawAddress& address) = 0; + /** + * Callback for call control operations. + * + * @param op call control operation initiated from remote + * @param indicies Indicies for the call control operations + * @param count number of Indicies for the call control operations + * @uri uri for the call control operation + * @param bd_addr remote device address which initiated the call control op + */ + virtual void CallControlCallback(uint8_t op, std::vector indicies, int count, std::vector uri_data, + const RawAddress& address) = 0; +}; + +} // namespace callcontrol +} // namespace bluetooth diff --git a/le_audio/vhal/include/hardware/bluetooth_callcontrol_interface.h b/le_audio/vhal/include/hardware/bluetooth_callcontrol_interface.h new file mode 100644 index 00000000000..c2b78498995 --- /dev/null +++ b/le_audio/vhal/include/hardware/bluetooth_callcontrol_interface.h @@ -0,0 +1,152 @@ +/* + *Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2017 The Android Open Source Project + * + * 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. + */ + +#pragma once + +#include "bluetooth_callcontrol_callbacks.h" +#include + +#define BT_PROFILE_CC_ID "cc_server" + +namespace bluetooth { +namespace call_control { + +/** + * Programming interface for CCS/GTBS profiles in the Fluoride stack + * Thread-safe + */ +class CallControllerInterface { + public: + virtual ~CallControllerInterface() = default; + /** + * Initialize the CCS/GTBS Interface + * + * @param callbacks callbacks for the user of the native stack + * @param max_ccs_clients maximum number of CCS/GTBS clients + * @param inband_ringing_enabled whether inband ringtone is enabled + * @return BT_STATUS_SUCCESS on success + */ + virtual bt_status_t Init(CallControllerCallbacks* callbacks, Uuid uuid, int max_ccs_clients, + bool inband_ringing_enabled) = 0; + /** + * Updates telephony bearer name + * + * @param operator_str bearer name of provider + * @return BT_STATUS_SUCCESS on success + */ + virtual bt_status_t UpdateBearerName(uint8_t* operator_str) = 0; + + /** + * Updates the bearer Technogly type + * + * @param bearer_tech bearer technology type of provider + * @return BT_STATUS_SUCCESS on success + */ + virtual bt_status_t UpdateBearerTechnology(int bearer_tech) = 0; + + /** + * Updates telephony network bearers supported + * + * @param supportedBearers supported bearers list + * @return BT_STATUS_SUCCESS on success + */ + virtual bt_status_t UpdateSupportedBearerList(uint8_t* supportedBearers) = 0; + + /** + * Updates optional Call control operations supported + * + * @param feature bitmask value representing the optional op code supported + * @return BT_STATUS_SUCCESS on success + */ + virtual bt_status_t CallControlOptionalOpSupported(int feature) = 0; + + /** + * Update network signal strength + * + * @param signal level + * @return BT_STATUS_SUCCESS on success + */ + virtual bt_status_t UpdateSignalStatus(int signal) = 0; + + /** + * Update status flag for GTBS + * + * @param bd_addr remote device address + * @return BT_STATUS_SUCCESS on success + */ + virtual bt_status_t UpdateStatusFlags(uint8_t status_flag) = 0; + + /** + * Update Content control for GTBS/CCS + * + * @param ccid content control Id for GTBS/CCS + * @return BT_STATUS_SUCCESS on success + */ + virtual void ContentControlId(uint32_t ccid) = 0; + + /** + * Update the Call State of CCS + * + * @param len of call state infos + * @param call_state_list array of call state list, where each call state + * comprised of index, state, flags + * @return BT_STATUS_SUCCESS on success + */ + virtual bt_status_t CallState(int len, std::vector call_state_list) = 0; + + /** + * Update Incoming call URI for the given Call Index + * + * @param index index of the call + * @param uri representing the Incoming call, will be Incoming call number for telephony + * @return BT_STATUS_SUCCESS on success + */ + + virtual void UpdateIncomingCall(int index, uint8_t* uri) = 0; + /** + * Response for Call control Operation initiated + * + * @param op Operation for which response is sent + * @param index index of call for operation + * @param status status of the operation performed + * @return BT_STATUS_SUCCESS on success + */ + virtual bt_status_t CallControlResponse(uint8_t op, uint8_t index, uint32_t status, const RawAddress& address) = 0; + + /** + * Set the current active device for GTBS/CCS + * + * @param active_device_addr remote device address + */ + virtual void SetActiveDevice(const RawAddress& address, int set_id) = 0; + + /** + * Disconnect GTBS/CTS profile for remote + * @param address remote device address + */ + virtual void Disconnect(const RawAddress& address) = 0; + /** + * Closes the GTBS/CCS interface. + */ + virtual void Cleanup() = 0; +}; + +} // namespace call_control +} // namespace bluetooth diff --git a/le_audio/vhal/include/hardware/bt_acm.h b/le_audio/vhal/include/hardware/bt_acm.h new file mode 100644 index 00000000000..22a2424bf7d --- /dev/null +++ b/le_audio/vhal/include/hardware/bt_acm.h @@ -0,0 +1,112 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + *****************************************************************************/ + +#ifndef ANDROID_INCLUDE_BT_ACM_H +#define ANDROID_INCLUDE_BT_ACM_H + +#include + +#include +#include +#include + +__BEGIN_DECLS + +#define BT_PROFILE_ACM_ID "bt_acm_proflie" +using bluetooth::bap::pacs::CodecConfig; +/* Bluetooth ACM connection states */ +typedef enum { + BTACM_CONNECTION_STATE_DISCONNECTED = 0, + BTACM_CONNECTION_STATE_CONNECTING, + BTACM_CONNECTION_STATE_CONNECTED, + BTACM_CONNECTION_STATE_DISCONNECTING +} btacm_connection_state_t; + +/* Bluetooth ACM datapath states */ +typedef enum { + BTACM_AUDIO_STATE_REMOTE_SUSPEND = 0, + BTACM_AUDIO_STATE_STOPPED, + BTACM_AUDIO_STATE_STARTED, +} btacm_audio_state_t; + +/** Callback for connection state change. + * state will have one of the values from btacm_connection_state_t + */ +typedef void (*btacm_connection_state_callback)(const RawAddress& bd_addr, + btacm_connection_state_t state, + uint16_t contextType); + +/** Callback for audiopath state change. + * state will have one of the values from btacm_audio_state_t + */ +typedef void (*btacm_audio_state_callback)(const RawAddress& bd_addr, + btacm_audio_state_t state, + uint16_t contextType); + +/** Callback for audio configuration change. + * Used only for the ACM Initiator interface. + */ +typedef void (*btacm_audio_config_callback)( + const RawAddress& bd_addr, CodecConfig codec_config, + std::vector codecs_local_acmabilities, + std::vector codecs_selectable_acmabilities, + uint16_t contextType); + +/** BT-ACM Initiator callback structure. */ +typedef struct { + /** set to sizeof(btacm_initiator_callbacks_t) */ + size_t size; + btacm_connection_state_callback connection_state_cb; + btacm_audio_state_callback audio_state_cb; + btacm_audio_config_callback audio_config_cb; +} btacm_initiator_callbacks_t; + +/** Represents the standard BT-ACM Initiator interface. + */ +typedef struct { + /** set to sizeof(btacm_source_interface_t) */ + size_t size; + /** + * Register the BtAcm callbacks. + */ + bt_status_t (*init)( + btacm_initiator_callbacks_t* callbacks, int max_connected_audio_devices, + const std::vector& codec_priorities); + + /** connect to headset */ + bt_status_t (*connect)(const RawAddress& bd_addr, uint16_t context_type, + uint16_t profile_type, uint16_t preferred_context); + + /** dis-connect from headset */ + bt_status_t (*disconnect)(const RawAddress& bd_addr, uint16_t context_type); + + /** sets the connected device as active */ + bt_status_t (*set_active_device)(const RawAddress& bd_addr, + uint16_t context_type); + + /** start stream */ + bt_status_t (*start_stream)(const RawAddress& bd_addr, uint16_t context_type); + + /** stop stream */ + bt_status_t (*stop_stream)(const RawAddress& bd_addr, uint16_t context_type); + + /** configure the codecs settings preferences */ + bt_status_t (*config_codec)( + const RawAddress& bd_addr, + std::vector codec_preferences, + uint16_t context_type, uint16_t preferred_context); + + /** configure the codec based on ID*/ + bt_status_t (*change_config_codec)( + const RawAddress& bd_addr, + char* Id); + + /** Closes the interface. */ + void (*cleanup)(void); + +} btacm_initiator_interface_t; + +__END_DECLS + +#endif /* ANDROID_INCLUDE_BT_AV_H */ diff --git a/le_audio/vhal/include/hardware/bt_apm.h b/le_audio/vhal/include/hardware/bt_apm.h new file mode 100644 index 00000000000..42b2452fb60 --- /dev/null +++ b/le_audio/vhal/include/hardware/bt_apm.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + **************************************************************************/ + +#ifndef ANDROID_INCLUDE_BT_APM_H +#define ANDROID_INCLUDE_BT_APM_H + +#define BT_APM_MODULE_ID "apm" + +#include + +#include + +__BEGIN_DECLS + +/* Bluetooth APM Audio Types */ +typedef enum { + BTAPM_VOICE_AUDIO = 0, + BTAPM_MEDIA_AUDIO, + BTAPM_BROADCAST_AUDIO +} btapm_audio_type_t; + +void call_active_profile_info(const RawAddress& bd_addr, uint16_t audio_type); +int get_active_profile(const RawAddress& bd_addr, uint16_t audio_type); +typedef int (*btapm_active_profile_callback)(const RawAddress& bd_addr, uint16_t audio_type); + + +typedef struct { + size_t size; + btapm_active_profile_callback active_profile_cb; +}btapm_initiator_callbacks_t; + + + +/** APM interface + */ +typedef struct { + + /** set to sizeof(bt_apm_interface_t) */ + size_t size; + /** + * Register the BtAv callbacks. + */ + bt_status_t (*init)(btapm_initiator_callbacks_t* callbacks); + + /** updates new active device to stack */ + bt_status_t (*active_device_change)(const RawAddress& bd_addr, uint16_t profile, uint16_t audio_type); + + /** send content control id to stack */ + bt_status_t (*set_content_control_id)(uint16_t content_control_id, uint16_t audio_type); + + /** Closes the interface. */ + void (*cleanup)( void ); + +}bt_apm_interface_t; + +__END_DECLS + +#endif /* ANDROID_INCLUDE_BT_APM_H */ + diff --git a/le_audio/vhal/include/hardware/bt_ascs_client.h b/le_audio/vhal/include/hardware/bt_ascs_client.h new file mode 100644 index 00000000000..cd9341b3d69 --- /dev/null +++ b/le_audio/vhal/include/hardware/bt_ascs_client.h @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + * Copyright 2018 The Android Open Source Project + * + * 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. + */ + +#ifndef ANDROID_INCLUDE_BT_ASCS_CLIENT_H +#define ANDROID_INCLUDE_BT_ASCS_CLIENT_H + +#include + +namespace bluetooth { +namespace bap { +namespace ascs { + +#define BT_PROFILE_ASCS_CLIENT_ID "bt_ascs_client" + +constexpr uint8_t ASE_DIRECTION_SINK = 0x01; +constexpr uint8_t ASE_DIRECTION_SOURCE = 0x02; + +constexpr uint32_t CONTEXT_TYPE_CONVERSATIONAL = 0x0002; +constexpr uint32_t CONTEXT_TYPE_MEDIA = 0x0004; + +constexpr uint8_t ASE_STATE_IDLE = 0x00; +constexpr uint8_t ASE_STATE_CODEC_CONFIGURED = 0x01; +constexpr uint8_t ASE_STATE_QOS_CONFIGURED = 0x02; +constexpr uint8_t ASE_STATE_ENABLING = 0x03; +constexpr uint8_t ASE_STATE_STREAMING = 0x04; +constexpr uint8_t ASE_STATE_DISABLING = 0x05; +constexpr uint8_t ASE_STATE_RELEASING = 0x06; +constexpr uint8_t ASE_STATE_INVALID = 0xFF; + +typedef uint8_t sdu_interval_t[3]; +typedef uint8_t presentation_delay_t[3]; +typedef uint8_t codec_type_t[5]; + + +enum class ASCSEvent { + ASCS_DISCOVERY_CMPL_EVT = 0, + ASCS_DEV_CONNECTED, + ASCS_DEV_DISCONNECTED, + ASCS_ASE_STATE, +}; + +struct AudioContext { + uint8_t length; + uint8_t type; + uint16_t value; +}; + +enum class AseState { + IDLE = 0, + CODEC_CONFIGURED, + QOS_CONFIGURED, + ENABLING, + STREAMING, + DISABLING, + RELEASING, +}; + +enum class AseOpId { + CODEC_CONFIG = 0x01, + QOS_CONFIG, + ENABLE, + START_READY, + DISABLE, + STOP_READY, + UPDATE_META_DATA, + RELEASE +}; + +enum class GattState { + DISCONNECTED = 0, + CONNECTING, + CONNECTED, + DISCONNECTING +}; + +struct AseCodecConfigOp { + uint8_t ase_id; + uint8_t tgt_latency; + uint8_t tgt_phy; + codec_type_t codec_id; + uint8_t codec_params_len; + std::vector codec_params; +} __attribute__((packed)); + +struct AseQosConfigOp { + uint8_t ase_id; + uint8_t cig_id; + uint8_t cis_id; + sdu_interval_t sdu_interval; + uint8_t framing; + uint8_t phy; + uint16_t max_sdu_size; + uint8_t retrans_number; + uint16_t trans_latency; + presentation_delay_t present_delay; +} __attribute__((packed)); + +struct AseEnableOp { + uint8_t ase_id; + uint8_t meta_data_len; + std::vector meta_data; +} __attribute__((packed)); + +struct AseDisableOp { + uint8_t ase_id; +} __attribute__((packed)); + +struct AseStartReadyOp { + uint8_t ase_id; +} __attribute__((packed)); + +struct AseStopReadyOp { + uint8_t ase_id; +} __attribute__((packed)); + +struct AseReleaseOp { + uint8_t ase_id; +} __attribute__((packed)); + +struct AseUpdateMetadataOp { + uint8_t ase_id; + uint8_t meta_data_len; + std::vector meta_data; +} __attribute__((packed)); + +struct AseCodecConfigParams { + uint8_t framing; + uint8_t pref_phy; + uint8_t pref_rtn; + uint16_t mtl; + presentation_delay_t pd_min; + presentation_delay_t pd_max; + presentation_delay_t pref_pd_min; + presentation_delay_t pref_pd_max; + codec_type_t codec_id; + uint8_t codec_params_len; + std::vector codec_params; +} __attribute__((packed)); + +struct AseQosConfigParams { + uint8_t cig_id; + uint8_t cis_id; + sdu_interval_t sdu_interval; + uint8_t framing; + uint8_t phy; + uint16_t max_sdu_size; + uint8_t rtn; + uint16_t mtl; + presentation_delay_t pd; +} __attribute__((packed)); + +struct AseGenericParams { + uint8_t cig_id; + uint8_t cis_id; + uint8_t meta_data_len; + std::vector meta_data; +} __attribute__((packed)); + +union AseOp { + AseCodecConfigOp codec_config_op; + AseQosConfigOp qos_config_op; + AseEnableOp enable_op; + AseDisableOp disable_op; + AseStartReadyOp start_ready_op; + AseStopReadyOp stop_ready_op; + AseReleaseOp release_op; +}; + +struct AseOpStatus { + uint8_t ase_id; + uint8_t resp_code; + uint8_t reason; +}; + +struct AseParams { + uint8_t ase_id; + uint8_t ase_state; + AseCodecConfigParams codec_config_params; + AseQosConfigParams qos_config_params; + AseGenericParams generic_params; +} __attribute__((packed)); + +struct AseCpNotification { + uint8_t ase_opcode; + uint8_t num_ases; + std::vector status; +} __attribute__((packed)); + +struct Ase { + uint16_t ase_handle; + uint16_t ase_ccc_handle; + AseParams ase_params; +} __attribute__((packed)); + +struct AscsDiscoveryDb { + std::vector ase_list; + uint16_t ase_cp_handle; + uint16_t ase_cp_ccc_handle; + bool service_changed_rcvd; + bool active; +}; + +class AscsClientCallbacks { + public: + virtual ~AscsClientCallbacks() = default; + + /** Callback for ascs server registration status */ + virtual void OnAscsInitialized(int status, int client_id) = 0; + + /** Callback for ascs server connection state change */ + virtual void OnConnectionState(const RawAddress& address, + GattState state) = 0; + + /** Callback for ascs server control op failed status */ + virtual void OnAseOpFailed(const RawAddress& address, + AseOpId ase_op_id, + std::vector status) = 0; + + /** Callback for ascs ase state change */ + virtual void OnAseState(const RawAddress& address, + AseParams ase) = 0; + + /** Callback for ascs discovery results */ + virtual void OnSearchComplete(int status, const RawAddress& address, + std::vector sink_ase_list, + std::vector src_ase_list) = 0; +}; + +class AscsClientInterface { + public: + virtual ~AscsClientInterface() = default; + + /** Register the Ascs client callbacks */ + virtual void Init(AscsClientCallbacks* callbacks) = 0; + + /** Connect to ascs server */ + virtual void Connect(uint16_t client_id, const RawAddress& address) = 0; + + /** Disconnect ascs server */ + virtual void Disconnect(uint16_t client_id, const RawAddress& address) = 0; + + virtual void StartDiscovery(uint16_t client_id, + const RawAddress& address) = 0; + + virtual void GetAseState(uint16_t client_id, const RawAddress& address, + uint8_t ase_id) = 0; + + virtual void CodecConfig(uint16_t client_id, const RawAddress& address, + std::vector codec_configs); + + virtual void QosConfig(uint16_t client_id, const RawAddress& address, + std::vector qos_configs); + + virtual void Enable(uint16_t client_id, const RawAddress& address, + std::vector enable_ops); + + virtual void Disable(uint16_t client_id, const RawAddress& address, + std::vector disable_ops); + + virtual void StartReady(uint16_t client_id, const RawAddress& address, + std::vector start_ready_ops); + + virtual void StopReady(uint16_t client_id, const RawAddress& address, + std::vector stop_ready_ops); + + virtual void Release(uint16_t client_id, const RawAddress& address, + std::vector release_ops); + + virtual void UpdateStream(uint16_t client_id, const RawAddress& address, + std::vector metadata_ops); + + /** Closes the interface. */ + virtual void Cleanup(uint16_t client_id) = 0; +}; + +} // namespace ascs +} // namespace bap +} // namespace bluetooth + +#endif /* ANDROID_INCLUDE_BT_CLIENT_H */ diff --git a/le_audio/vhal/include/hardware/bt_bap_ba.h b/le_audio/vhal/include/hardware/bt_bap_ba.h new file mode 100644 index 00000000000..951eee35461 --- /dev/null +++ b/le_audio/vhal/include/hardware/bt_bap_ba.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#ifndef ANDROID_INCLUDE_BT_BAP_BA_H +#define ANDROID_INCLUDE_BT_BAP_BA_H + +#include +#include "hardware/bt_av.h" + +__BEGIN_DECLS + +#define BT_PROFILE_BAP_BROADCAST_ID "bap_broadcast" + +/* Bluetooth BAP BROADCAST states */ +typedef enum { + BTBAP_BROADCAST_STATE_IDLE = 0, //Idle + BTBAP_BROADCAST_STATE_CONFIGURED, //Configured + BTBAP_BROADCAST_STATE_STREAMING, //Streaming +} btbap_broadcast_state_t; + +/* Bluetooth BAP BROADCAST Audio states */ +typedef enum { + BTBAP_BROADCAST_AUDIO_STATE_STOPPED = 0, + BTBAP_BROADCAST__AUDIO_STATE_STARTED, +} btbap_broadcast_audio_state_t; + +/** Callback for broadcast state change. + * state will have one of the values from btbap_broadcast_state_t + */ +typedef void (* bap_broadcast_state_callback)(int adv_id, + btbap_broadcast_state_t state); + +/** Callback for audiopath state change. + * state will have one of the values from btbap_broadcast_audio_state_t + */ +typedef void (* bap_broadcast_audio_state_callback)(int adv_id, + btbap_broadcast_audio_state_t state); + +/** Callback for audio configuration change. + */ +typedef void (* bap_broadcast_audio_config_callback)(int adv_id, + btav_a2dp_codec_config_t codec_config, + std::vector codec_capabilities); + +/** Callback for Iso datapath setup or removed. + */ +//typedef void (* bap_broadcast_iso_datapath_callback) (int big_handle, int enabled); + +/** Callback for encryption key generation notification + */ +typedef void (* bap_broadcast_enckey_callback) (std::string key); + +/** Callback to create/terminate BIG + */ + +typedef void (* bap_broadcast_setup_big_callback) (int enable, int adv_id, int big_handle, + int num_bises, std::vector bis_handles); + +typedef void (* bap_broadcast_bid_callback) (std::vector broadcast_id); + +/** BT-BAP-BROADCAST callback structure. */ +typedef struct { + /** set to sizeof(btbap_broadcast_callbacks_t) */ + size_t size; + bap_broadcast_state_callback broadcast_state_cb; + bap_broadcast_audio_state_callback audio_state_cb; + bap_broadcast_audio_config_callback audio_config_cb; + //bap_broadcast_iso_datapath_callback iso_datapath_cb; + bap_broadcast_enckey_callback enc_key_cb; + bap_broadcast_setup_big_callback create_big_cb; + bap_broadcast_bid_callback broadcast_id_cb; +} btbap_broadcast_callbacks_t; + +typedef struct { + + /** set to sizeof(btav_source_interface_t) */ + size_t size; + /** + * Register the btbap_broadcast callbacks. + */ + bt_status_t (*init)(btbap_broadcast_callbacks_t* callbacks, + int max_broadcast, btav_a2dp_codec_config_t config, int mode); + + /** Enable broadcast with provided codec config */ + bt_status_t (*enable_broadcast)(btav_a2dp_codec_config_t config); + + /** disable broadcast to move the state machine to idle state */ + bt_status_t (*disable_broadcast)(int adv_id); + + /** sets bap broadcast as active session */ + bt_status_t (*set_broadcast_active)(bool enable, uint8_t adv_id); + + /** configure the codecs settings preferences */ + bt_status_t (*codec_config_change)(uint8_t adv_id, btav_a2dp_codec_config_t config); + + /** Disable ISO datapath */ + bt_status_t (*setup_audiopath)(bool enable, uint8_t adv_id, uint8_t big_handle, int num_bises, int* bis_handles); + + /** Get stored encryption key */ + std::string (*get_encryption_key)( void ); + + /** Set Encryption with encryption lenght provided */ + bt_status_t (*set_encryption) (bool enabled, uint8_t key_length); + + /** Closes the interface. */ + void (*cleanup)( void ); + +} btbap_broadcast_interface_t; +__END_DECLS +#endif /*ANDROID_INCLUDE_BT_BAP_BA_H*/ + diff --git a/le_audio/vhal/include/hardware/bt_bap_uclient.h b/le_audio/vhal/include/hardware/bt_bap_uclient.h new file mode 100644 index 00000000000..7f5a481fc62 --- /dev/null +++ b/le_audio/vhal/include/hardware/bt_bap_uclient.h @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * 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. + */ + +#ifndef ANDROID_INCLUDE_BT_BAP_UCLIENT_H +#define ANDROID_INCLUDE_BT_BAP_UCLIENT_H + +#include +#include +#include + + +namespace bluetooth { +namespace bap { +namespace ucast { + +#define BT_PROFILE_BAP_UCLIENT_ID "bt_bap_uclient" + +using bluetooth::bap::pacs::CodecConfig; + +constexpr uint8_t ASE_DIRECTION_SINK = 0x01 << 0; +constexpr uint8_t ASE_DIRECTION_SRC = 0x01 << 1; + +constexpr uint8_t LATENCY_LOW = 0x01; +constexpr uint8_t LATENCY_BALANCED = 0x02; +constexpr uint8_t LATENCY_HIGH = 0x03; + +// Content types +constexpr uint16_t CONTENT_TYPE_UNSPECIFIED = 0x0001; // Unspecified +constexpr uint16_t CONTENT_TYPE_CONVERSATIONAL = 0x0002; // Conversational + +// Media(music playback, radio, podcast or movie soundtrack, or tv audio) +constexpr uint16_t CONTENT_TYPE_MEDIA = 0x0004; +constexpr uint16_t CONTENT_TYPE_GAME = 0x0008; // Game Audio +constexpr uint16_t CONTENT_TYPE_INSTRUCTIONAL = 0x0010; // Instructional + +// ManMachine(with voice recognition or virtual assistants) +constexpr uint16_t CONTENT_TYPE_MAN_MACHINE = 0x0020; +constexpr uint16_t CONTENT_TYPE_LIVE = 0x0040; // Live audio + +// Sound Effects(including keyboard and touch feedback; +// menu and user interface sounds; and other system sounds) +constexpr uint16_t CONTENT_TYPE_SOUND_EFFECTS = 0x0080; + +// Notification and reminder sounds; attention-seeking audio, +//for example, in beeps signaling the arrival of a message +constexpr uint16_t CONTENT_TYPE_NOTIFICATIONS = 0x0100; +constexpr uint16_t CONTENT_TYPE_RINGTONE = 0x0200; // Ringtone +constexpr uint16_t CONTENT_TYPE_ALERT = 0x0400; // ImmediateAlert +constexpr uint16_t CONTENT_TYPE_EMERGENCY = 0x0800; // EmergencyAlert + +// Audio locations +constexpr uint32_t AUDIO_LOC_LEFT = 0x0001; +constexpr uint32_t AUDIO_LOC_RIGHT = 0x0002; +constexpr uint32_t AUDIO_LOC_CENTER = 0x0004; + +constexpr uint8_t LE_2M_PHY = 0x02; +constexpr uint8_t LE_QHS_PHY = 0x80; + +typedef uint8_t sdu_interval_t[3]; +typedef uint8_t presentation_delay_t[3]; +typedef uint8_t codec_type_t[5]; +typedef uint8_t codec_config[255]; + +struct CISConfig { + uint8_t cis_id; + uint16_t max_sdu_m_to_s; + uint16_t max_sdu_s_to_m; + uint8_t phy_m_to_s; + uint8_t phy_s_to_m; + uint8_t rtn_m_to_s; + uint8_t rtn_s_to_m; +}; + +struct CIGConfig { + uint8_t cig_id; + uint8_t cis_count; + uint8_t packing; + uint8_t framing; + uint16_t max_tport_latency_m_to_s; + uint16_t max_tport_latency_s_to_m; + sdu_interval_t sdu_interval_m_to_s; + sdu_interval_t sdu_interval_s_to_m; +}; + +struct ASCSConfig { + uint8_t cig_id; + uint8_t cis_id; + uint8_t target_latency; + bool bi_directional; + presentation_delay_t presentation_delay; +}; + +struct QosConfig { + CIGConfig cig_config; + std::vector cis_configs; // individual CIS configs + std::vector ascs_configs; +}; + +enum class AseState { + IDLE = 0, + CODEC_CONFIGURED, + QOS_CONFIGURED, + ENABLING, + STREAMING, + DISABLING, + RELEASING, +}; + +enum class StreamState { + DISCONNECTED = 0, + CONNECTING, + CONNECTED, + STARTING, + STREAMING, + STOPPING, + DISCONNECTING, + RECONFIGURING, + UPDATING +}; + +enum class DeviceState { + DISCONNECTED = 0, + CONNECTING, + CONNECTED +}; + +enum class StreamDiscReason { + REASON_NONE, + REASON_USER_DISC +}; + +struct CodecQosConfig { + CodecConfig codec_config; + QosConfig qos_config; +}; + +struct StreamType { + uint16_t type; + uint16_t audio_context; + uint8_t direction; +}; + +struct StreamConnect { + StreamType stream_type; + //CCID_List ccids; //TODO + std::vector codec_qos_config_pair; +}; + +enum class StreamReconfigType { + CODEC_CONFIG, + QOS_CONFIG +}; + +struct StreamReconfig { + StreamType stream_type; + StreamReconfigType reconf_type; + std::vector codec_qos_config_pair; +}; + +enum class StreamUpdateType { + STREAMING_CONTEXT, +}; + +struct StreamUpdate { + StreamType stream_type; + StreamUpdateType update_type; + uint16_t update_value; +}; + +struct StreamStateInfo { + StreamType stream_type; + StreamState stream_state; + StreamDiscReason reason; +}; + +struct StreamConfigInfo { + StreamType stream_type; + CodecConfig codec_config; // codec + uint32_t audio_location; // location info of remote dev for the stream + QosConfig qos_config; // current CIG, CISs configuration + std::vector codecs_selectable; // pacs codec capabilities +}; + +class UcastClientCallbacks { + public: + virtual ~UcastClientCallbacks() = default; + + virtual void OnStreamState(const RawAddress &address, + std::vector streams_state_info) = 0; + + virtual void OnStreamConfig(const RawAddress &address, + std::vector streams_config_info) = 0; + + virtual void OnStreamAvailable(const RawAddress &address, + uint16_t src_audio_contexts, + uint16_t sink_audio_contexts) = 0; +}; + +class UcastClientInterface { + public: + virtual ~UcastClientInterface() = default; + + /** Register the ucast client callbacks */ + virtual void Init(UcastClientCallbacks* callbacks) = 0; + + virtual void Connect(std::vector &address, bool is_direct, + std::vector &streams) = 0; + + virtual void Disconnect(const RawAddress& address, + std::vector &streams) = 0; + + virtual void Start(const RawAddress& address, + std::vector &streams) = 0; + + virtual void Stop(const RawAddress& address, + std::vector &streams) = 0; + + virtual void Reconfigure(const RawAddress& address, + std::vector &streams) = 0; + + virtual void UpdateStream(const RawAddress& address, + std::vector &update_streams) = 0; + + /** Closes the interface. */ + virtual void Cleanup() = 0; +}; + +UcastClientInterface* btif_bap_uclient_get_interface(); + +} // namespace ucast +} // namespace bap +} // namespace bluetooth + +#endif /* ANDROID_INCLUDE_BT_BAP_UCLIENT_H */ diff --git a/le_audio/vhal/include/hardware/bt_csip.h b/le_audio/vhal/include/hardware/bt_csip.h new file mode 100644 index 00000000000..a559c28fabc --- /dev/null +++ b/le_audio/vhal/include/hardware/bt_csip.h @@ -0,0 +1,105 @@ +/****************************************************************************** + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +#ifndef ANDROID_INCLUDE_BT_CSIP_H +#define ANDROID_INCLUDE_BT_CSIP_H + +#include +#include +#include + +__BEGIN_DECLS + +#define BT_PROFILE_CSIP_CLIENT_ID "csip_client" + +/** Callback when app has registered with CSIP Client module + */ +typedef void (* btcsip_csip_app_registered_callback)(uint8_t status, uint8_t app_id, + const bluetooth::Uuid& app_uuid); + +/** Callback when connection state is changed for CSIP Profile + */ +typedef void (* btcsip_csip_connection_state_callback)(uint8_t app_id, RawAddress& bd_addr, + uint8_t state, uint8_t status); + +/** Callback when new set has been identified on remote device + */ +typedef void (* btcsip_new_set_found_callback) (uint8_t set_id, RawAddress& bd_addr, + uint8_t size, uint8_t* sirk, + const bluetooth::Uuid& p_srvc_uuid, + bool lock_support); + +/** Callback when new set member has been identified + */ +typedef void (* btcsip_new_set_member_found_callback) (uint8_t set_id, + RawAddress& bd_addr); + +/** Callback for lock status changed event to requesting client + */ +typedef void (* btcsip_lock_state_changed_callback) (uint8_t app_id, uint8_t set_id, + uint8_t value, uint8_t status, + std::vector addr); + +/** Callback when lock is available on earlier denying set member + */ +typedef void (* btcsip_lock_available_callback) (uint8_t app_id, uint8_t set_id, + RawAddress& bd_addr); + +/** Callback when size of coordinated set has been changed + */ +typedef void (* btcsip_set_size_changed_callback) (uint8_t set_id, uint8_t size, + RawAddress& bd_addr); + +/** Callback when SIRK of coordinated set has been changed + */ +typedef void (* btcsip_set_sirk_changed_callback) (uint8_t set_id, uint8_t* sirk, + RawAddress& bd_addr); + +/** BT-CSIP callback structure. */ +typedef struct { + size_t size; + btcsip_csip_app_registered_callback app_registered_cb; + btcsip_csip_connection_state_callback conn_state_cb; + btcsip_new_set_found_callback new_set_found_cb; + btcsip_new_set_member_found_callback new_set_member_cb; + btcsip_lock_state_changed_callback lock_status_cb; + btcsip_lock_available_callback lock_available_cb; + btcsip_set_size_changed_callback size_changed_cb; + btcsip_set_sirk_changed_callback sirk_changed_cb; +} btcsip_callbacks_t; + +/** Represents the standard BT-CSIP interface. */ +typedef struct { + + /** set to sizeof(BtCsipInterface) */ + size_t size; + + /** Register the BtCsipInterface callbacks + */ + bt_status_t (*init) (btcsip_callbacks_t* callbacks); + + /** CSIP opportunistic gatt client connection*/ + bt_status_t (*connect) (uint8_t app_id, RawAddress *bd_addr); + + /** disconnect csip gatt connection */ + bt_status_t (*disconnect) (uint8_t app_id, RawAddress *bd_addr ); + + /** register app/module with CSIP profile*/ + bt_status_t (*register_csip_app) (const bluetooth::Uuid& app_uuid); + + /** unregister app/module with CSIP profile */ + bt_status_t (*unregister_csip_app) (uint8_t app_id); + + /** change lock value */ + bt_status_t (*set_lock_value) (uint8_t app_id, uint8_t set_id, uint8_t lock_value, + std::vector devices); + + /** Closes the interface. */ + void (*cleanup) (void); + +} btcsip_interface_t; +__END_DECLS + +#endif /* ANDROID_INCLUDE_BT_CSIP_H */ diff --git a/le_audio/vhal/include/hardware/bt_mcp.h b/le_audio/vhal/include/hardware/bt_mcp.h new file mode 100644 index 00000000000..64f0d170214 --- /dev/null +++ b/le_audio/vhal/include/hardware/bt_mcp.h @@ -0,0 +1,52 @@ +/****************************************************************************** + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * + *****************************************************************************/ + +#ifndef ANDROID_INCLUDE_BT_MCP_H +#define ANDROID_INCLUDE_BT_MCP_H + + + +#include +#include + +#define BT_PROFILE_MCP_ID "mcs_server" + +namespace bluetooth { +namespace mcp_server { + +class McpServerCallbacks { + public: + virtual ~McpServerCallbacks() = default; + virtual void OnConnectionStateChange(int status, const RawAddress& bd_addr) = 0; + virtual void MediaControlPointChangeReq(uint8_t state, const RawAddress& bd_addr) = 0; + virtual void TrackPositionChangeReq(int32_t position) = 0; + virtual void PlayingOrderChangeReq(uint32_t order) = 0; +}; + + +class McpServerInterface { + public: + virtual ~McpServerInterface() = default; + virtual void Init(McpServerCallbacks* callbacks, Uuid uuid) = 0; + virtual void MediaState(uint8_t state) = 0; + virtual void MediaPlayerName(uint8_t *name) = 0; + virtual void MediaControlPointOpcodeSupported(uint32_t feature) = 0; + virtual void MediaControlPoint(uint8_t value) = 0; + virtual void TrackChanged(bool status) = 0; + virtual void TrackTitle(uint8_t* title) = 0; + virtual void TrackPosition(int32_t position) = 0; + virtual void TrackDuration(int32_t duration) = 0; + virtual void PlayingOrderSupported(uint16_t order) = 0; + virtual void PlayingOrder(uint8_t value) = 0; + virtual void ContentControlId(uint8_t ccid) = 0; + virtual void SetActiveDevice(const RawAddress& address, int set_id, int profile) = 0; + virtual void DisconnectMcp(const RawAddress& address) = 0; + virtual void BondStateChange(const RawAddress& address, int state) = 0; + virtual void Cleanup(void) = 0; +}; + +} +} +#endif /* ANDROID_INCLUDE_BT_MCP_H */ diff --git a/le_audio/vhal/include/hardware/bt_pacs_client.h b/le_audio/vhal/include/hardware/bt_pacs_client.h new file mode 100644 index 00000000000..7579b728e3b --- /dev/null +++ b/le_audio/vhal/include/hardware/bt_pacs_client.h @@ -0,0 +1,183 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * 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. + */ + +#ifndef ANDROID_INCLUDE_BT_PACS_CLIENT_H +#define ANDROID_INCLUDE_BT_PACS_CLIENT_H + +#include +#include + +namespace bluetooth { +namespace bap { +namespace pacs { + +#define BT_PROFILE_PACS_CLIENT_ID "bt_pacs_client" + +enum class CodecDirection { + CODEC_DIR_SINK = 0x1 << 0, + CODEC_DIR_SRC = 0x1 << 1 +}; + +enum class CodecCapFrameDuration { + FRAME_DUR_7_5 = 0x1 << 0, + FRAME_DUR_10 = 0x1 << 1, + FRAME_DUR_7_5_PREF = 0x1 << 4, + FRAME_DUR_10_PREF = 0x1 << 5, +}; + +enum class CodecFrameDuration { + FRAME_DUR_7_5 = 0x00, + FRAME_DUR_10 = 0x01, +}; + +enum class CodecCapChnlCount { + CHNL_COUNT_ONE = 0x1 << 0, + CHNL_COUNT_TWO = 0x1 << 1, + CHNL_COUNT_THREE = 0x1 << 2, + CHNL_COUNT_FOUR = 0x1 << 3, + CHNL_COUNT_FIVE = 0x1 << 4, + CHNL_COUNT_SIX = 0x1 << 5, + CHNL_COUNT_SEVEN = 0x1 << 6, + CHNL_COUNT_EIGHT = 0x1 << 7, +}; + +enum class ConnectionState { + DISCONNECTED = 0, + CONNECTING, + CONNECTED, + DISCONNECTING +}; + +enum class CodecIndex { + CODEC_INDEX_SOURCE_MIN = 0x09, + + // Add an entry for each source codec here. + // NOTE: The values should be same as those listed in the following file: + // BluetoothCodecConfig.java + CODEC_INDEX_SOURCE_LC3 = CODEC_INDEX_SOURCE_MIN, + CODEC_INDEX_SOURCE_MAX, + CODEC_INDEX_MIN = CODEC_INDEX_SOURCE_MIN, + CODEC_INDEX_MAX = CODEC_INDEX_SOURCE_MAX, +}; + +enum class CodecPriority { + // Disable the codec. + CODEC_PRIORITY_DISABLED = -1, + + // Reset the codec priority to its default value. + CODEC_PRIORITY_DEFAULT = 0, + + // Highest codec priority. + CODEC_PRIORITY_HIGHEST = 1000 * 1000 +}; + +enum class CodecSampleRate { + CODEC_SAMPLE_RATE_NONE = 0x0, + CODEC_SAMPLE_RATE_44100 = 0x1 << 0, + CODEC_SAMPLE_RATE_48000 = 0x1 << 1, + CODEC_SAMPLE_RATE_88200 = 0x1 << 2, + CODEC_SAMPLE_RATE_96000 = 0x1 << 3, + CODEC_SAMPLE_RATE_176400 = 0x1 << 4, + CODEC_SAMPLE_RATE_192000 = 0x1 << 5, + CODEC_SAMPLE_RATE_16000 = 0x1 << 6, + CODEC_SAMPLE_RATE_24000 = 0x1 << 7, + CODEC_SAMPLE_RATE_32000 = 0x1 << 8, + CODEC_SAMPLE_RATE_8000 = 0x1 << 9 +}; + +enum class CodecBPS { + CODEC_BITS_PER_SAMPLE_NONE = 0x0, + CODEC_BITS_PER_SAMPLE_16 = 0x1 << 0, + CODEC_BITS_PER_SAMPLE_24 = 0x1 << 1, + CODEC_BITS_PER_SAMPLE_32 = 0x1 << 2 +}; + +enum class CodecChannelMode { + CODEC_CHANNEL_MODE_NONE = 0x0, + CODEC_CHANNEL_MODE_MONO = 0x1 << 0, + CODEC_CHANNEL_MODE_STEREO = 0x1 << 1 +}; + +struct CodecConfig { + CodecIndex codec_type; + CodecPriority codec_priority; // Codec selection priority + // relative to other codecs: larger value + // means higher priority. If 0, reset to + // default. + CodecSampleRate sample_rate; + CodecBPS bits_per_sample; + CodecChannelMode channel_mode; + int64_t codec_specific_1; // Codec-specific value 1 + int64_t codec_specific_2; // Codec-specific value 2 + int64_t codec_specific_3; // Codec-specific value 3 + int64_t codec_specific_4; // Codec-specific value 4 +}; + +class PacsClientCallbacks { + public: + virtual ~PacsClientCallbacks() = default; + + /** Callback for pacs server registration status */ + virtual void OnInitialized(int status, int client_id) = 0; + + /** Callback for pacs server connection state change */ + virtual void OnConnectionState(const RawAddress& address, + ConnectionState state) = 0; + + /** Callback for audio ( media or voice) being available */ + virtual void OnAudioContextAvailable(const RawAddress& address, + uint32_t available_contexts) = 0; + + /** Callback for pacs discovery results */ + virtual void OnSearchComplete(int status, + const RawAddress& address, + std::vector sink_pac_records, + std::vector src_pac_records, + uint32_t sink_locations, + uint32_t src_locations, + uint32_t available_contexts, + uint32_t supported_contexts) = 0; +}; + +class PacsClientInterface { + public: + virtual ~PacsClientInterface() = default; + + /** Register the Pacs client callbacks */ + virtual void Init(PacsClientCallbacks* callbacks) = 0; + + /** Connect to pacs server */ + virtual void Connect(uint16_t client_id, const RawAddress& address) = 0; + + /** Disconnect pacs server */ + virtual void Disconnect(uint16_t client_id, const RawAddress& address) = 0; + + /** start pacs discovery */ + virtual void StartDiscovery(uint16_t client_id, + const RawAddress& address) = 0; + + /** get available audio context */ + virtual void GetAvailableAudioContexts(uint16_t client_id, + const RawAddress& address) = 0; + /** Closes the interface. */ + virtual void Cleanup(uint16_t client_id) = 0; +}; + +} // namespace pacs +} // namespace bap +} // namespace bluetooth + +#endif /* ANDROID_INCLUDE_BT_CLIENT_H */ diff --git a/le_audio/vhal/include/hardware/bt_vcp_controller.h b/le_audio/vhal/include/hardware/bt_vcp_controller.h new file mode 100644 index 00000000000..ea2e35a8570 --- /dev/null +++ b/le_audio/vhal/include/hardware/bt_vcp_controller.h @@ -0,0 +1,82 @@ +/* + *Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +/* + * Copyright 2018 The Android Open Source Project + * + * 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. + */ + +#ifndef ANDROID_INCLUDE_BT_VCP_CONTROLLER_H +#define ANDROID_INCLUDE_BT_VCP_CONTROLLER_H + +#include + +#define BT_PROFILE_VOLUME_CONTROL_ID "volume_control" + +namespace bluetooth { +namespace vcp_controller { + +enum class ConnectionState { + DISCONNECTED = 0, + CONNECTING, + CONNECTED, + DISCONNECTING +}; + +class VcpControllerCallbacks { + public: + virtual ~VcpControllerCallbacks() = default; + + /** Callback for profile connection state change */ + virtual void OnConnectionState(ConnectionState state, + const RawAddress& address) = 0; + + virtual void OnVolumeStateChange(uint8_t volume, uint8_t mute, + const RawAddress& address) = 0; + + virtual void OnVolumeFlagsChange(uint8_t flags, + const RawAddress& address) = 0; +}; + +class VcpControllerInterface { + public: + virtual ~VcpControllerInterface() = default; + + /** Register the Volume Controller callbacks */ + virtual void Init(VcpControllerCallbacks* callbacks) = 0; + + /** Connect to Volume Renderer device */ + virtual void Connect(const RawAddress& address, bool isDirect) = 0; + + /** Disconnect from Volume Renderer device */ + virtual void Disconnect(const RawAddress& address) = 0; + + /** Set absolute volume */ + virtual void SetAbsVolume(uint8_t volume, const RawAddress& address) = 0; + + virtual void Mute(const RawAddress& address) = 0; + + virtual void Unmute(const RawAddress& address) = 0; + + /** Closes the interface. */ + virtual void Cleanup(void) = 0; +}; + +} // namespace vcp_controller +} // namespace bluetooth + +#endif /* ANDROID_INCLUDE_BT_VCP_CONTROLLER_H */ + + -- GitLab