aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--binder/android/bluetooth/IBluetooth.aidl4
-rw-r--r--blueberry/tests/gd/cert/gd_device.py18
-rw-r--r--blueberry/tests/gd/hci/acl_manager_test_blueberry.py3
-rw-r--r--bta/Android.bp4
-rw-r--r--bta/csis/csis_client.cc3
-rw-r--r--bta/dm/bta_dm_act.cc36
-rw-r--r--bta/dm/bta_dm_api.cc7
-rw-r--r--bta/dm/bta_dm_pm.cc4
-rw-r--r--bta/hf_client/bta_hf_client_at.cc11
-rw-r--r--bta/hf_client/bta_hf_client_sco.cc1
-rw-r--r--bta/include/bta_api.h12
-rw-r--r--bta/le_audio/client.cc88
-rw-r--r--bta/le_audio/hal_verifier.cc2
-rw-r--r--bta/le_audio/le_audio_client_test.cc94
-rw-r--r--bta/le_audio/le_audio_types.h1
-rw-r--r--bta/pan/bta_pan_int.h23
-rw-r--r--bta/test/bta_pan_test.cc (renamed from test/mock/mock_hci_packet_parser.cc)30
-rw-r--r--bta/vc/vc.cc4
-rw-r--r--btcore/Android.bp15
-rw-r--r--btcore/src/hal_util.cc39
-rw-r--r--btif/Android.bp15
-rw-r--r--btif/BUILD.gn1
-rw-r--r--btif/include/btif_pan_internal.h8
-rw-r--r--btif/src/bluetooth.cc21
-rw-r--r--btif/src/btif_dm.cc26
-rw-r--r--btif/src/btif_keystore.cc40
-rw-r--r--btif/src/btif_pan.cc39
-rw-r--r--btif/src/btif_sdp_server.cc49
-rw-r--r--btif/src/btif_storage.cc16
-rw-r--r--btif/test/btif_core_test.cc7
-rw-r--r--build/dpkg/floss/README.mkdn16
-rwxr-xr-xbuild/dpkg/floss/build-dpkg183
-rwxr-xr-xbuild/dpkg/floss/install-dependencies95
-rw-r--r--build/dpkg/floss/package/DEBIAN/control11
-rw-r--r--build/dpkg/floss/package/etc/dbus-1/system.d/org.chromium.bluetooth.conf37
-rw-r--r--build/dpkg/floss/package/lib/systemd/system/btadapterd@.service22
-rw-r--r--build/dpkg/floss/package/lib/systemd/system/btmanagerd.service21
-rw-r--r--gd/Android.bp12
-rw-r--r--gd/BUILD.gn2
-rw-r--r--gd/btaa/android/activity_attribution.cc73
-rw-r--r--gd/cert/event_stream.py22
-rw-r--r--gd/cert/gd_device_lib.py6
-rw-r--r--gd/cert/py_le_acl_manager.py17
-rw-r--r--gd/common/strings.h10
-rw-r--r--gd/common/strings_test.cc10
-rw-r--r--gd/dumpsys/BUILD.gn3
-rw-r--r--gd/dumpsys/bundler/bundler.gni20
-rw-r--r--gd/hci/acl_manager.cc12
-rw-r--r--gd/hci/acl_manager.h3
-rw-r--r--gd/hci/acl_manager/le_impl.h11
-rw-r--r--gd/hci/cert/acl_manager_test.py18
-rw-r--r--gd/hci/cert/acl_manager_test_lib.py20
-rw-r--r--gd/hci/cert/le_acl_manager_test_lib.py63
-rw-r--r--gd/hci/facade/le_acl_manager_facade.cc25
-rw-r--r--gd/hci/facade/le_acl_manager_facade.proto2
-rw-r--r--gd/hci/le_address_manager.cc51
-rw-r--r--gd/hci/le_address_manager.h6
-rw-r--r--gd/os/android/parameter_provider.cc36
-rw-r--r--gd/os/bt_keystore.h25
-rw-r--r--gd/os/host/parameter_provider.cc18
-rw-r--r--gd/os/linux/parameter_provider.cc18
-rw-r--r--gd/os/parameter_provider.h14
-rw-r--r--gd/rust/linux/client/src/dbus_iface.rs2
-rw-r--r--gd/rust/linux/service/src/iface_bluetooth.rs2
-rw-r--r--gd/rust/linux/stack/src/bluetooth.rs1
-rw-r--r--gd/rust/linux/stack/src/bluetooth_media.rs62
-rw-r--r--gd/rust/linux/stack/src/lib.rs7
-rw-r--r--gd/rust/shim/Android.bp23
-rw-r--r--gd/rust/topshim/Android.bp5
-rw-r--r--gd/rust/topshim/BUILD.gn10
-rw-r--r--gd/rust/topshim/Cargo.toml1
-rw-r--r--gd/rust/topshim/btav_sink/btav_sink_shim.cc24
-rw-r--r--gd/rust/topshim/btav_sink/btav_sink_shim.h12
-rw-r--r--gd/rust/topshim/controller/controller_shim.cc54
-rw-r--r--gd/rust/topshim/controller/controller_shim.h49
-rw-r--r--gd/rust/topshim/facade/Android.bp17
-rw-r--r--gd/rust/topshim/facade/src/main.rs12
-rw-r--r--gd/rust/topshim/facade/src/media_service.rs10
-rw-r--r--gd/rust/topshim/facade/utils.proto13
-rw-r--r--gd/rust/topshim/hfp/hfp_shim.cc121
-rw-r--r--gd/rust/topshim/hfp/hfp_shim.h15
-rw-r--r--gd/rust/topshim/src/btif.rs2
-rw-r--r--gd/rust/topshim/src/controller.rs32
-rw-r--r--gd/rust/topshim/src/lib.rs1
-rw-r--r--gd/rust/topshim/src/profiles/a2dp.rs37
-rw-r--r--gd/rust/topshim/src/profiles/hfp.rs117
-rw-r--r--gd/rust/topshim/src/profiles/mod.rs1
-rw-r--r--gd/shim/BUILD.gn2
-rw-r--r--gd/storage/config_cache.cc57
-rw-r--r--gd/storage/config_cache.h1
-rw-r--r--gd/storage/storage_module.cc27
-rw-r--r--gd/storage/storage_module.h1
-rw-r--r--hci/include/hci_layer.h4
-rw-r--r--include/hardware/bt_keystore.h9
-rw-r--r--internal_include/bt_target.h8
-rw-r--r--le_audio/certification_tool/bap_uclient_test_tool/Android.bp55
-rw-r--r--le_audio/certification_tool/bap_uclient_test_tool/bap_uclient_test.cpp1904
-rw-r--r--le_audio/certification_tool/types/Android.bp44
-rw-r--r--le_audio/certification_tool/types/bluetooth/uuid.cc177
-rw-r--r--le_audio/certification_tool/types/bluetooth/uuid.h143
-rw-r--r--le_audio/certification_tool/types/class_of_device.cc78
-rw-r--r--le_audio/certification_tool/types/class_of_device.h63
-rw-r--r--le_audio/certification_tool/types/raw_address.cc73
-rw-r--r--le_audio/certification_tool/types/raw_address.h79
-rw-r--r--le_audio/frameworks/base/Android.bp31
-rw-r--r--le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastAudioScanAssistCallback.java178
-rw-r--r--le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastAudioScanAssistManager.java635
-rw-r--r--le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastSourceChannel.java231
-rw-r--r--le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastSourceInfo.java1052
-rw-r--r--le_audio/frameworks/base/core/java/android/bluetooth/BluetoothBroadcast.java280
-rw-r--r--le_audio/frameworks/base/core/java/android/bluetooth/BluetoothSyncHelper.java736
-rw-r--r--le_audio/frameworks/base/packages/SettingsLib/Android.bp31
-rw-r--r--le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BCProfile.java301
-rw-r--r--le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastProfile.java180
-rw-r--r--le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastSourceInfoHandler.java79
-rw-r--r--le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/VendorCachedBluetoothDevice.java273
-rw-r--r--le_audio/packages/apps/Bluetooth/Android.bp39
-rw-r--r--le_audio/packages/apps/Bluetooth/jni/Android.bp61
-rw-r--r--le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_acm.cpp557
-rw-r--r--le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_apm.cpp194
-rw-r--r--le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_broadcast.cpp470
-rw-r--r--le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterServiceExt.cpp76
-rw-r--r--le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_cc.cpp402
-rw-r--r--le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_csip_client.cpp409
-rw-r--r--le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_ext.h44
-rw-r--r--le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_mcp.cpp431
-rw-r--r--le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_pacs_client.cpp378
-rw-r--r--le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_vcp_controller.cpp295
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmCodecConfig.java141
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmNativeInterface.java238
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmService.java1866
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmStackEvent.java144
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmStateMachine.java2354
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ActiveDeviceManagerService.java1444
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ApmConst.java70
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ApmNativeInterface.java131
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/CallAudio.java758
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/CallControl.java164
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/DeviceProfileMap.java814
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/MediaAudio.java1204
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/MediaControlManager.java211
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/StreamAudioService.java382
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/VolumeManager.java779
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BCService.java1691
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BaseData.java861
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassClientStateMachine.java2388
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassCsetManager.java592
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassUtils.java506
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/broadcast/BroadcastNativeInterface.java253
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/broadcast/BroadcastService.java1960
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/broadcast/BroadcastStackEvent.java120
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCHalConstants.java100
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCNativeInterface.java255
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCService.java864
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CallControlState.java84
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupAppMap.java178
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupClientNativeInterface.java251
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupScanner.java529
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupService.java1107
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/mcp/McpNativeInterface.java274
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/mcp/McpService.java842
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PCService.java528
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientNativeInterface.java237
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientStackEvent.java95
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/pacsclient/PacsClientStateMachine.java703
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpController.java733
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpControllerNativeInterface.java200
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpControllerStateMachine.java1046
-rw-r--r--le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/vcp/VcpStackEvent.java79
-rw-r--r--le_audio/packages/apps/Settings/Android.bp31
-rw-r--r--le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BADevicePreferenceController.java144
-rw-r--r--le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoDetailsController.java679
-rw-r--r--le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoDetailsFragment.java129
-rw-r--r--le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoPreference.java234
-rw-r--r--le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoPreferenceCallback.java37
-rw-r--r--le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoUpdater.java255
-rw-r--r--le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastEnableController.java247
-rw-r--r--le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastPinController.java237
-rw-r--r--le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastPinFragment.java256
-rw-r--r--le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastSourceInfoEntries.java52
-rw-r--r--le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothDetailsAddSourceButtonController.java118
-rw-r--r--le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothSADetail.java655
-rw-r--r--le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BroadcastScanAssistanceUtils.java151
-rw-r--r--le_audio/system/bt/binder/Android.bp36
-rw-r--r--le_audio/system/bt/binder/android/bluetooth/BleBroadcastSourceChannel.aidl (renamed from internal_include/bt_common.h)11
-rw-r--r--le_audio/system/bt/binder/android/bluetooth/BleBroadcastSourceInfo.aidl22
-rw-r--r--le_audio/system/bt/binder/android/bluetooth/IBleBroadcastAudioScanAssistCallback.aidl48
-rw-r--r--le_audio/system/bt/binder/android/bluetooth/IBluetoothBroadcast.aidl35
-rw-r--r--le_audio/system/bt/binder/android/bluetooth/IBluetoothSyncHelper.aidl75
-rw-r--r--le_audio/system/bt/bta/Android.bp97
-rw-r--r--le_audio/system/bt/bta/bap/ascs_client.cc1731
-rw-r--r--le_audio/system/bt/bta/bap/connected_iso.cc1556
-rw-r--r--le_audio/system/bt/bta/bap/gattc_ops_queue.cc297
-rw-r--r--le_audio/system/bt/bta/bap/gattc_ops_queue.h119
-rw-r--r--le_audio/system/bt/bta/bap/gatts_ops_queue.cc139
-rw-r--r--le_audio/system/bt/bta/bap/gatts_ops_queue.h62
-rw-r--r--le_audio/system/bt/bta/bap/pacs_client.cc1862
-rw-r--r--le_audio/system/bt/bta/bap/ucast_client_int.h1276
-rw-r--r--le_audio/system/bt/bta/bap/uclient_alarm.cc99
-rw-r--r--le_audio/system/bt/bta/bap/uclient_alarm.h63
-rw-r--r--le_audio/system/bt/bta/bap/uclient_main.cc548
-rw-r--r--le_audio/system/bt/bta/bap/uclient_strm_mgr.cc1013
-rw-r--r--le_audio/system/bt/bta/bap/uclient_strm_tracker.cc4001
-rw-r--r--le_audio/system/bt/bta/cc/bta_cc_main.cc2334
-rw-r--r--le_audio/system/bt/bta/csip/bta_csip_act.cc1686
-rw-r--r--le_audio/system/bt/bta/csip/bta_csip_api.cc287
-rw-r--r--le_audio/system/bt/bta/csip/bta_csip_int.h321
-rw-r--r--le_audio/system/bt/bta/csip/bta_csip_main.cc296
-rw-r--r--le_audio/system/bt/bta/csip/bta_csip_utils.cc1318
-rw-r--r--le_audio/system/bt/bta/dm/bta_dm_adv_audio.cc1186
-rw-r--r--le_audio/system/bt/bta/include/bta_ascs_client_api.h77
-rw-r--r--le_audio/system/bt/bta/include/bta_bap_uclient_api.h67
-rw-r--r--le_audio/system/bt/bta/include/bta_cc_api.h472
-rw-r--r--le_audio/system/bt/bta/include/bta_csip_api.h396
-rw-r--r--le_audio/system/bt/bta/include/bta_dm_adv_audio.h152
-rw-r--r--le_audio/system/bt/bta/include/bta_mcp_api.h489
-rw-r--r--le_audio/system/bt/bta/include/bta_pacs_client_api.h48
-rw-r--r--le_audio/system/bt/bta/include/bta_vcp_controller_api.h148
-rw-r--r--le_audio/system/bt/bta/include/connected_iso_api.h111
-rw-r--r--le_audio/system/bt/bta/mcp/bta_mcp_main.cc3089
-rw-r--r--le_audio/system/bt/bta/vcp/bta_vcp_controller.cc1108
-rw-r--r--le_audio/system/bt/btif/Android.bp107
-rw-r--r--le_audio/system/bt/btif/include/bluetooth_adv_audio.h33
-rw-r--r--le_audio/system/bt/btif/include/btif_acm.h250
-rw-r--r--le_audio/system/bt/btif/include/btif_acm_source.h35
-rw-r--r--le_audio/system/bt/btif/include/btif_bap_broadcast.h92
-rw-r--r--le_audio/system/bt/btif/include/btif_bap_codec_utils.h90
-rw-r--r--le_audio/system/bt/btif/include/btif_bap_config.h83
-rw-r--r--le_audio/system/bt/btif/include/btif_dm_adv_audio.h33
-rw-r--r--le_audio/system/bt/btif/include/btif_vmcp.h147
-rw-r--r--le_audio/system/bt/btif/leaudio_configs.xml727
-rw-r--r--le_audio/system/bt/btif/src/bluetooth_adv_audio.cc179
-rw-r--r--le_audio/system/bt/btif/src/btif_acm.cc6672
-rw-r--r--le_audio/system/bt/btif/src/btif_acm_source.cc242
-rw-r--r--le_audio/system/bt/btif/src/btif_apm.cc189
-rw-r--r--le_audio/system/bt/btif/src/btif_ascs_client.cc209
-rw-r--r--le_audio/system/bt/btif/src/btif_bap_broadcast.cc1822
-rw-r--r--le_audio/system/bt/btif/src/btif_bap_codec_utils.cc395
-rw-r--r--le_audio/system/bt/btif/src/btif_bap_config.cc860
-rw-r--r--le_audio/system/bt/btif/src/btif_bap_uclient.cc186
-rw-r--r--le_audio/system/bt/btif/src/btif_bap_uclient_test.cc2971
-rw-r--r--le_audio/system/bt/btif/src/btif_cc.cc222
-rw-r--r--le_audio/system/bt/btif/src/btif_csip.cc252
-rw-r--r--le_audio/system/bt/btif/src/btif_dm_adv_audio.cc692
-rw-r--r--le_audio/system/bt/btif/src/btif_mcp.cc185
-rw-r--r--le_audio/system/bt/btif/src/btif_pacs_client.cc147
-rw-r--r--le_audio/system/bt/btif/src/btif_vcp_controller.cc131
-rw-r--r--le_audio/system/bt/btif/src/btif_vmcp.cc775
-rw-r--r--le_audio/system/bt/common/state_machine.h214
-rw-r--r--le_audio/vhal/include/hardware/bluetooth_callcontrol_callbacks.h62
-rw-r--r--le_audio/vhal/include/hardware/bluetooth_callcontrol_interface.h152
-rw-r--r--le_audio/vhal/include/hardware/bt_acm.h126
-rw-r--r--le_audio/vhal/include/hardware/bt_apm.h75
-rw-r--r--le_audio/vhal/include/hardware/bt_ascs_client.h295
-rw-r--r--le_audio/vhal/include/hardware/bt_bap_ba.h126
-rw-r--r--le_audio/vhal/include/hardware/bt_bap_uclient.h251
-rw-r--r--le_audio/vhal/include/hardware/bt_csip.h119
-rw-r--r--le_audio/vhal/include/hardware/bt_mcp.h66
-rw-r--r--le_audio/vhal/include/hardware/bt_pacs_client.h183
-rw-r--r--le_audio/vhal/include/hardware/bt_vcp_controller.h82
-rw-r--r--main/shim/acl.cc133
-rw-r--r--main/shim/acl.h10
-rw-r--r--main/shim/acl_api.cc19
-rw-r--r--main/shim/acl_api.h7
-rw-r--r--main/shim/btm_api.cc24
-rw-r--r--main/shim/btm_api.h47
-rw-r--r--main/shim/config.cc4
-rw-r--r--main/shim/config.h1
-rw-r--r--main/shim/hci_layer.cc23
-rw-r--r--main/shim/stack.cc3
-rw-r--r--main/test/main_shim_test.cc5
-rw-r--r--profile/avrcp/device.cc38
-rw-r--r--service/bluetooth_interface.cc874
-rw-r--r--service/hal/bluetooth_interface.cc40
-rw-r--r--stack/acl/acl.h5
-rw-r--r--stack/avdt/avdt_int.h3
-rw-r--r--stack/avdt/avdt_scb_act.cc34
-rw-r--r--stack/btm/btm_ble.cc11
-rw-r--r--stack/btm/btm_ble_bgconn.cc2
-rw-r--r--stack/btm/btm_ble_privacy.cc2
-rw-r--r--stack/btm/btm_inq.cc14
-rw-r--r--stack/btm/btm_int_types.h8
-rw-r--r--stack/btm/btm_sco.cc3
-rw-r--r--stack/btm/btm_sec.cc40
-rw-r--r--stack/btm/neighbor_inquiry.h2
-rw-r--r--stack/gatt/gatt_api.cc16
-rw-r--r--stack/gatt/gatt_cl.cc122
-rw-r--r--stack/include/btm_api.h6
-rw-r--r--stack/include/btm_api_types.h1
-rw-r--r--stack/include/btm_client_interface.h2
-rw-r--r--stack/include/gatt_api.h12
-rw-r--r--stack/include/pan_api.h6
-rw-r--r--stack/include/security_client_callbacks.h3
-rw-r--r--stack/l2cap/l2c_csm.cc6
-rw-r--r--stack/pan/pan_api.cc67
-rw-r--r--stack/pan/pan_int.h7
-rw-r--r--stack/pan/pan_main.cc5
-rw-r--r--test/Android.bp7
-rw-r--r--test/mock/mock_bluetooth_interface.cc237
-rw-r--r--test/mock/mock_bta_dm_api.cc2
-rw-r--r--test/mock/mock_bta_dm_api.h8
-rw-r--r--test/mock/mock_btif_bta_pan_co_rx.cc100
-rw-r--r--test/mock/mock_btif_bta_pan_co_rx.h147
-rw-r--r--test/mock/mock_main_shim_BtifConfigInterface.cc2
-rw-r--r--test/mock/mock_main_shim_acl_api.cc15
-rw-r--r--test/mock/mock_main_shim_btm_api.cc15
-rw-r--r--test/mock/mock_stack_acl_btm_pm.cc2
-rw-r--r--test/mock/mock_stack_btm.cc8
-rw-r--r--test/mock/mock_stack_btm_inq.cc6
-rw-r--r--test/mock/mock_stack_gatt.cc3
-rw-r--r--test/mock/mock_stack_pan_api.cc1
-rw-r--r--test/suite/Android.bp20
-rw-r--r--types/raw_address.h6
-rw-r--r--vendor_libs/test_vendor_lib/Android.bp1
-rw-r--r--vendor_libs/test_vendor_lib/model/controller/dual_mode_controller.cc16
-rw-r--r--vendor_libs/test_vendor_lib/model/controller/link_layer_controller.cc11
-rw-r--r--vendor_libs/test_vendor_lib/model/controller/link_layer_controller.h2
-rw-r--r--vendor_libs/test_vendor_lib/scripts/hci_socket.py2
-rw-r--r--vendor_libs/test_vendor_lib/scripts/link_layer_socket.py2
-rw-r--r--vendor_libs/test_vendor_lib/scripts/send_simple_commands.py2
-rw-r--r--vendor_libs/test_vendor_lib/scripts/simple_link_layer_socket.py2
-rw-r--r--vendor_libs/test_vendor_lib/scripts/simple_stack.py2
-rw-r--r--vendor_libs/test_vendor_lib/scripts/test_channel.py2
323 files changed, 90446 insertions, 554 deletions
diff --git a/binder/android/bluetooth/IBluetooth.aidl b/binder/android/bluetooth/IBluetooth.aidl
index 0efa972ea..3e78f5b6b 100644
--- a/binder/android/bluetooth/IBluetooth.aidl
+++ b/binder/android/bluetooth/IBluetooth.aidl
@@ -196,6 +196,10 @@ interface IBluetooth
@JavaPassthrough(annotation="@android.annotation.RequiresNoPermission")
boolean isLePeriodicAdvertisingSupported();
@JavaPassthrough(annotation="@android.annotation.RequiresNoPermission")
+ int isCisCentralSupported();
+ @JavaPassthrough(annotation="@android.annotation.RequiresNoPermission")
+ int isLePeriodicAdvertisingSyncTransferSenderSupported();
+ @JavaPassthrough(annotation="@android.annotation.RequiresNoPermission")
int getLeMaximumAdvertisingDataLength();
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
diff --git a/blueberry/tests/gd/cert/gd_device.py b/blueberry/tests/gd/cert/gd_device.py
index 8540306ce..4dc8b2735 100644
--- a/blueberry/tests/gd/cert/gd_device.py
+++ b/blueberry/tests/gd/cert/gd_device.py
@@ -319,22 +319,28 @@ class GdAndroidDevice(GdDeviceBase):
self.logcat_logger.stop()
self.cleanup_port_forwarding()
try:
- self.adb.pull("/data/misc/bluetooth/logs/btsnoop_hci.log %s" % os.path.join(
- self.log_path_base, "%s_btsnoop_hci.log" % self.label))
+ self.adb.pull([
+ "/data/misc/bluetooth/logs/btsnoop_hci.log",
+ str(os.path.join(self.log_path_base, "%s_btsnoop_hci.log" % self.label))
+ ])
except AdbError as error:
# Some tests have no snoop logs, and that's OK
if ADB_FILE_NOT_EXIST_ERROR not in str(error):
logging.error(PULL_LOG_FILE_ERROR_MSG_PREFIX + str(error))
try:
- self.adb.pull("/data/misc/bluedroid/bt_config.conf %s" % os.path.join(self.log_path_base,
- "%s_bt_config.conf" % self.label))
+ self.adb.pull([
+ "/data/misc/bluedroid/bt_config.conf",
+ str(os.path.join(self.log_path_base, "%s_bt_config.conf" % self.label))
+ ])
except AdbError as error:
# Some tests have no config file, and that's OK
if ADB_FILE_NOT_EXIST_ERROR not in str(error):
logging.error(PULL_LOG_FILE_ERROR_MSG_PREFIX + str(error))
try:
- self.adb.pull("/data/misc/bluedroid/bt_config.bak %s" % os.path.join(self.log_path_base,
- "%s_bt_config.bak" % self.label))
+ self.adb.pull([
+ "/data/misc/bluedroid/bt_config.bak",
+ str(os.path.join(self.log_path_base, "%s_bt_config.bak" % self.label))
+ ])
except AdbError as error:
# Some tests have no config.bak file, and that's OK
if ADB_FILE_NOT_EXIST_ERROR not in str(error):
diff --git a/blueberry/tests/gd/hci/acl_manager_test_blueberry.py b/blueberry/tests/gd/hci/acl_manager_test_blueberry.py
index 0040bf271..9081f1c78 100644
--- a/blueberry/tests/gd/hci/acl_manager_test_blueberry.py
+++ b/blueberry/tests/gd/hci/acl_manager_test_blueberry.py
@@ -39,6 +39,9 @@ class AclManagerTestBb(gd_base_test.GdBaseTestClass, AclManagerTestBase):
def test_cert_connects(self):
AclManagerTestBase.test_cert_connects(self)
+ def test_reject_broadcast(self):
+ AclManagerTestBase.test_reject_broadcast(self)
+
def test_cert_connects_disconnects(self):
AclManagerTestBase.test_cert_connects_disconnects(self)
diff --git a/bta/Android.bp b/bta/Android.bp
index 0242c8a57..b91db34a6 100644
--- a/bta/Android.bp
+++ b/bta/Android.bp
@@ -216,10 +216,14 @@ cc_test {
"hh/bta_hh_le.cc",
"hh/bta_hh_main.cc",
"hh/bta_hh_utils.cc",
+ "pan/bta_pan_act.cc",
+ "pan/bta_pan_api.cc",
+ "pan/bta_pan_main.cc",
"sys/bta_sys_conn.cc",
"sys/bta_sys_main.cc",
"test/bta_dm_test.cc",
"test/bta_gatt_test.cc",
+ "test/bta_pan_test.cc",
],
shared_libs: [
"libcrypto",
diff --git a/bta/csis/csis_client.cc b/bta/csis/csis_client.cc
index c1a3f573b..ba1613207 100644
--- a/bta/csis/csis_client.cc
+++ b/bta/csis/csis_client.cc
@@ -1080,9 +1080,8 @@ class CsisClientImpl : public CsisClient {
p_service_data + service_data_len,
(remaining_data_len -= service_data_len), BTM_BLE_AD_TYPE_RSI,
&service_data_len))) {
- uint8_t* p = (uint8_t*)(p_service_data);
RawAddress bda;
- STREAM_TO_BDADDR(bda, p);
+ STREAM_TO_BDADDR(bda, p_service_data);
devices.push_back(std::move(bda));
}
diff --git a/bta/dm/bta_dm_act.cc b/bta/dm/bta_dm_act.cc
index f21cb6b3a..42fcdc790 100644
--- a/bta/dm/bta_dm_act.cc
+++ b/bta/dm/bta_dm_act.cc
@@ -68,7 +68,7 @@ void BTIF_dm_disable();
void BTIF_dm_enable();
void btm_ble_adv_init(void);
-static void bta_dm_inq_results_cb(tBTM_INQ_RESULTS* p_inq, uint8_t* p_eir,
+static void bta_dm_inq_results_cb(tBTM_INQ_RESULTS* p_inq, const uint8_t* p_eir,
uint16_t eir_len);
static void bta_dm_inq_cmpl_cb(void* p_result);
static void bta_dm_service_search_remname_cback(const RawAddress& bd_addr,
@@ -78,7 +78,7 @@ static void bta_dm_find_services(const RawAddress& bd_addr);
static void bta_dm_discover_next_device(void);
static void bta_dm_sdp_callback(tSDP_STATUS sdp_status);
static uint8_t bta_dm_pin_cback(const RawAddress& bd_addr, DEV_CLASS dev_class,
- BD_NAME bd_name, bool min_16_digit);
+ const BD_NAME bd_name, bool min_16_digit);
static uint8_t bta_dm_new_link_key_cback(const RawAddress& bd_addr,
DEV_CLASS dev_class, BD_NAME bd_name,
const LinkKey& key, uint8_t key_type);
@@ -119,7 +119,7 @@ static void bta_dm_gattc_register(void);
static void btm_dm_start_gatt_discovery(const RawAddress& bd_addr);
static void bta_dm_cancel_gatt_discovery(const RawAddress& bd_addr);
static void bta_dm_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data);
-extern tBTA_DM_CONTRL_STATE bta_dm_pm_obtain_controller_state(void);
+extern tBTM_CONTRL_STATE bta_dm_pm_obtain_controller_state(void);
#if (BLE_VND_INCLUDED == TRUE)
static void bta_dm_ctrl_features_rd_cmpl_cback(tHCI_STATUS result);
#endif
@@ -196,8 +196,8 @@ WaitForAllAclConnectionsToDrain::FromAlarmCallbackData(void* data) {
static void bta_dm_reset_sec_dev_pending(const RawAddress& remote_bd_addr);
static void bta_dm_remove_sec_dev_entry(const RawAddress& remote_bd_addr);
-static void bta_dm_observe_results_cb(tBTM_INQ_RESULTS* p_inq, uint8_t* p_eir,
- uint16_t eir_len);
+static void bta_dm_observe_results_cb(tBTM_INQ_RESULTS* p_inq,
+ const uint8_t* p_eir, uint16_t eir_len);
static void bta_dm_observe_cmpl_cb(void* p_result);
static void bta_dm_delay_role_switch_cback(void* data);
static void bta_dm_wait_for_acl_to_drain_cback(void* data);
@@ -1812,7 +1812,7 @@ static void bta_dm_sdp_callback(tSDP_STATUS sdp_status) {
* Returns void
*
******************************************************************************/
-static void bta_dm_inq_results_cb(tBTM_INQ_RESULTS* p_inq, uint8_t* p_eir,
+static void bta_dm_inq_results_cb(tBTM_INQ_RESULTS* p_inq, const uint8_t* p_eir,
uint16_t eir_len) {
tBTA_DM_SEARCH result;
tBTM_INQ_INFO* p_inq_info;
@@ -1832,7 +1832,7 @@ static void bta_dm_inq_results_cb(tBTM_INQ_RESULTS* p_inq, uint8_t* p_eir,
result.inq_res.include_rsi = p_inq->include_rsi;
/* application will parse EIR to find out remote device name */
- result.inq_res.p_eir = p_eir;
+ result.inq_res.p_eir = const_cast<uint8_t*>(p_eir);
result.inq_res.eir_len = eir_len;
p_inq_info = BTM_InqDbRead(p_inq->remote_bd_addr);
@@ -2028,7 +2028,7 @@ static void bta_dm_pinname_cback(void* p_data) {
*
******************************************************************************/
static uint8_t bta_dm_pin_cback(const RawAddress& bd_addr, DEV_CLASS dev_class,
- BD_NAME bd_name, bool min_16_digit) {
+ const BD_NAME bd_name, bool min_16_digit) {
if (!bta_dm_cb.p_sec_cback) return BTM_NOT_AUTHORIZED;
/* If the device name is not known, save bdaddr and devclass and initiate a
@@ -2909,8 +2909,8 @@ static void bta_dm_set_eir(char* local_name) {
num_uuid = p_bta_dm_eir_cfg->bta_dm_eir_uuid16_len / Uuid::kNumBytes16;
#else // BTA_EIR_CANNED_UUID_LIST
max_num_uuid = (free_eir_length - 2) / Uuid::kNumBytes16;
- data_type = BTM_GetEirSupportedServices(bta_dm_cb.eir_uuid, &p,
- max_num_uuid, &num_uuid);
+ data_type = get_btm_client_interface().eir.BTM_GetEirSupportedServices(
+ bta_dm_cb.eir_uuid, &p, max_num_uuid, &num_uuid);
p = (uint8_t*)p_buf + BTM_HCI_EIR_OFFSET; /* reset p */
#endif // BTA_EIR_CANNED_UUID_LIST
@@ -2967,8 +2967,8 @@ static void bta_dm_set_eir(char* local_name) {
num_uuid = 0;
max_num_uuid = (free_eir_length - 2) / Uuid::kNumBytes16;
- data_type = BTM_GetEirSupportedServices(bta_dm_cb.eir_uuid, &p,
- max_num_uuid, &num_uuid);
+ data_type = get_btm_client_interface().eir.BTM_GetEirSupportedServices(
+ bta_dm_cb.eir_uuid, &p, max_num_uuid, &num_uuid);
if (data_type == HCI_EIR_MORE_16BITS_UUID_TYPE) {
APPL_TRACE_WARNING("BTA EIR: UUID 16-bit list is truncated");
@@ -3314,8 +3314,8 @@ bool bta_dm_check_if_only_hd_connected(const RawAddress& peer_addr) {
* Returns void
*
******************************************************************************/
-static void bta_dm_observe_results_cb(tBTM_INQ_RESULTS* p_inq, uint8_t* p_eir,
- uint16_t eir_len) {
+static void bta_dm_observe_results_cb(tBTM_INQ_RESULTS* p_inq,
+ const uint8_t* p_eir, uint16_t eir_len) {
tBTA_DM_SEARCH result;
tBTM_INQ_INFO* p_inq_info;
APPL_TRACE_DEBUG("bta_dm_observe_results_cb");
@@ -3334,7 +3334,7 @@ static void bta_dm_observe_results_cb(tBTM_INQ_RESULTS* p_inq, uint8_t* p_eir,
result.inq_res.ble_periodic_adv_int = p_inq->ble_periodic_adv_int;
/* application will parse EIR to find out remote device name */
- result.inq_res.p_eir = p_eir;
+ result.inq_res.p_eir = const_cast<uint8_t*>(p_eir);
result.inq_res.eir_len = eir_len;
p_inq_info = BTM_InqDbRead(p_inq->remote_bd_addr);
@@ -3366,7 +3366,7 @@ static void bta_dm_observe_results_cb(tBTM_INQ_RESULTS* p_inq, uint8_t* p_eir,
*
******************************************************************************/
static void bta_dm_opportunistic_observe_results_cb(tBTM_INQ_RESULTS* p_inq,
- uint8_t* p_eir,
+ const uint8_t* p_eir,
uint16_t eir_len) {
tBTA_DM_SEARCH result;
tBTM_INQ_INFO* p_inq_info;
@@ -3385,7 +3385,7 @@ static void bta_dm_opportunistic_observe_results_cb(tBTM_INQ_RESULTS* p_inq,
result.inq_res.ble_periodic_adv_int = p_inq->ble_periodic_adv_int;
/* application will parse EIR to find out remote device name */
- result.inq_res.p_eir = p_eir;
+ result.inq_res.p_eir = const_cast<uint8_t*>(p_eir);
result.inq_res.eir_len = eir_len;
p_inq_info = BTM_InqDbRead(p_inq->remote_bd_addr);
@@ -3830,7 +3830,7 @@ static void bta_ble_energy_info_cmpl(tBTM_BLE_TX_TIME_MS tx_time,
tBTM_BLE_ENERGY_USED energy_used,
tHCI_STATUS status) {
tBTA_STATUS st = (status == HCI_SUCCESS) ? BTA_SUCCESS : BTA_FAILURE;
- tBTA_DM_CONTRL_STATE ctrl_state = 0;
+ tBTM_CONTRL_STATE ctrl_state = BTM_CONTRL_UNKNOWN;
if (BTA_SUCCESS == st) ctrl_state = bta_dm_pm_obtain_controller_state();
diff --git a/bta/dm/bta_dm_api.cc b/bta/dm/bta_dm_api.cc
index b884b969d..c1bdf0ef2 100644
--- a/bta/dm/bta_dm_api.cc
+++ b/bta/dm/bta_dm_api.cc
@@ -33,6 +33,7 @@
#include "stack/btm/btm_sec.h"
#include "stack/include/bt_octets.h"
#include "stack/include/btm_api.h"
+#include "stack/include/btm_client_interface.h"
#include "stack/include/btu.h" // do_in_main_thread
#include "types/bluetooth/uuid.h"
#include "types/raw_address.h"
@@ -279,7 +280,7 @@ tBTA_STATUS BTA_DmRemoveDevice(const RawAddress& bd_addr) {
*
******************************************************************************/
extern const uint16_t bta_service_id_to_uuid_lkup_tbl[];
-void BTA_GetEirService(uint8_t* p_eir, size_t eir_len,
+void BTA_GetEirService(const uint8_t* p_eir, size_t eir_len,
tBTA_SERVICE_MASK* p_services) {
uint8_t xx, yy;
uint8_t num_uuid, max_num_uuid = 32;
@@ -287,8 +288,8 @@ void BTA_GetEirService(uint8_t* p_eir, size_t eir_len,
uint16_t* p_uuid16 = (uint16_t*)uuid_list;
tBTA_SERVICE_MASK mask;
- BTM_GetEirUuidList(p_eir, eir_len, Uuid::kNumBytes16, &num_uuid, uuid_list,
- max_num_uuid);
+ get_btm_client_interface().eir.BTM_GetEirUuidList(
+ p_eir, eir_len, Uuid::kNumBytes16, &num_uuid, uuid_list, max_num_uuid);
for (xx = 0; xx < num_uuid; xx++) {
mask = 1;
for (yy = 0; yy < BTA_MAX_SERVICE_ID; yy++) {
diff --git a/bta/dm/bta_dm_pm.cc b/bta/dm/bta_dm_pm.cc
index 7fee5c9e0..7e0b4cc9f 100644
--- a/bta/dm/bta_dm_pm.cc
+++ b/bta/dm/bta_dm_pm.cc
@@ -1101,12 +1101,12 @@ static int bta_dm_get_sco_index() {
* Parameters:
*
******************************************************************************/
-tBTA_DM_CONTRL_STATE bta_dm_pm_obtain_controller_state(void) {
+tBTM_CONTRL_STATE bta_dm_pm_obtain_controller_state(void) {
/* Did not use counts as it is not sure, how accurate the count values are
*in
** bta_dm_cb.device_list.count > 0 || bta_dm_cb.device_list.le_count > 0 */
- tBTA_DM_CONTRL_STATE cur_state = BTA_DM_CONTRL_UNKNOWN;
+ tBTM_CONTRL_STATE cur_state = BTM_CONTRL_UNKNOWN;
cur_state = BTM_PM_ReadControllerState();
APPL_TRACE_DEBUG("bta_dm_pm_obtain_controller_state: %d", cur_state);
diff --git a/bta/hf_client/bta_hf_client_at.cc b/bta/hf_client/bta_hf_client_at.cc
index 721bbbcf7..f2fcf8295 100644
--- a/bta/hf_client/bta_hf_client_at.cc
+++ b/bta/hf_client/bta_hf_client_at.cc
@@ -2132,6 +2132,17 @@ void bta_hf_client_send_at_bia(tBTA_HF_CLIENT_CB* client_cb) {
for (i = 0; i < BTA_HF_CLIENT_AT_INDICATOR_COUNT; i++) {
int sup = client_cb->at_cb.indicator_lookup[i] == -1 ? 0 : 1;
+/* If this value matches the position of SIGNAL in the indicators array,
+ * then hardcode disable signal strength indicators.
+ * indicator_lookup[i] points to the position in the bta_hf_client_indicators
+ * array defined at the top of this file */
+#ifdef BTA_HF_CLIENT_INDICATOR_SIGNAL_POS
+ if (client_cb->at_cb.indicator_lookup[i] ==
+ BTA_HF_CLIENT_INDICATOR_SIGNAL_POS) {
+ sup = 0;
+ }
+#endif
+
at_len += snprintf(buf + at_len, sizeof(buf) - at_len, "%u,", sup);
}
diff --git a/bta/hf_client/bta_hf_client_sco.cc b/bta/hf_client/bta_hf_client_sco.cc
index 2ec02b317..998240283 100644
--- a/bta/hf_client/bta_hf_client_sco.cc
+++ b/bta/hf_client/bta_hf_client_sco.cc
@@ -332,7 +332,6 @@ static void bta_hf_client_sco_event(tBTA_HF_CLIENT_CB* client_cb,
break;
case BTA_HF_CLIENT_SCO_SHUTDOWN_E:
- case BTA_HF_CLIENT_SCO_CLOSE_E:
/* remove listening connection */
bta_hf_client_sco_remove(client_cb);
diff --git a/bta/include/bta_api.h b/bta/include/bta_api.h
index 995798f23..8ac26359c 100644
--- a/bta/include/bta_api.h
+++ b/bta/include/bta_api.h
@@ -65,7 +65,11 @@ typedef enum : uint8_t {
#define BTA_BIP_SERVICE_ID 13 /* Basic Imaging profile */
#define BTA_A2DP_SINK_SERVICE_ID 18 /* A2DP Sink */
#define BTA_HID_SERVICE_ID 20 /* HID */
+#define BTA_PBAP_SERVICE_ID 22 /* PhoneBook Access Server*/
#define BTA_HFP_HS_SERVICE_ID 24 /* HSP HS role */
+#define BTA_MAP_SERVICE_ID 25 /* Message Access Profile */
+#define BTA_MN_SERVICE_ID 26 /* Message Notification Service */
+#define BTA_PCE_SERVICE_ID 28 /* PhoneBook Access Client */
#define BTA_SDP_SERVICE_ID 29 /* SDP Search */
#define BTA_HIDD_SERVICE_ID 30 /* HID Device */
@@ -477,15 +481,11 @@ typedef void(tBTA_DM_ENCRYPT_CBACK)(const RawAddress& bd_addr,
tBT_TRANSPORT transport,
tBTA_STATUS result);
-#define BTA_DM_CONTRL_UNKNOWN 0 /* Unknown state */
-
-typedef uint8_t tBTA_DM_CONTRL_STATE;
-
typedef void(tBTA_BLE_ENERGY_INFO_CBACK)(tBTM_BLE_TX_TIME_MS tx_time,
tBTM_BLE_RX_TIME_MS rx_time,
tBTM_BLE_IDLE_TIME_MS idle_time,
tBTM_BLE_ENERGY_USED energy_used,
- tBTA_DM_CONTRL_STATE ctrl_state,
+ tBTM_CONTRL_STATE ctrl_state,
tBTA_STATUS status);
/* Maximum service name length */
@@ -866,7 +866,7 @@ extern tBTA_STATUS BTA_DmRemoveDevice(const RawAddress& bd_addr);
* Returns None
*
******************************************************************************/
-extern void BTA_GetEirService(uint8_t* p_eir, size_t eir_len,
+extern void BTA_GetEirService(const uint8_t* p_eir, size_t eir_len,
tBTA_SERVICE_MASK* p_services);
/*******************************************************************************
diff --git a/bta/le_audio/client.cc b/bta/le_audio/client.cc
index 54e60170d..763f8ebf5 100644
--- a/bta/le_audio/client.cc
+++ b/bta/le_audio/client.cc
@@ -414,6 +414,12 @@ class LeAudioClientImpl : public LeAudioClient {
group_id);
}
+ void remove_group_if_possible(LeAudioDeviceGroup* group) {
+ if (group && group->IsEmpty() && !group->cig_created_) {
+ aseGroups_.Remove(group->group_id_);
+ }
+ }
+
void group_remove_node(LeAudioDeviceGroup* group, const RawAddress& address,
bool update_group_module = false) {
int group_id = group->group_id_;
@@ -431,8 +437,7 @@ class LeAudioClientImpl : public LeAudioClient {
/* Remove group if this was the last leAudioDevice in this group */
if (group->IsEmpty()) {
- aseGroups_.Remove(group_id);
-
+ remove_group_if_possible(group);
return;
}
@@ -657,6 +662,12 @@ class LeAudioClientImpl : public LeAudioClient {
return;
}
+ if (leAudioDevice->conn_id_ != GATT_INVALID_CONN_ID) {
+ Disconnect(address);
+ leAudioDevice->removing_device_ = true;
+ return;
+ }
+
/* Remove the group assignment if not yet removed. It might happen that the
* group module has already called the appropriate callback and we have
* already removed the group assignment.
@@ -666,12 +677,6 @@ class LeAudioClientImpl : public LeAudioClient {
group_remove_node(group, address, true);
}
- if (leAudioDevice->conn_id_ != GATT_INVALID_CONN_ID) {
- Disconnect(address);
- leAudioDevice->removing_device_ = true;
- return;
- }
-
leAudioDevices_.Remove(address);
}
@@ -718,7 +723,30 @@ class LeAudioClientImpl : public LeAudioClient {
group_add_node(group_id, address);
}
- if (autoconnect) Connect(address);
+ if (autoconnect) {
+ BTA_GATTC_Open(gatt_if_, address, false, false);
+ }
+ }
+
+ void BackgroundConnectIfGroupConnected(LeAudioDevice* leAudioDevice) {
+ DLOG(INFO) << __func__ << leAudioDevice->address_ ;
+ auto group = aseGroups_.FindById(leAudioDevice->group_id_);
+ if (!group) {
+ DLOG(INFO) << __func__ << " Device is not yet part of the group. ";
+ return;
+ }
+
+ if (!group->IsAnyDeviceConnected()) {
+ DLOG(INFO) << __func__ << " group: " << leAudioDevice->group_id_
+ << " is not connected";
+ return;
+ }
+
+ DLOG(INFO) << __func__ << "Add " << leAudioDevice->address_
+ << " to background connect to connected group: "
+ << leAudioDevice->group_id_;
+
+ BTA_GATTC_Open(gatt_if_, leAudioDevice->address_, false, false);
}
void Disconnect(const RawAddress& address) override {
@@ -731,19 +759,23 @@ class LeAudioClientImpl : public LeAudioClient {
}
/* cancel pending direct connect */
- if (leAudioDevice->connecting_actively_)
+ if (leAudioDevice->connecting_actively_) {
BTA_GATTC_CancelOpen(gatt_if_, address, true);
+ leAudioDevice->connecting_actively_ = false;
+ }
/* Removes all registrations for connection */
BTA_GATTC_CancelOpen(0, address, false);
- if (leAudioDevice->conn_id_ == GATT_INVALID_CONN_ID) {
- LOG(ERROR) << __func__ << ", leAudioDevice not connected (" << address
- << ")";
+ if (leAudioDevice->conn_id_ != GATT_INVALID_CONN_ID) {
+ DisconnectDevice(leAudioDevice);
return;
}
- DisconnectDevice(leAudioDevice);
+ /* If this is a device which is a part of the group which is connected,
+ * lets start backgroup connect
+ */
+ BackgroundConnectIfGroupConnected(leAudioDevice);
}
void DisconnectDevice(LeAudioDevice* leAudioDevice,
@@ -1154,7 +1186,13 @@ class LeAudioClientImpl : public LeAudioClient {
leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID;
leAudioDevice->encrypted_ = false;
- if (leAudioDevice->removing_device_) leAudioDevices_.Remove(address);
+ if (leAudioDevice->removing_device_) {
+ if (leAudioDevice->group_id_ != bluetooth::groups::kGroupUnknown) {
+ auto group = aseGroups_.FindById(leAudioDevice->group_id_);
+ group_remove_node(group, address, true);
+ }
+ leAudioDevices_.Remove(address);
+ }
}
bool subscribe_for_indications(uint16_t conn_id, const RawAddress& address,
@@ -2130,15 +2168,23 @@ class LeAudioClientImpl : public LeAudioClient {
context_type, le_audio::types::kLeAudioDirectionSource);
if (source_configuration) {
+ bool send_active = false;
/* Stream configuration differs from previous one */
if (!current_source_codec_config.IsInvalid() &&
- (*source_configuration != current_source_codec_config))
+ (*source_configuration != current_source_codec_config)) {
+ callbacks_->OnGroupStatus(group_id, GroupStatus::INACTIVE);
+ send_active = true;
LeAudioClientAudioSource::Stop();
+ }
current_source_codec_config = *source_configuration;
LeAudioClientAudioSource::Start(current_source_codec_config,
audioSinkReceiver);
+ if (send_active) {
+ callbacks_->OnGroupStatus(group_id, GroupStatus::ACTIVE);
+ }
+
} else {
if (!current_source_codec_config.IsInvalid()) {
LeAudioClientAudioSource::Stop();
@@ -2152,15 +2198,22 @@ class LeAudioClientImpl : public LeAudioClient {
}
if (sink_configuration) {
+ bool send_active = false;
/* Stream configuration differs from previous one */
if (!current_sink_codec_config.IsInvalid() &&
- (*sink_configuration != current_sink_codec_config))
+ (*sink_configuration != current_sink_codec_config)) {
+ callbacks_->OnGroupStatus(group_id, GroupStatus::INACTIVE);
+ send_active = true;
LeAudioClientAudioSink::Stop();
+ }
current_sink_codec_config = *sink_configuration;
LeAudioClientAudioSink::Start(current_sink_codec_config,
audioSourceReceiver);
+ if (send_active) {
+ callbacks_->OnGroupStatus(group_id, GroupStatus::ACTIVE);
+ }
} else {
if (!current_sink_codec_config.IsInvalid()) {
LeAudioClientAudioSink::Stop();
@@ -2482,6 +2535,7 @@ class LeAudioClientImpl : public LeAudioClient {
auto* evt = static_cast<cig_remove_cmpl_evt*>(data);
LeAudioDeviceGroup* group = aseGroups_.FindById(evt->cig_id);
groupStateMachine_->ProcessHciNotifOnCigRemove(evt->status, group);
+ remove_group_if_possible(group);
} break;
default:
LOG(ERROR) << __func__ << " Invalid event " << int{event_type};
diff --git a/bta/le_audio/hal_verifier.cc b/bta/le_audio/hal_verifier.cc
index 6fd2c6641..921071443 100644
--- a/bta/le_audio/hal_verifier.cc
+++ b/bta/le_audio/hal_verifier.cc
@@ -18,6 +18,6 @@
#include "bta_le_audio_api.h"
bool LeAudioHalVerifier::SupportsLeAudio() {
- return bluetooth::audio::HalVersionManager::GetHalVersion() ==
+ return bluetooth::audio::HalVersionManager::GetHalVersion() >=
bluetooth::audio::BluetoothAudioHalVersion::VERSION_2_1;
}
diff --git a/bta/le_audio/le_audio_client_test.cc b/bta/le_audio/le_audio_client_test.cc
index 7a0b31858..ce15362bb 100644
--- a/bta/le_audio/le_audio_client_test.cc
+++ b/bta/le_audio/le_audio_client_test.cc
@@ -528,6 +528,10 @@ class UnicastTestNoInit : public Test {
state_machine_callbacks_->StatusReportCb(
group->group_id_, GroupStreamStatus::STREAMING);
streaming_groups[group->group_id_] = group;
+
+ /* Assume CIG is created */
+ group->cig_created_ = true;
+
return true;
});
@@ -554,8 +558,8 @@ class UnicastTestNoInit : public Test {
});
ON_CALL(mock_state_machine_, ProcessHciNotifAclDisconnected(_, _))
- .WillByDefault([](LeAudioDeviceGroup* group,
- LeAudioDevice* leAudioDevice) {
+ .WillByDefault([this](LeAudioDeviceGroup* group,
+ LeAudioDevice* leAudioDevice) {
if (!group) return;
auto* stream_conf = &group->stream_conf;
if (stream_conf->valid) {
@@ -584,6 +588,11 @@ class UnicastTestNoInit : public Test {
stream_conf->valid = false;
}
}
+
+ if (group->IsEmpty()) {
+ group->cig_created_ = false;
+ InjectCigRemoved(group->group_id_);
+ }
});
ON_CALL(mock_state_machine_, ProcessHciNotifCisDisconnected(_, _, _))
@@ -1548,6 +1557,16 @@ class UnicastTestNoInit : public Test {
bluetooth::hci::iso_manager::kIsoEventCisDisconnected, &cis_evt);
}
+ void InjectCigRemoved(uint8_t cig_id) {
+ bluetooth::hci::iso_manager::cig_remove_cmpl_evt evt;
+ evt.status = 0;
+ evt.cig_id = cig_id;
+
+ ASSERT_NE(cig_callbacks_, nullptr);
+ cig_callbacks_->OnCisEvent(
+ bluetooth::hci::iso_manager::kIsoEventCigOnRemoveCmpl, &evt);
+ }
+
MockLeAudioClientCallbacks mock_client_callbacks_;
MockLeAudioClientAudioSource mock_audio_source_;
MockLeAudioClientAudioSink mock_audio_sink_;
@@ -1847,7 +1866,7 @@ TEST_F(UnicastTestNoInit, LoadStoredEarbudsCsisGrouped) {
.Times(1);
ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address0, _))
.WillByDefault(DoAll(Return(true)));
- EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, true, _))
+ EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, false, _))
.Times(1);
// Expect stored device1 to connect automatically
@@ -1856,7 +1875,7 @@ TEST_F(UnicastTestNoInit, LoadStoredEarbudsCsisGrouped) {
.Times(1);
ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address1, _))
.WillByDefault(DoAll(Return(true)));
- EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address1, true, _))
+ EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address1, false, _))
.Times(1);
ON_CALL(mock_groups_module_, GetGroupId(_, _))
@@ -1942,7 +1961,7 @@ TEST_F(UnicastTestNoInit, LoadStoredEarbudsCsisGroupedDifferently) {
.Times(1);
ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address0, _))
.WillByDefault(DoAll(Return(true)));
- EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, true, _))
+ EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, false, _))
.Times(1);
// Expect stored device1 to NOT connect automatically
@@ -1951,7 +1970,7 @@ TEST_F(UnicastTestNoInit, LoadStoredEarbudsCsisGroupedDifferently) {
.Times(0);
ON_CALL(mock_btm_interface_, BTM_IsEncrypted(test_address1, _))
.WillByDefault(DoAll(Return(true)));
- EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address1, true, _))
+ EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address1, false, _))
.Times(0);
// Initialize
@@ -2227,6 +2246,69 @@ TEST_F(UnicastTest, RemoveTwoEarbudsCsisGrouped) {
Mock::VerifyAndClearExpectations(&mock_btif_storage_);
}
+TEST_F(UnicastTest, RemoveWhileStreaming) {
+ const RawAddress test_address0 = GetTestAddress(0);
+ int group_id = bluetooth::groups::kGroupUnknown;
+
+ SetSampleDatabaseEarbudsValid(
+ 1, test_address0, codec_spec_conf::kLeAudioLocationStereo,
+ codec_spec_conf::kLeAudioLocationStereo, false /*add_csis*/,
+ true /*add_cas*/, true /*add_pacs*/, true /*add_ascs*/, 1 /*set_size*/,
+ 0 /*rank*/);
+ EXPECT_CALL(mock_client_callbacks_,
+ OnConnectionState(ConnectionState::CONNECTED, test_address0))
+ .Times(1);
+ EXPECT_CALL(mock_client_callbacks_,
+ OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED))
+ .WillOnce(DoAll(SaveArg<1>(&group_id)));
+
+ ConnectLeAudio(test_address0);
+ ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown);
+
+ // Start streaming
+ uint8_t cis_count_out = 1;
+ uint8_t cis_count_in = 0;
+
+ EXPECT_CALL(mock_audio_source_, Start(_, _)).Times(1);
+ LeAudioClient::Get()->GroupSetActive(group_id);
+
+ EXPECT_CALL(mock_state_machine_, StartStream(_, _)).Times(1);
+
+ StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id);
+
+ SyncOnMainLoop();
+ Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+ Mock::VerifyAndClearExpectations(&mock_audio_source_);
+ Mock::VerifyAndClearExpectations(&mock_state_machine_);
+ SyncOnMainLoop();
+
+ // Verify Data transfer on one audio source cis
+ TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
+
+ EXPECT_CALL(mock_groups_module_, RemoveDevice(test_address0, group_id))
+ .Times(1);
+
+ LeAudioDeviceGroup* group = nullptr;
+ EXPECT_CALL(mock_state_machine_, ProcessHciNotifAclDisconnected(_, _))
+ .WillOnce(DoAll(SaveArg<0>(&group)));
+ EXPECT_CALL(
+ mock_client_callbacks_,
+ OnGroupNodeStatus(test_address0, group_id, GroupNodeStatus::REMOVED));
+
+ EXPECT_CALL(mock_client_callbacks_,
+ OnConnectionState(ConnectionState::DISCONNECTED, test_address0))
+ .Times(1);
+
+ LeAudioClient::Get()->RemoveDevice(test_address0);
+
+ SyncOnMainLoop();
+ Mock::VerifyAndClearExpectations(&mock_groups_module_);
+ Mock::VerifyAndClearExpectations(&mock_state_machine_);
+ Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+
+ ASSERT_NE(group, nullptr);
+}
+
TEST_F(UnicastTest, SpeakerStreaming) {
const RawAddress test_address0 = GetTestAddress(0);
int group_id = bluetooth::groups::kGroupUnknown;
diff --git a/bta/le_audio/le_audio_types.h b/bta/le_audio/le_audio_types.h
index 4d0dfbef1..b55b23880 100644
--- a/bta/le_audio/le_audio_types.h
+++ b/bta/le_audio/le_audio_types.h
@@ -24,6 +24,7 @@
#include <stdint.h>
+#include <bitset>
#include <map>
#include <optional>
#include <string>
diff --git a/bta/pan/bta_pan_int.h b/bta/pan/bta_pan_int.h
index efe81c1a6..0c0c4893c 100644
--- a/bta/pan/bta_pan_int.h
+++ b/bta/pan/bta_pan_int.h
@@ -103,6 +103,17 @@ typedef struct {
} tBTA_PAN_CONN;
+/* pan data param */
+typedef struct {
+ BT_HDR_RIGID hdr;
+ RawAddress src;
+ RawAddress dst;
+ uint16_t protocol;
+ bool ext;
+ bool forward;
+
+} tBTA_PAN_DATA_PARAMS;
+
/* union of all data types */
typedef union {
BT_HDR_RIGID hdr;
@@ -111,6 +122,7 @@ typedef union {
tBTA_PAN_API_OPEN api_open;
tBTA_PAN_CI_TX_FLOW ci_tx_flow;
tBTA_PAN_CONN conn;
+ tBTA_PAN_DATA_PARAMS params;
} tBTA_PAN_DATA;
/* state machine control block */
@@ -140,17 +152,6 @@ typedef struct {
} tBTA_PAN_CB;
-/* pan data param */
-typedef struct {
- BT_HDR_RIGID hdr;
- RawAddress src;
- RawAddress dst;
- uint16_t protocol;
- bool ext;
- bool forward;
-
-} tBTA_PAN_DATA_PARAMS;
-
/*****************************************************************************
* Global data
****************************************************************************/
diff --git a/test/mock/mock_hci_packet_parser.cc b/bta/test/bta_pan_test.cc
index d25725e43..a1a085e6d 100644
--- a/test/mock/mock_hci_packet_parser.cc
+++ b/bta/test/bta_pan_test.cc
@@ -14,28 +14,24 @@
* limitations under the License.
*/
-/*
- * Generated mock file from original source file
- * Functions generated:3
- */
+#include <gtest/gtest.h>
+#include <string.h>
+#include <cstdint>
#include <map>
+#include <memory>
#include <string>
+#include "bta/pan/bta_pan_int.h"
+
+// TODO put this in common place
extern std::map<std::string, int> mock_function_count_map;
-#include "hci/include/hci_packet_parser.h"
+class BtaPanTest : public ::testing::Test {
+ protected:
+ void SetUp() override {}
-#ifndef UNUSED_ATTR
-#define UNUSED_ATTR
-#endif
+ void TearDown() override {}
+};
-const hci_packet_parser_t* hci_packet_parser_get_interface() {
- mock_function_count_map[__func__]++;
- return nullptr;
-}
-const hci_packet_parser_t* hci_packet_parser_get_test_interface(
- allocator_t* buffer_allocator_interface) {
- mock_function_count_map[__func__]++;
- return nullptr;
-}
+TEST_F(BtaPanTest, nop) {}
diff --git a/bta/vc/vc.cc b/bta/vc/vc.cc
index dd558dc24..7bc7fabb5 100644
--- a/bta/vc/vc.cc
+++ b/bta/vc/vc.cc
@@ -159,7 +159,7 @@ class VolumeControlImpl : public VolumeControl {
return;
}
- LOG(INFO) << __func__ << " " << address << " status: " << success;
+ LOG(INFO) << __func__ << " " << address << " status: " << +success;
if (device->HasHandles()) {
device->EnqueueInitialRequests(gatt_if_, chrc_read_callback_static,
@@ -743,7 +743,7 @@ class VolumeControlImpl : public VolumeControl {
} break;
case BTA_GATTC_ENC_CMPL_CB_EVT:
- OnEncryptionComplete(p_data->enc_cmpl.remote_bda, true);
+ OnEncryptionComplete(p_data->enc_cmpl.remote_bda, BTM_SUCCESS);
break;
case BTA_GATTC_SRVC_CHG_EVT:
diff --git a/btcore/Android.bp b/btcore/Android.bp
index f96877f61..ea71c7914 100644
--- a/btcore/Android.bp
+++ b/btcore/Android.bp
@@ -8,8 +8,8 @@ package {
default_applicable_licenses: ["system_bt_license"],
}
-cc_library_static {
- name: "libbtcore",
+cc_defaults {
+ name: "libbtcore_defaults",
defaults: ["fluoride_defaults"],
local_include_dirs: ["include"],
include_dirs: [
@@ -35,6 +35,17 @@ cc_library_static {
},
}
+cc_library_static {
+ name: "libbtcore",
+ defaults: ["libbtcore_defaults"],
+}
+
+cc_library_static {
+ name: "libbtcore-static",
+ defaults: ["libbtcore_defaults"],
+ cflags: ["-DSTATIC_LIBBLUETOOTH"],
+}
+
cc_library_headers {
name: "libbtcore_headers",
defaults: ["libchrome_support_defaults"],
diff --git a/btcore/src/hal_util.cc b/btcore/src/hal_util.cc
index 616bd2b46..ab1a3b406 100644
--- a/btcore/src/hal_util.cc
+++ b/btcore/src/hal_util.cc
@@ -29,44 +29,6 @@
using base::StringPrintf;
-#define BLUETOOTH_LIBRARY_NAME "libbluetooth.so"
-
-#if !defined(STATIC_LIBBLUETOOTH)
-int hal_util_load_bt_library(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.
- void* handle = dlopen(BLUETOOTH_LIBRARY_NAME, RTLD_NOW);
- if (!handle) {
- const char* err_str = dlerror();
- LOG(ERROR) << __func__ << ": failed to load bluetooth library, error="
- << (err_str ? err_str : "error unknown");
- goto error;
- }
-
- // Get the address of the bt_interface_t.
- itf = (bt_interface_t*)dlsym(handle, sym);
- if (!itf) {
- LOG(ERROR) << __func__ << ": failed to load symbol from Bluetooth library "
- << sym;
- goto error;
- }
-
- // Success.
- LOG(INFO) << __func__ << " loaded HAL path=" << BLUETOOTH_LIBRARY_NAME
- << " btinterface=" << itf << " handle=" << handle;
-
- *interface = itf;
- return 0;
-
-error:
- *interface = NULL;
- if (handle) dlclose(handle);
-
- return -EINVAL;
-}
-#else
extern bt_interface_t bluetoothInterface;
int hal_util_load_bt_library(const bt_interface_t** interface) {
@@ -74,4 +36,3 @@ int hal_util_load_bt_library(const bt_interface_t** interface) {
return 0;
}
-#endif
diff --git a/btif/Android.bp b/btif/Android.bp
index 238f964e9..bbdcebddc 100644
--- a/btif/Android.bp
+++ b/btif/Android.bp
@@ -88,8 +88,8 @@ genrule {
}
// libbtif static library for target
-cc_library_static {
- name: "libbtif",
+cc_defaults {
+ name: "libbtif_defaults",
defaults: ["fluoride_defaults"],
include_dirs: btifCommonIncludes,
srcs: [
@@ -195,6 +195,17 @@ cc_library_static {
host_supported: true,
}
+cc_library_static {
+ name: "libbtif",
+ defaults: ["libbtif_defaults"],
+}
+
+cc_library_static {
+ name: "libbtif-static",
+ defaults: ["libbtif_defaults"],
+ cflags: ["-DSTATIC_LIBBLUETOOTH"],
+}
+
// btif unit tests for target
cc_test {
name: "net_test_btif",
diff --git a/btif/BUILD.gn b/btif/BUILD.gn
index 0100bb03d..26be562c3 100644
--- a/btif/BUILD.gn
+++ b/btif/BUILD.gn
@@ -102,6 +102,7 @@ static_library("btif") {
"//bt/device/include",
"//bt/embdrv/sbc/encoder/include",
"//bt/embdrv/sbc/decoder/include",
+ "//bt/gd",
"//bt/hci/include",
"//bt/stack/a2dp",
"//bt/stack/btm",
diff --git a/btif/include/btif_pan_internal.h b/btif/include/btif_pan_internal.h
index ca52ce7f9..e601a6c0a 100644
--- a/btif/include/btif_pan_internal.h
+++ b/btif/include/btif_pan_internal.h
@@ -60,8 +60,8 @@ typedef struct {
int state;
uint16_t protocol;
RawAddress peer;
- int local_role;
- int remote_role;
+ tBTA_PAN_ROLE local_role;
+ tBTA_PAN_ROLE remote_role;
RawAddress eth_addr;
} btpan_conn_t;
@@ -82,8 +82,8 @@ typedef struct {
******************************************************************************/
extern btpan_cb_t btpan_cb;
-btpan_conn_t* btpan_new_conn(int handle, const RawAddress& addr, int local_role,
- int peer_role);
+btpan_conn_t* btpan_new_conn(int handle, const RawAddress& addr,
+ tBTA_PAN_ROLE local_role, tBTA_PAN_ROLE peer_role);
btpan_conn_t* btpan_find_conn_addr(const RawAddress& addr);
btpan_conn_t* btpan_find_conn_handle(uint16_t handle);
void btpan_set_flow_control(bool enable);
diff --git a/btif/src/bluetooth.cc b/btif/src/bluetooth.cc
index 1fec5b54a..756374623 100644
--- a/btif/src/bluetooth.cc
+++ b/btif/src/bluetooth.cc
@@ -73,6 +73,7 @@
#include "common/os_utils.h"
#include "device/include/interop.h"
#include "gd/common/init_flags.h"
+#include "gd/os/parameter_provider.h"
#include "main/shim/dumpsys.h"
#include "main/shim/shim.h"
#include "osi/include/alarm.h"
@@ -178,8 +179,24 @@ static int init(bt_callbacks_t* callbacks, bool start_restricted,
set_hal_cbacks(callbacks);
restricted_mode = start_restricted;
- common_criteria_mode = is_common_criteria_mode;
- common_criteria_config_compare_result = config_compare_result;
+
+ if (bluetooth::shim::is_any_gd_enabled()) {
+ bluetooth::os::ParameterProvider::SetBtKeystoreInterface(
+ bluetooth::bluetooth_keystore::getBluetoothKeystoreInterface());
+ bluetooth::os::ParameterProvider::SetCommonCriteriaMode(
+ is_common_criteria_mode);
+ if (is_bluetooth_uid() && is_common_criteria_mode) {
+ bluetooth::os::ParameterProvider::SetCommonCriteriaConfigCompareResult(
+ config_compare_result);
+ } else {
+ bluetooth::os::ParameterProvider::SetCommonCriteriaConfigCompareResult(
+ CONFIG_COMPARE_ALL_PASS);
+ }
+ } else {
+ common_criteria_mode = is_common_criteria_mode;
+ common_criteria_config_compare_result = config_compare_result;
+ }
+
is_local_device_atv = is_atv;
stack_manager_get_interface()->init_stack();
diff --git a/btif/src/btif_dm.cc b/btif/src/btif_dm.cc
index a8b33a74f..3fd506c3d 100644
--- a/btif/src/btif_dm.cc
+++ b/btif/src/btif_dm.cc
@@ -36,6 +36,7 @@
#include <hardware/bt_csis.h>
#include <hardware/bt_hearing_aid.h>
#include <hardware/bt_le_audio.h>
+#include <hardware/bt_vc.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
@@ -51,6 +52,7 @@
#include "bta_dm_int.h"
#include "bta_gatt_api.h"
#include "bta_le_audio_api.h"
+#include "bta_vc_api.h"
#include "btif/include/stack_manager.h"
#include "btif_api.h"
#include "btif_av.h"
@@ -250,6 +252,8 @@ btif_hearing_aid_get_interface();
extern bluetooth::csis::CsisClientInterface* btif_csis_client_get_interface();
extern bluetooth::le_audio::LeAudioClientInterface*
btif_le_audio_get_interface();
+extern bluetooth::vc::VolumeControlInterface*
+btif_volume_control_get_interface();
/******************************************************************************
* Functions
@@ -303,6 +307,22 @@ bt_status_t btif_in_execute_service_request(tBTA_SERVICE_ID service_id,
case BTA_HIDD_SERVICE_ID: {
btif_hd_execute_service(b_enable);
} break;
+ case BTA_PBAP_SERVICE_ID:
+ FALLTHROUGH_INTENDED; /* FALLTHROUGH */
+ case BTA_PCE_SERVICE_ID:
+ FALLTHROUGH_INTENDED; /* FALLTHROUGH */
+ case BTA_MAP_SERVICE_ID:
+ FALLTHROUGH_INTENDED; /* FALLTHROUGH */
+ case BTA_MN_SERVICE_ID: {
+ /**
+ * Do nothing; these services were started elsewhere. However, we need to flow through this
+ * codepath in order to properly report back the local UUIDs back to adapter properties in
+ * Java. To achieve this, we need to catch these service IDs in order for {@link
+ * btif_in_execute_service_request} to return {@code BT_STATUS_SUCCESS}, so that in {@link
+ * btif_dm_enable_service} the check passes and the UUIDs are allowed to be passed up into
+ * the Java layer.
+ */
+ } break;
default:
BTIF_TRACE_ERROR("%s: Unknown service %d being %s", __func__, service_id,
(b_enable) ? "enabled" : "disabled");
@@ -1598,6 +1618,10 @@ static void btif_dm_upstreams_evt(uint16_t event, char* p_param) {
if (LeAudioClient::IsLeAudioClientRunning())
btif_le_audio_get_interface()->RemoveDevice(bd_addr);
+ if (VolumeControl::IsVolumeControlRunning()) {
+ btif_volume_control_get_interface()->RemoveDevice(bd_addr);
+ }
+
btif_storage_remove_bonded_device(&bd_addr);
bond_state_changed(BT_STATUS_SUCCESS, bd_addr, BT_BOND_STATE_NONE);
break;
@@ -1792,7 +1816,7 @@ static void bta_energy_info_cb(tBTM_BLE_TX_TIME_MS tx_time,
tBTM_BLE_RX_TIME_MS rx_time,
tBTM_BLE_IDLE_TIME_MS idle_time,
tBTM_BLE_ENERGY_USED energy_used,
- tBTA_DM_CONTRL_STATE ctrl_state,
+ tBTM_CONTRL_STATE ctrl_state,
tBTA_STATUS status) {
BTIF_TRACE_DEBUG(
"energy_info_cb-Status:%d,state=%d,tx_t=%ld, rx_t=%ld, "
diff --git a/btif/src/btif_keystore.cc b/btif/src/btif_keystore.cc
index dcc816b62..f9628c82f 100644
--- a/btif/src/btif_keystore.cc
+++ b/btif/src/btif_keystore.cc
@@ -16,16 +16,21 @@
/* BluetoothKeystore Interface */
-#include <btif_common.h>
-#include <btif_keystore.h>
-#include "btif_storage.h"
+#include "btif_keystore.h"
#include <base/bind.h>
#include <base/location.h>
#include <base/logging.h>
#include <hardware/bluetooth.h>
+
#include <map>
+#include "btif_common.h"
+#include "btif_storage.h"
+#include "gd/os/parameter_provider.h"
+#include "main/shim/config.h"
+#include "main/shim/shim.h"
+
using base::Bind;
using base::Unretained;
using bluetooth::bluetooth_keystore::BluetoothKeystoreCallbacks;
@@ -35,27 +40,45 @@ namespace bluetooth {
namespace bluetooth_keystore {
class BluetoothKeystoreInterfaceImpl;
std::unique_ptr<BluetoothKeystoreInterface> bluetoothKeystoreInstance;
+const int CONFIG_COMPARE_ALL_PASS = 0b11;
class BluetoothKeystoreInterfaceImpl
- : public bluetooth::bluetooth_keystore::BluetoothKeystoreInterface,
- public bluetooth::bluetooth_keystore::BluetoothKeystoreCallbacks {
+ : public bluetooth::bluetooth_keystore::BluetoothKeystoreInterface {
~BluetoothKeystoreInterfaceImpl() override = default;
void init(BluetoothKeystoreCallbacks* callbacks) override {
VLOG(2) << __func__;
this->callbacks = callbacks;
- // Get bonded devices number to get all bonded devices key.
+
+ bluetooth::os::ParameterProvider::SetCommonCriteriaConfigCompareResult(
+ CONFIG_COMPARE_ALL_PASS);
+ ConvertEncryptOrDecryptKeyIfNeeded();
+ }
+
+ void ConvertEncryptOrDecryptKeyIfNeeded() {
+ VLOG(2) << __func__;
+ if (!callbacks) {
+ LOG(INFO) << __func__ << " callback isn't ready.";
+ return;
+ }
+ if (bluetooth::shim::is_any_gd_enabled()) {
+ do_in_jni_thread(
+ FROM_HERE, base::Bind([]() {
+ shim::BtifConfigInterface::ConvertEncryptOrDecryptKeyIfNeeded();
+ }));
+ return;
+ }
do_in_jni_thread(
FROM_HERE, base::Bind([]() { btif_storage_get_num_bonded_devices(); }));
}
- void set_encrypt_key_or_remove_key(std::string prefix,
+ bool set_encrypt_key_or_remove_key(std::string prefix,
std::string decryptedString) override {
VLOG(2) << __func__ << " prefix: " << prefix;
if (!callbacks) {
LOG(WARNING) << __func__ << " callback isn't ready. prefix: " << prefix;
- return;
+ return false;
}
// Save the value into a map.
@@ -65,6 +88,7 @@ class BluetoothKeystoreInterfaceImpl
base::Bind(&bluetooth::bluetooth_keystore::BluetoothKeystoreCallbacks::
set_encrypt_key_or_remove_key,
base::Unretained(callbacks), prefix, decryptedString));
+ return true;
}
std::string get_key(std::string prefix) override {
diff --git a/btif/src/btif_pan.cc b/btif/src/btif_pan.cc
index e9ed488c4..d8c636bb4 100644
--- a/btif/src/btif_pan.cc
+++ b/btif/src/btif_pan.cc
@@ -165,44 +165,39 @@ static void btpan_jni_cleanup() {
jni_initialized = false;
}
-static inline int bta_role_to_btpan(int bta_pan_role) {
+static inline int bta_role_to_btpan(tBTA_PAN_ROLE bta_pan_role) {
int btpan_role = 0;
- BTIF_TRACE_DEBUG("bta_pan_role:0x%x", bta_pan_role);
if (bta_pan_role & PAN_ROLE_NAP_SERVER) btpan_role |= BTPAN_ROLE_PANNAP;
if (bta_pan_role & PAN_ROLE_CLIENT) btpan_role |= BTPAN_ROLE_PANU;
return btpan_role;
}
-static inline int btpan_role_to_bta(int btpan_role) {
- int bta_pan_role = PAN_ROLE_INACTIVE;
- BTIF_TRACE_DEBUG("btpan_role:0x%x", btpan_role);
+static inline tBTA_PAN_ROLE btpan_role_to_bta(int btpan_role) {
+ tBTA_PAN_ROLE bta_pan_role = PAN_ROLE_INACTIVE;
if (btpan_role & BTPAN_ROLE_PANNAP) bta_pan_role |= PAN_ROLE_NAP_SERVER;
if (btpan_role & BTPAN_ROLE_PANU) bta_pan_role |= PAN_ROLE_CLIENT;
return bta_pan_role;
}
-static volatile int btpan_dev_local_role;
+static tBTA_PAN_ROLE btpan_dev_local_role;
static tBTA_PAN_ROLE_INFO bta_panu_info = {PANU_SERVICE_NAME, 0};
static tBTA_PAN_ROLE_INFO bta_pan_nap_info = {PAN_NAP_SERVICE_NAME, 1};
static bt_status_t btpan_enable(int local_role) {
- BTIF_TRACE_DEBUG("%s - local_role: %d", __func__, local_role);
- int bta_pan_role = btpan_role_to_bta(local_role);
+ const tBTA_PAN_ROLE bta_pan_role = btpan_role_to_bta(local_role);
BTA_PanSetRole(bta_pan_role, &bta_panu_info, &bta_pan_nap_info);
btpan_dev_local_role = local_role;
return BT_STATUS_SUCCESS;
}
static int btpan_get_local_role() {
- BTIF_TRACE_DEBUG("btpan_dev_local_role:%d", btpan_dev_local_role);
- return btpan_dev_local_role;
+ return static_cast<int>(btpan_dev_local_role);
}
static bt_status_t btpan_connect(const RawAddress* bd_addr, int local_role,
int remote_role) {
- BTIF_TRACE_DEBUG("local_role:%d, remote_role:%d", local_role, remote_role);
- int bta_local_role = btpan_role_to_bta(local_role);
- int bta_remote_role = btpan_role_to_bta(remote_role);
+ tBTA_PAN_ROLE bta_local_role = btpan_role_to_bta(local_role);
+ tBTA_PAN_ROLE bta_remote_role = btpan_role_to_bta(remote_role);
btpan_new_conn(-1, *bd_addr, bta_local_role, bta_remote_role);
BTA_PanOpen(*bd_addr, bta_local_role, bta_remote_role);
return BT_STATUS_SUCCESS;
@@ -492,14 +487,15 @@ static void btpan_cleanup_conn(btpan_conn_t* conn) {
}
}
-btpan_conn_t* btpan_new_conn(int handle, const RawAddress& addr, int local_role,
- int remote_role) {
+btpan_conn_t* btpan_new_conn(int handle, const RawAddress& addr,
+ tBTA_PAN_ROLE local_role,
+ tBTA_PAN_ROLE remote_role) {
for (int i = 0; i < MAX_PAN_CONNS; i++) {
- BTIF_TRACE_DEBUG("conns[%d]:%d", i, btpan_cb.conns[i].handle);
if (btpan_cb.conns[i].handle == -1) {
- BTIF_TRACE_DEBUG("handle:%d, local_role:%d, remote_role:%d", handle,
- local_role, remote_role);
-
+ LOG_DEBUG(
+ "Allocated new pan connection handle:%d local_role:%hhu"
+ " remote_role:%hhu",
+ handle, local_role, remote_role);
btpan_cb.conns[i].handle = handle;
btpan_cb.conns[i].peer = addr;
btpan_cb.conns[i].local_role = local_role;
@@ -507,9 +503,8 @@ btpan_conn_t* btpan_new_conn(int handle, const RawAddress& addr, int local_role,
return &btpan_cb.conns[i];
}
}
- BTIF_TRACE_DEBUG("MAX_PAN_CONNS:%d exceeded, return NULL as failed",
- MAX_PAN_CONNS);
- return NULL;
+ LOG_WARN("Unable to create new pan connection max:%d", MAX_PAN_CONNS);
+ return nullptr;
}
void btpan_close_handle(btpan_conn_t* p) {
diff --git a/btif/src/btif_sdp_server.cc b/btif/src/btif_sdp_server.cc
index cab45200d..ba166b606 100644
--- a/btif/src/btif_sdp_server.cc
+++ b/btif/src/btif_sdp_server.cc
@@ -288,6 +288,38 @@ bt_status_t create_sdp_record(bluetooth_sdp_record* record,
bt_status_t remove_sdp_record(int record_id) {
int handle;
+ bluetooth_sdp_record* record;
+ bluetooth_sdp_types sdp_type = SDP_TYPE_RAW;
+ {
+ std::unique_lock<std::recursive_mutex> lock(sdp_lock);
+ record = sdp_slots[record_id].record_data;
+ if (record != NULL) {
+ sdp_type = record->hdr.type;
+ }
+ }
+ tBTA_SERVICE_ID service_id = -1;
+ switch (sdp_type) {
+ case SDP_TYPE_MAP_MAS:
+ service_id = BTA_MAP_SERVICE_ID;
+ break;
+ case SDP_TYPE_MAP_MNS:
+ service_id = BTA_MN_SERVICE_ID;
+ break;
+ case SDP_TYPE_PBAP_PSE:
+ service_id = BTA_PBAP_SERVICE_ID;
+ break;
+ case SDP_TYPE_PBAP_PCE:
+ service_id = BTA_PCE_SERVICE_ID;
+ break;
+ default:
+ /* other enumeration values were not enabled in {@link on_create_record_event} */
+ break;
+ }
+ if (service_id > 0) {
+ // {@link btif_disable_service} sets the mask {@link btif_enabled_services}.
+ btif_disable_service(service_id);
+ }
+
/* Get the Record handle, and free the slot */
handle = free_sdp_slot(record_id);
BTIF_TRACE_DEBUG("Sdp Server %s id=%d to handle=0x%08x", __func__, record_id,
@@ -317,6 +349,7 @@ void on_create_record_event(int id) {
* */
BTIF_TRACE_DEBUG("Sdp Server %s", __func__);
const sdp_slot_t* sdp_slot = start_create_sdp(id);
+ tBTA_SERVICE_ID service_id = -1;
/* In the case we are shutting down, sdp_slot is NULL */
if (sdp_slot != NULL) {
bluetooth_sdp_record* record = sdp_slot->record_data;
@@ -324,12 +357,15 @@ void on_create_record_event(int id) {
switch (record->hdr.type) {
case SDP_TYPE_MAP_MAS:
handle = add_maps_sdp(&record->mas);
+ service_id = BTA_MAP_SERVICE_ID;
break;
case SDP_TYPE_MAP_MNS:
handle = add_mapc_sdp(&record->mns);
+ service_id = BTA_MN_SERVICE_ID;
break;
case SDP_TYPE_PBAP_PSE:
handle = add_pbaps_sdp(&record->pse);
+ service_id = BTA_PBAP_SERVICE_ID;
break;
case SDP_TYPE_OPP_SERVER:
handle = add_opps_sdp(&record->ops);
@@ -339,6 +375,7 @@ void on_create_record_event(int id) {
break;
case SDP_TYPE_PBAP_PCE:
handle = add_pbapc_sdp(&record->pce);
+ service_id = BTA_PCE_SERVICE_ID;
break;
default:
BTIF_TRACE_DEBUG("Record type %d is not supported", record->hdr.type);
@@ -346,6 +383,18 @@ void on_create_record_event(int id) {
}
if (handle != -1) {
set_sdp_handle(id, handle);
+ if (service_id > 0) {
+ /**
+ * {@link btif_enable_service} calls {@link btif_dm_enable_service}, which calls {@link
+ * btif_in_execute_service_request}.
+ * - {@link btif_enable_service} sets the mask {@link btif_enabled_services}.
+ * - {@link btif_dm_enable_service} invokes the java callback to return uuids based
+ * on the enabled services mask.
+ * - {@link btif_in_execute_service_request} gates the java callback in {@link
+ * btif_dm_enable_service}.
+ */
+ btif_enable_service(service_id);
+ }
}
}
}
diff --git a/btif/src/btif_storage.cc b/btif/src/btif_storage.cc
index 38640e235..af84a225b 100644
--- a/btif/src/btif_storage.cc
+++ b/btif/src/btif_storage.cc
@@ -698,11 +698,27 @@ bt_status_t btif_storage_get_adapter_property(bt_property_t* property) {
*(p_uuid + num_uuids) = Uuid::From16Bit(UUID_SERVCLASS_AUDIO_SINK);
num_uuids++;
} break;
+ case BTA_PBAP_SERVICE_ID: {
+ *(p_uuid + num_uuids) = Uuid::From16Bit(UUID_SERVCLASS_PBAP_PSE);
+ num_uuids++;
+ } break;
case BTA_HFP_HS_SERVICE_ID: {
*(p_uuid + num_uuids) =
Uuid::From16Bit(UUID_SERVCLASS_HF_HANDSFREE);
num_uuids++;
} break;
+ case BTA_MAP_SERVICE_ID: {
+ *(p_uuid + num_uuids) = Uuid::From16Bit(UUID_SERVCLASS_MESSAGE_ACCESS);
+ num_uuids++;
+ } break;
+ case BTA_MN_SERVICE_ID: {
+ *(p_uuid + num_uuids) = Uuid::From16Bit(UUID_SERVCLASS_MESSAGE_NOTIFICATION);
+ num_uuids++;
+ } break;
+ case BTA_PCE_SERVICE_ID: {
+ *(p_uuid + num_uuids) = Uuid::From16Bit(UUID_SERVCLASS_PBAP_PCE);
+ num_uuids++;
+ } break;
}
}
}
diff --git a/btif/test/btif_core_test.cc b/btif/test/btif_core_test.cc
index 8f8335161..4512827e5 100644
--- a/btif/test/btif_core_test.cc
+++ b/btif/test/btif_core_test.cc
@@ -20,6 +20,7 @@
#include <map>
#include "bta/include/bta_ag_api.h"
+#include "btcore/include/module.h"
#include "btif/include/btif_api.h"
#include "btif/include/btif_common.h"
#include "types/raw_address.h"
@@ -32,6 +33,12 @@ uint8_t btu_trace_level = BT_TRACE_LEVEL_DEBUG;
const tBTA_AG_RES_DATA tBTA_AG_RES_DATA::kEmpty = {};
+module_t bt_utils_module;
+module_t gd_controller_module;
+module_t gd_idle_module;
+module_t gd_shim_module;
+module_t osi_module;
+
namespace {
auto timeout_time = std::chrono::seconds(3);
diff --git a/build/dpkg/floss/README.mkdn b/build/dpkg/floss/README.mkdn
new file mode 100644
index 000000000..bc3c423fe
--- /dev/null
+++ b/build/dpkg/floss/README.mkdn
@@ -0,0 +1,16 @@
+Debian 10
+
+build-dpkg:
+ - Builds a binary debian package
+
+package:
+ - Debian package
+
+`./build-dpkg` will verify, download, build, and install everything needed to build the floss dpkg.
+
+How to use:
+
+Run `sudo install-dependencies` first then run `build-dpkg`
+
+TODO:
+ - Figure out versioning for DEBIAN/control
diff --git a/build/dpkg/floss/build-dpkg b/build/dpkg/floss/build-dpkg
new file mode 100755
index 000000000..e917e9cd0
--- /dev/null
+++ b/build/dpkg/floss/build-dpkg
@@ -0,0 +1,183 @@
+#!/bin/bash
+
+DRY_RUN=""
+if [ $# -gt 0 ]; then
+ if [ "$1" == "--dry-run" ]; then
+ DRY_RUN="echo "
+ fi
+fi
+
+REQUIRED="git cargo"
+
+for name in $(echo ${REQUIRED});
+do
+ type -P "$name" &>/dev/null || { echo "Install '$name'" >&2; exit 1;}
+done
+
+FIRST_DIR="$(pwd)"
+
+# Vars
+URL_GN="http://commondatastorage.googleapis.com/chromeos-localmirror/distfiles/gn-3e43fac03281e2f5e5ae5f27c8e9a6bb45966ea9.bin"
+URL_PLATFORM2_GIT="https://chromium.googlesource.com/chromiumos/platform2"
+URL_RUST_CRATES_GIT="https://chromium.googlesource.com/chromiumos/third_party/rust_crates"
+URL_PROTO_LOGGING_GIT="https://android.googlesource.com/platform/frameworks/proto_logging"
+CHROMIUM_BRANCH="release-R92-13982.B"
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PARENT_DIR="$(echo ${SCRIPT_DIR} | rev | cut -d '/' -f 2- | rev )"
+TMP_DIR=$(mktemp -d)
+
+trap ctrl_c INT
+
+function ctrl_c() {
+ rm -rf "${TMP_DIR}"
+ exit 1
+}
+
+echo Generating source package in "${TMP_DIR}"
+OUT_DIR="${TMP_DIR}/out"
+BIN_DIR="${TMP_DIR}/bin"
+
+${DRY_RUN} mkdir -p "${OUT_DIR}"
+${DRY_RUN} mkdir -p "${BIN_DIR}"
+
+pushd "${BIN_DIR}"
+wget -O gn "${URL_GN}"
+popd
+export PATH="${PATH}:${BIN_DIR}"
+
+# Check dependencies
+# libchrome requires modp_b64
+APT_REQUIRED="modp-b64 libchrome flatbuffers-compiler flex g++-multilib gcc-multilib generate-ninja gnupg gperf libc++-dev libdbus-1-dev libevent-dev libevent-dev libflatbuffers-dev libflatbuffers1 libgl1-mesa-dev libglib2.0-dev liblz4-tool libncurses5 libnss3-dev libprotobuf-dev libre2-9 libssl-dev libtinyxml2-dev libx11-dev libxml2-utils ninja-build openssl protobuf-compiler unzip x11proto-core-dev xsltproc zip zlib1g-dev"
+
+# SPEED UP TEST, REMOVE ME
+APT_REQUIRED="modp-b64 libchrome flatbuffers-compiler"
+
+APT_MISSING=()
+for name in $(echo ${APT_REQUIRED});
+do
+ R="$(apt -qq list "${name}" 2>/dev/null | grep "installed")"
+ if [ "${R}" == "" ]; then
+ echo "Need to install '${name}'" >&2;
+ if [ "${name}" == "modp-b64" ]; then
+ echo "${name} source is available to build in this repository"
+ echo Run the following to build and install:
+ echo " pushd ${PARENT_DIR}/${name}/"
+ echo " ./gen-src-pkg.sh ${OUT_DIR}"
+ echo " sudo dpkg -i ${OUT_DIR}"/${name}*.deb || ctrl_c
+ echo " popd"
+ ${DRY_RUN} rm -rf ${TMP_DIR}
+ exit 1
+ elif [ "${name}" == "libchrome" ]; then
+ echo "${name} source is available to build in this repository"
+ echo Run the following to build and install:
+ echo pushd "${PARENT_DIR}/${name}/"
+ echo ./gen-src-pkg.sh "${OUT_DIR}"
+ echo sudo dpkg -i "${OUT_DIR}"/${name}*.deb || ctrl_c
+ echo popd
+ ${DRY_RUN} rm -rf ${TMP_DIR}
+ exit 1
+ else
+ APT_MISSING+=("${name}")
+ fi
+ fi
+done
+
+APT_MISSING_LEN="${#APT_MISSING[@]}"
+
+if [ $APT_MISSING_LEN -gt 0 ]; then
+ echo "Missing Packages:"
+ echo " ${APT_MISSING[*]}"
+ echo
+ echo Run the following to build and install:
+ echo " sudo apt install" "${APT_MISSING[*]}" || ctrl_c
+ ${DRY_RUN} rm -rf ${TMP_DIR}
+ exit 1
+fi
+
+# Check cargo for cxxbridge-cmd
+HAS_CXX="$(cargo install --list | grep cxxbridge-cmd)"
+if [ "$HAS_CXX" == "" ]; then
+ echo "Missing cxxbridge-cmd cargo package"
+ echo Run the following to build and install:
+ echo cargo install cxxbridge-cmd || ctrl_c
+ ${DRY_RUN} rm -rf ${TMP_DIR}
+ exit 1
+fi
+
+HAS_CXX="$(cargo install --list | grep cargo-proc-macro)"
+if [ "$HAS_CXX" == "" ]; then
+ echo "Missing cargo-proc-macro cargo package"
+ echo Run the following to build and install:
+ echo cargo install cargo-proc-macro || ctrl_c
+ ${DRY_RUN} rm -rf ${TMP_DIR}
+ exit 1
+fi
+
+# Git
+GIT_DIR="${OUT_DIR}/repos"
+GIT_DIR_PLATFORM2="${GIT_DIR}/platform2"
+GIT_DIR_PLATFORM2_COMMON_MK="${GIT_DIR_PLATFORM2}/common-mk"
+GIT_DIR_PLATFORM2_GN="${GIT_DIR_PLATFORM2}/.gn"
+GIT_DIR_RUST_CRATES="${GIT_DIR}/rust_crates"
+GIT_DIR_PROTO_LOGGING="${GIT_DIR}/proto_logging"
+GIT_DIR_BT="$(echo "${PARENT_DIR}" | rev | cut -d '/' -f 3- | rev)"
+
+# Staging
+STAGING_DIR="${OUT_DIR}/staging"
+STAGING_DIR_PLATFORM2="${STAGING_DIR}/platform2"
+STAGING_DIR_COMMON_MK="${STAGING_DIR}/common-mk"
+STAGING_DIR_GN="${STAGING_DIR}/.gn"
+STAGING_DIR_BT="${STAGING_DIR}/bt"
+# No it isn't a typo, use 'rust'
+STAGING_DIR_RUST_CRATES="${STAGING_DIR}/rust"
+STAGING_DIR_PROTO_LOGGING="${STAGING_DIR}/proto_logging"
+
+OUTPUT_DIR="${OUT_DIR}/output"
+EXTERNAL_DIR="${STAGING_DIR}/external"
+EXTERNAL_DIR_RUST="${EXTERNAL_DIR}/rust"
+EXTERNAL_DIR_PROTO_LOGGING="${EXTERNAL_DIR}/proto_logging"
+
+${DRY_RUN} mkdir -p "${GIT_DIR}"
+${DRY_RUN} mkdir -p "${STAGING_DIR}"
+${DRY_RUN} mkdir -p "${OUTPUT_DIR}"
+${DRY_RUN} mkdir -p "${EXTERNAL_DIR}"
+
+${DRY_RUN} git clone -b "${CHROMIUM_BRANCH}" "${URL_PLATFORM2_GIT}" "${GIT_DIR_PLATFORM2}"
+
+${DRY_RUN} git clone "${URL_RUST_CRATES_GIT}" "${GIT_DIR_RUST_CRATES}"
+${DRY_RUN} git clone "${URL_PROTO_LOGGING_GIT}" "${GIT_DIR_PROTO_LOGGING}"
+
+${DRY_RUN} ln -s "${GIT_DIR_PLATFORM2_COMMON_MK}" "${STAGING_DIR_COMMON_MK}" || ctrl_c
+${DRY_RUN} ln -s "${GIT_DIR_PLATFORM2_GN}" "${STAGING_DIR_GN}" || ctrl_c
+${DRY_RUN} ln -s "${GIT_DIR_BT}" "${STAGING_DIR_BT}" || ctrl_c
+${DRY_RUN} ln -s "${GIT_DIR_RUST_CRATES}" "${EXTERNAL_DIR_RUST}" || ctrl_c
+${DRY_RUN} ln -s "${GIT_DIR_PROTO_LOGGING}" "${EXTERNAL_DIR_PROTO_LOGGING}" || ctrl_c
+
+${DRY_RUN} "${GIT_DIR_BT}"/build.py --bootstrap-dir "$(readlink -f "${OUT_DIR}")" --libdir /usr/lib || ctrl_c
+
+PKG_DIR="${SCRIPT_DIR}/package"
+PKG_USR_DIR="${PKG_DIR}/usr"
+
+OUT_PKG_DIR="${OUT_DIR}/package"
+OUT_PKG_USR_DIR="${OUT_PKG_DIR}/usr"
+
+BIN_OUTPUT="${OUTPUT_DIR}/debug"
+
+BTCLIENT_BIN="${BIN_OUTPUT}/btclient"
+BTMANAGERD_BIN="${BIN_OUTPUT}/btmanagerd"
+BTADAPTERD_BIN="${BIN_OUTPUT}/btadapterd"
+
+${DRY_RUN} cp -r "${PKG_DIR}" "${OUT_DIR}/"
+
+${DRY_RUN} cp "${BTCLIENT_BIN}" "${OUT_PKG_USR_DIR}/bin/"
+${DRY_RUN} cp "${BTMANAGERD_BIN}" "${OUT_PKG_USR_DIR}/libexec/bluetooth/"
+${DRY_RUN} cp "${BTADAPTERD_BIN}" "${OUT_PKG_USR_DIR}/libexec/bluetooth/"
+
+${DRY_RUN} dpkg-deb --build "${OUT_PKG_DIR}" "${FIRST_DIR}/floss.deb"
+
+${DRY_RUN} rm -rf ${TMP_DIR}
+
+echo
+echo "Now run:"
+echo " sudo dpkg -i "${FIRST_DIR}"/floss.deb"
diff --git a/build/dpkg/floss/install-dependencies b/build/dpkg/floss/install-dependencies
new file mode 100755
index 000000000..be2fd20fd
--- /dev/null
+++ b/build/dpkg/floss/install-dependencies
@@ -0,0 +1,95 @@
+#!/bin/bash
+
+DRY_RUN=""
+if [ $# -gt 0 ]; then
+ if [ "$1" == "--dry-run" ]; then
+ DRY_RUN="echo "
+ fi
+fi
+
+echo "Checking for dependencies..."
+
+REQUIRED="git cargo"
+
+for name in $(echo ${REQUIRED});
+do
+ type -P "$name" &>/dev/null || { echo "Install '$name'" >&2; exit 1;}
+done
+
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PARENT_DIR="$(echo ${SCRIPT_DIR} | rev | cut -d '/' -f 2- | rev )"
+
+TMP_DIR=$(mktemp -d)
+OUT_DIR="${TMP_DIR}/out"
+
+trap ctrl_c INT
+
+function ctrl_c() {
+ rm -rf "${TMP_DIR}"
+ exit 1
+}
+
+# Check dependencies
+# libchrome requires modp_b64
+APT_REQUIRED="flatbuffers-compiler flex g++-multilib gcc-multilib generate-ninja \
+gnupg gperf libc++-dev libdbus-1-dev libevent-dev libevent-dev libflatbuffers-dev libflatbuffers1 \
+libgl1-mesa-dev libglib2.0-dev liblz4-tool libncurses5 libnss3-dev libprotobuf-dev libre2-9 \
+libssl-dev libtinyxml2-dev libx11-dev libxml2-utils ninja-build openssl protobuf-compiler unzip \
+x11proto-core-dev xsltproc zip zlib1g-dev debmake ninja-build modp-b64 libchrome"
+
+APT_MISSING=()
+for name in $(echo ${APT_REQUIRED});
+do
+ R="$(apt -qq list "${name}" 2>/dev/null | grep "installed")"
+ if [ "${R}" == "" ]; then
+ echo "Need to install '${name}'" >&2;
+ if [ "${name}" == "modp-b64" ]; then
+ echo "${name} source is available to build in this repository"
+ # dir name is different than package name :'(
+ ${DRY_RUN} pushd "${PARENT_DIR}/modp_b64/"
+ ${DRY_RUN} ./gen-src-pkg.sh "${OUT_DIR}"
+ ${DRY_RUN} sudo dpkg -i "${OUT_DIR}"/${name}*.deb || ctrl_c
+ ${DRY_RUN} popd
+ elif [ "${name}" == "libchrome" ]; then
+ echo "${name} source is available to build in this repository"
+ ${DRY_RUN} pushd "${PARENT_DIR}/${name}/"
+ ${DRY_RUN} ./gen-src-pkg.sh "${OUT_DIR}"
+ ${DRY_RUN} sudo dpkg -i "${OUT_DIR}"/${name}*.deb || ctrl_c
+ ${DRY_RUN} popd
+ else
+ APT_MISSING+=("${name}")
+ fi
+ fi
+done
+
+APT_MISSING_LEN="${#APT_MISSING[@]}"
+
+if [ $APT_MISSING_LEN -gt 0 ]; then
+ echo "Missing Packages:"
+ echo " ${APT_MISSING[*]}"
+ ${DRY_RUN} sudo apt install "${APT_MISSING[*]}" || ctrl_c
+else
+ rm -rf "${TMP_DIR}"
+ exit 0
+fi
+
+echo Generating missing packages in "${TMP_DIR}"
+
+# Check cargo for cxxbridge-cmd
+HAS_CXX="$(cargo install --list | grep cxxbridge-cmd)"
+if [ "$HAS_CXX" == "" ]; then
+ echo "Missing cxxbridge-cmd cargo package"
+ echo "Installing 'cxxbridge-cmd'" >&2
+ ${DRY_RUN} cargo install cxxbridge-cmd || ctrl_c
+fi
+
+HAS_CXX="$(cargo install --list | grep cargo-proc-macro)"
+if [ "$HAS_CXX" == "" ]; then
+ echo "Missing cargo-proc-macro cargo package"
+ echo "Installing 'cxxbridge-cmd'" >&2
+ ${DRY_RUN} cargo install cargo-proc-macro || ctrl_c
+fi
+
+rm -rf "${TMP_DIR}"
+echo "DONE"
diff --git a/build/dpkg/floss/package/DEBIAN/control b/build/dpkg/floss/package/DEBIAN/control
new file mode 100644
index 000000000..3bb5d35eb
--- /dev/null
+++ b/build/dpkg/floss/package/DEBIAN/control
@@ -0,0 +1,11 @@
+Package: floss
+Section: custom
+Priority: optional
+Maintainer: Martin Brabham <optedoblivion@google.com>
+Version: 0.1
+Homepage: https://www.google.com
+Depends: debmake, ninja-build, flatbuffers-compiler, flex, g++-multilib, gcc-multilib, generate-ninja, gnupg, gperf, libc++-dev, libdbus-1-dev, libevent-dev, libevent-dev, libflatbuffers-dev, libflatbuffers1, libgl1-mesa-dev, libglib2.0-dev, liblz4-tool, libncurses5, libnss3-dev, libprotobuf-dev, libre2-9, libssl-dev, libtinyxml2-dev, libx11-dev, libxml2-utils, ninja-build, openssl, protobuf-compiler, unzip, x11proto-core-dev, xsltproc, zip, zlib1g-dev, modp-b64, libchrome
+Architecture: all
+Essential: no
+Installed-Size: 490MB
+Description: The Fluoride Bluetooth stack on Linux
diff --git a/build/dpkg/floss/package/etc/dbus-1/system.d/org.chromium.bluetooth.conf b/build/dpkg/floss/package/etc/dbus-1/system.d/org.chromium.bluetooth.conf
new file mode 100644
index 000000000..4e226e7ad
--- /dev/null
+++ b/build/dpkg/floss/package/etc/dbus-1/system.d/org.chromium.bluetooth.conf
@@ -0,0 +1,37 @@
+<!DOCTYPE busconfig PUBLIC
+ "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+
+ <!-- Only root or user bluetooth can own the btmanagerd service -->
+ <policy user="bluetooth">
+ <allow own="org.chromium.bluetooth"/>
+ <allow own="org.chromium.bluetooth.Manager"/>
+ <allow own="org.chromium.bluetooth.ManagerCallback"/>
+ </policy>
+ <policy user="root">
+ <allow own="org.chromium.bluetooth"/>
+ <allow own="org.chromium.bluetooth.Manager"/>
+ <allow own="org.chromium.bluetooth.ManagerCallback"/>
+ </policy>
+
+ <!-- Allow anyone to invoke methods on btmanagerd server, -->
+ <!-- Will likely change this as the project matures -->
+ <policy context="default">
+ <allow send_destination="org.chromium.bluetooth"/>
+ <allow send_destination="org.chromium.bluetooth.Manager"/>
+ <allow send_destination="org.chromium.bluetooth.ManagerCallback"/>
+ </policy>
+
+ <!-- Allow access to everything to the group "bluetooth" -->
+ <policy group="bluetooth">
+ <allow send_destination="org.chromium.bluetooth"/>
+ <allow send_destination="org.chromium.bluetooth.Manager"/>
+ <allow send_destination="org.chromium.bluetooth.ManagerCallback"/>
+ </policy>
+ <policy user="root">
+ <allow send_destination="org.chromium.bluetooth"/>
+ <allow send_destination="org.chromium.bluetooth.Manager"/>
+ <allow send_destination="org.chromium.bluetooth.ManagerCallback"/>
+ </policy>
+</busconfig>
diff --git a/build/dpkg/floss/package/lib/systemd/system/btadapterd@.service b/build/dpkg/floss/package/lib/systemd/system/btadapterd@.service
new file mode 100644
index 000000000..e63c56d6c
--- /dev/null
+++ b/build/dpkg/floss/package/lib/systemd/system/btadapterd@.service
@@ -0,0 +1,22 @@
+[Unit]
+Description=Floss Bluetooth Adapter service
+Documentation=man:btadapterd(8)
+ConditionPathIsDirectory=/sys/class/bluetooth
+After=bluetooth.target btmanagerd.service
+
+[Service]
+Type=dbus
+BusName=org.chromium.bluetooth
+ExecStart=/usr/libexec/bluetooth/btadapterd --hci=%i
+ExecStartPost=/usr/bin/rm -f /var/run/bluetooth/bluetooth%i.pid
+TimeoutStopSec=3
+TimeoutStartSec=5
+NotifyAccess=main
+CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
+LimitNPROC=1
+ProtectHome=true
+ProtectSystem=full
+
+[Install]
+WantedBy=bluetooth.target btmanagerd.service
+Alias=dbus-org.btadapterd.service
diff --git a/build/dpkg/floss/package/lib/systemd/system/btmanagerd.service b/build/dpkg/floss/package/lib/systemd/system/btmanagerd.service
new file mode 100644
index 000000000..43bba3b25
--- /dev/null
+++ b/build/dpkg/floss/package/lib/systemd/system/btmanagerd.service
@@ -0,0 +1,21 @@
+[Unit]
+Description=Floss Bluetooth service
+Documentation=man:btmanagerd(8)
+ConditionPathIsDirectory=/sys/class/bluetooth
+After=bluetooth.target
+
+[Service]
+Type=dbus
+BusName=org.chromium.bluetooth.Manager
+ExecStart=/usr/libexec/bluetooth/btmanagerd --systemd
+NotifyAccess=main
+CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
+LimitNPROC=1
+ProtectHome=true
+ProtectSystem=full
+TimeoutStopSec=3
+TimeoutStartSec=5
+
+[Install]
+WantedBy=bluetooth.target
+Alias=dbus-org.btmanagerd.service
diff --git a/gd/Android.bp b/gd/Android.bp
index fadab2658..46cd7d97c 100644
--- a/gd/Android.bp
+++ b/gd/Android.bp
@@ -118,6 +118,18 @@ cc_defaults {
],
}
+// Clang is targeted for android core libraries but other base libraries
+// may not support clang tidy recommendations (e.g. MacOS)
+cc_defaults {
+ name: "gd_clang_tidy_ignore_android",
+ tidy: true,
+ tidy_checks: [
+ "-android-cloexec-pipe2", // warning: 'pipe2' should use O_CLOEXEC where possible
+ "-android-cloexec-accept", // warning: prefer accept4() to accept() because accept4() allows SOCK_CLOEXEC
+ "-android-cloexec-socket", // warning: 'pipe2' should use O_CLOEXEC where possible
+ ],
+}
+
cc_defaults {
name: "libbluetooth_gd_defaults",
defaults: [
diff --git a/gd/BUILD.gn b/gd/BUILD.gn
index eb6b00217..d12a3802d 100644
--- a/gd/BUILD.gn
+++ b/gd/BUILD.gn
@@ -48,7 +48,7 @@ group("gd_default_deps") {
deps = [
"//bt/gd:BluetoothGeneratedDumpsysDataSchema_h",
"//bt/gd:BluetoothGeneratedPackets_h",
- "//bt/gd/dumpsys:BluetoothGeneratedDumpsysBundledSchema_cc",
+ "//bt/gd/dumpsys:libbluetooth-dumpsys",
"//bt/gd/rust/shim:init_flags_bridge_header",
]
}
diff --git a/gd/btaa/android/activity_attribution.cc b/gd/btaa/android/activity_attribution.cc
index 34d4a3691..19c0df11f 100644
--- a/gd/btaa/android/activity_attribution.cc
+++ b/gd/btaa/android/activity_attribution.cc
@@ -46,41 +46,62 @@ static const std::string kBtWakelockName("hal_bluetooth_lock");
static const std::string kBtWakeupReason("hs_uart_wakeup");
static const size_t kHciAclHeaderSize = 4;
+static std::mutex g_module_mutex;
+static ActivityAttribution* g_module = nullptr;
+static bool is_wakeup_callback_registered = false;
+static bool is_wakelock_callback_registered = false;
+
struct wakelock_callback : public BnWakelockCallback {
- wakelock_callback(ActivityAttribution* module) : module_(module) {}
+ wakelock_callback() {}
Status notifyAcquired() override {
- module_->OnWakelockAcquired();
+ std::lock_guard<std::mutex> guard(g_module_mutex);
+ if (g_module != nullptr) {
+ g_module->OnWakelockAcquired();
+ }
return Status::ok();
}
Status notifyReleased() override {
- module_->OnWakelockReleased();
+ std::lock_guard<std::mutex> guard(g_module_mutex);
+ if (g_module != nullptr) {
+ g_module->OnWakelockReleased();
+ }
return Status::ok();
}
-
- ActivityAttribution* module_;
};
+static std::shared_ptr<wakelock_callback> g_wakelock_callback = nullptr;
+
struct wakeup_callback : public BnSuspendCallback {
- wakeup_callback(ActivityAttribution* module) : module_(module) {}
+ wakeup_callback() {}
Status notifyWakeup(bool success, const std::vector<std::string>& wakeup_reasons) override {
for (auto& wakeup_reason : wakeup_reasons) {
if (wakeup_reason.find(kBtWakeupReason) != std::string::npos) {
- module_->OnWakeup();
+ std::lock_guard<std::mutex> guard(g_module_mutex);
+ if (g_module != nullptr) {
+ g_module->OnWakeup();
+ }
break;
}
}
return Status::ok();
}
-
- ActivityAttribution* module_;
};
+static std::shared_ptr<wakeup_callback> g_wakeup_callback = nullptr;
+
struct ActivityAttribution::impl {
impl(ActivityAttribution* module) {
- bool is_registered = false;
+ std::lock_guard<std::mutex> guard(g_module_mutex);
+ g_module = module;
+ if (is_wakeup_callback_registered && is_wakelock_callback_registered) {
+ LOG_ERROR("Wakeup and wakelock callbacks are already registered");
+ return;
+ }
+ Status register_callback_status;
+ bool is_register_successful = false;
auto control_service =
ISuspendControlService::fromBinder(SpAIBinder(AServiceManager_getService("suspend_control")));
if (!control_service) {
@@ -88,21 +109,33 @@ struct ActivityAttribution::impl {
return;
}
- Status register_callback_status =
- control_service->registerCallback(SharedRefBase::make<wakeup_callback>(module), &is_registered);
- if (!is_registered || !register_callback_status.isOk()) {
- LOG_ERROR("Fail to register wakeup callback");
- return;
+ if (!is_wakeup_callback_registered) {
+ g_wakeup_callback = SharedRefBase::make<wakeup_callback>();
+ register_callback_status = control_service->registerCallback(g_wakeup_callback, &is_register_successful);
+ if (!is_register_successful || !register_callback_status.isOk()) {
+ LOG_ERROR("Fail to register wakeup callback");
+ return;
+ }
+ is_wakeup_callback_registered = true;
}
- register_callback_status = control_service->registerWakelockCallback(
- SharedRefBase::make<wakelock_callback>(module), kBtWakelockName, &is_registered);
- if (!is_registered || !register_callback_status.isOk()) {
- LOG_ERROR("Fail to register wakelock callback");
- return;
+ if (!is_wakelock_callback_registered) {
+ g_wakelock_callback = SharedRefBase::make<wakelock_callback>();
+ register_callback_status =
+ control_service->registerWakelockCallback(g_wakelock_callback, kBtWakelockName, &is_register_successful);
+ if (!is_register_successful || !register_callback_status.isOk()) {
+ LOG_ERROR("Fail to register wakelock callback");
+ return;
+ }
+ is_wakelock_callback_registered = true;
}
}
+ ~impl() {
+ std::lock_guard<std::mutex> guard(g_module_mutex);
+ g_module = nullptr;
+ }
+
void on_hci_packet(hal::HciPacket packet, hal::SnoopLogger::PacketType type, uint16_t length) {
attribution_processor_.OnBtaaPackets(std::move(hci_processor_.OnHciPacket(std::move(packet), type, length)));
}
diff --git a/gd/cert/event_stream.py b/gd/cert/event_stream.py
index 1115c8bed..3a71935e9 100644
--- a/gd/cert/event_stream.py
+++ b/gd/cert/event_stream.py
@@ -197,7 +197,8 @@ class EventStream(IEventStream, Closable):
except Empty:
continue
logging.debug("Done waiting, got %d events" % len(event_list))
- asserts.assert_true(
+ assert_true(
+ self,
len(event_list) <= at_most_times,
msg=("Expected at most %d events, but got %d" % (at_most_times, len(event_list))))
@@ -209,6 +210,12 @@ def static_remaining_time_delta(end_time):
return remaining
+def assert_true(istream, expr, msg, extras=None):
+ if not expr:
+ istream.close()
+ asserts.fail(msg, extras)
+
+
def NOT_FOR_YOU_assert_event_occurs(istream,
match_fn,
at_least_times=1,
@@ -227,7 +234,9 @@ def NOT_FOR_YOU_assert_event_occurs(istream,
except Empty:
continue
logging.debug("Done waiting for event, received %d", len(event_list))
- asserts.assert_true(
+
+ assert_true(
+ istream,
len(event_list) >= at_least_times,
msg=("Expected at least %d events, but got %d" % (at_least_times, len(event_list))))
@@ -252,7 +261,8 @@ def NOT_FOR_YOU_assert_all_events_occur(istream,
except Empty:
continue
logging.debug("Done waiting for event")
- asserts.assert_true(
+ assert_true(
+ istream,
len(matched_order) == len(match_fns),
msg=("Expected at least %d events, but got %d" % (len(match_fns), len(matched_order))))
if order_matters:
@@ -263,7 +273,7 @@ def NOT_FOR_YOU_assert_all_events_occur(istream,
correct_order = False
break
i += 1
- asserts.assert_true(correct_order, "Events not received in correct order %s %s" % (match_fns, matched_order))
+ assert_true(istream, correct_order, "Events not received in correct order %s %s" % (match_fns, matched_order))
def NOT_FOR_YOU_assert_none_matching(istream, match_fn, timeout):
@@ -282,13 +292,13 @@ def NOT_FOR_YOU_assert_none_matching(istream, match_fn, timeout):
logging.debug("Done waiting for an event")
if event is None:
return # Avoid an assert in MessageToString(None, ...)
- asserts.assert_true(event is None, msg='Expected None matching, but got {}'.format(pretty_print(event)))
+ assert_true(istream, event is None, msg='Expected None matching, but got {}'.format(pretty_print(event)))
def NOT_FOR_YOU_assert_none(istream, timeout):
logging.debug("assert_none %fs" % (timeout.total_seconds()))
try:
event = istream.get_event_queue().get(timeout=timeout.total_seconds())
- asserts.assert_true(event is None, msg='Expected None, but got {}'.format(pretty_print(event)))
+ assert_true(istream, event is None, msg='Expected None, but got {}'.format(pretty_print(event)))
except Empty:
return
diff --git a/gd/cert/gd_device_lib.py b/gd/cert/gd_device_lib.py
index a34c0e12c..f21d9401c 100644
--- a/gd/cert/gd_device_lib.py
+++ b/gd/cert/gd_device_lib.py
@@ -243,7 +243,7 @@ class GdDeviceBaseCore(ABC):
def get_coverage_profdata_path_for_host(test_runner_base_path, type_identifier, label) -> pathlib.Path:
- return pathlib.Path(test_runner_base_path).parent.joinpath(
+ return pathlib.Path(test_runner_base_path).parent.parent.joinpath(
"%s_%s_backing_process_coverage.profdata" % (type_identifier, label))
@@ -294,7 +294,7 @@ def generate_coverage_report_for_host(coverage_info):
logging.info("[%s] Skip coverage report as llvm-cov is not found at %s" % (label, str(llvm_cov)))
return
logging.info("[%s] Generating coverage report in JSON" % label)
- coverage_result_path = pathlib.Path(test_runner_base_path).parent.joinpath(
+ coverage_result_path = pathlib.Path(test_runner_base_path).parent.parent.joinpath(
"%s_%s_backing_process_coverage.json" % (type_identifier, label))
with coverage_result_path.open("w") as coverage_result_file:
llvm_cov_export_cmd = [
@@ -310,7 +310,7 @@ def generate_coverage_report_for_host(coverage_info):
coverage_result_path.unlink(missing_ok=True)
return
logging.info("[%s] Generating coverage summary in text" % label)
- coverage_summary_path = pathlib.Path(test_runner_base_path).parent.joinpath(
+ coverage_summary_path = pathlib.Path(test_runner_base_path).parent.parent.joinpath(
"%s_%s_backing_process_coverage_summary.txt" % (type_identifier, label))
with coverage_summary_path.open("w") as coverage_summary_file:
llvm_cov_report_cmd = [
diff --git a/gd/cert/py_le_acl_manager.py b/gd/cert/py_le_acl_manager.py
index c52a5abbc..18ba9d34e 100644
--- a/gd/cert/py_le_acl_manager.py
+++ b/gd/cert/py_le_acl_manager.py
@@ -23,6 +23,7 @@ from cert.closable import Closable
from cert.closable import safeClose
from bluetooth_packets_python3 import hci_packets
from cert.truth import assertThat
+from datetime import timedelta
from hci.facade import le_acl_manager_facade_pb2 as le_acl_manager_facade
@@ -98,6 +99,14 @@ class PyLeAclManager(Closable):
self.listen_for_incoming_connections()
return self.complete_incoming_connection()
+ def wait_for_connection_fail(self, token):
+ assertThat(self.outgoing_connection_event_streams[token]).isNotNone()
+ event_stream = self.outgoing_connection_event_streams[token][0]
+ connection_fail = HciCaptures.LeConnectionCompleteCapture()
+ assertThat(event_stream).emits(connection_fail, timeout=timedelta(seconds=35))
+ complete = connection_fail.get()
+ assertThat(complete.GetStatus() == hci_packets.ErrorCode.CONNECTION_ACCEPT_TIMEOUT).isTrue()
+
def cancel_connection(self, token):
assertThat(token in self.outgoing_connection_event_streams).isTrue()
pair = self.outgoing_connection_event_streams.pop(token)
@@ -112,6 +121,14 @@ class PyLeAclManager(Closable):
self.next_token += 1
return token
+ def initiate_background_and_direct_connection(self, remote_addr):
+ assertThat(self.next_token in self.outgoing_connection_event_streams).isFalse()
+ self.outgoing_connection_event_streams[self.next_token] = EventStream(
+ self.le_acl_manager.CreateBackgroundAndDirectConnection(remote_addr)), remote_addr
+ token = self.next_token
+ self.next_token += 1
+ return token
+
def complete_connection(self, event_stream):
connection_complete = HciCaptures.LeConnectionCompleteCapture()
assertThat(event_stream).emits(connection_complete)
diff --git a/gd/common/strings.h b/gd/common/strings.h
index 473edd001..3c0d314cc 100644
--- a/gd/common/strings.h
+++ b/gd/common/strings.h
@@ -49,7 +49,7 @@ std::string ToHexString(T x) {
}
template <>
-inline std::string ToHexString<signed long>(signed long x) {
+inline std::string ToHexString<>(signed long x) {
if (x < 0) {
if (x == LONG_MIN) return "LONG_MIN";
return "-" + ToHexString<signed long>(-x);
@@ -60,6 +60,14 @@ inline std::string ToHexString<signed long>(signed long x) {
return tmp.str();
}
+template <>
+inline std::string ToHexString<>(unsigned int x) {
+ std::stringstream tmp;
+ tmp << "0x" << std::internal << std::hex << std::setfill('0') << std::setw(sizeof(unsigned int) * 2)
+ << (unsigned long)x;
+ return tmp.str();
+}
+
// Convert value into a hex decimal formatted string in lower case, prefixed with 0s
template <class InputIt>
std::string ToHexString(InputIt first, InputIt last) {
diff --git a/gd/common/strings_test.cc b/gd/common/strings_test.cc
index 8a9ff1a87..7da1ccd08 100644
--- a/gd/common/strings_test.cc
+++ b/gd/common/strings_test.cc
@@ -78,6 +78,16 @@ TEST(StringsTest, to_hex_string_from_number) {
ASSERT_EQ(ToHexString('a'), "0x61");
}
+TEST(StringsTest, to_hex_string_from_number_unsigned_int) {
+ ASSERT_EQ(ToHexString(0U), "0x00000000");
+ ASSERT_EQ(ToHexString(1U), "0x00000001");
+ ASSERT_EQ(ToHexString(3U), "0x00000003");
+ ASSERT_EQ(ToHexString(25U), "0x00000019");
+ ASSERT_EQ(ToHexString(UINT_MAX), "0xffffffff");
+ ASSERT_EQ(ToHexString(1U + UINT_MAX), "0x00000000"); // Rolled over
+ ASSERT_EQ(ToHexString(2U + UINT_MAX), "0x00000001"); // Rolled over
+}
+
TEST(StringsTest, trim_string_test) {
ASSERT_EQ(StringTrim(" aa bb"), "aa bb");
ASSERT_EQ(StringTrim("aa bb "), "aa bb");
diff --git a/gd/dumpsys/BUILD.gn b/gd/dumpsys/BUILD.gn
index c55f1d58a..a280ec8e1 100644
--- a/gd/dumpsys/BUILD.gn
+++ b/gd/dumpsys/BUILD.gn
@@ -29,9 +29,10 @@ source_set("BluetoothDumpsysSources") {
deps = [ "//bt/gd:gd_default_deps" ]
}
-bt_flatc_bundler("BluetoothGeneratedDumpsysBundledSchema_cc") {
+bt_flatc_bundler("libbluetooth-dumpsys") {
root_name = "bluetooth.DumpsysData"
filename = "generated_dumpsys_bundled_schema"
namespace = "bluetooth::dumpsys"
deps = [ "//bt/gd:BluetoothGeneratedDumpsysBinarySchema_bfbs" ]
+ configs = [ "//bt/gd:gd_defaults" ]
}
diff --git a/gd/dumpsys/bundler/bundler.gni b/gd/dumpsys/bundler/bundler.gni
index d58697aaa..20207d5e5 100644
--- a/gd/dumpsys/bundler/bundler.gni
+++ b/gd/dumpsys/bundler/bundler.gni
@@ -144,7 +144,7 @@ template("bt_flatc_bundler") {
]
outputs = [
- "${target_gen_dir}/${invoker.filename}.h",
+ "${target_gen_dir}/${invoker.filename}.cc",
"${target_gen_dir}/${invoker.filename}",
]
@@ -158,20 +158,12 @@ template("bt_flatc_bundler") {
include_dirs = [ "${target_gen_dir}" ]
}
- generated_file(target_name) {
- outputs = [ "${target_gen_dir}/${target_name}.files" ]
- output_conversion = "list lines"
- data_keys = [ "all_outputs" ]
-
- all_dependent_configs = [ ":${all_dependent_config_name}" ]
- if (defined(invoker.all_dependent_configs)) {
- all_dependent_configs += invoker.all_dependent_configs
- }
+ source_set(target_name) {
+ sources = [
+ "${target_gen_dir}/${invoker.filename}.cc",
+ ]
- deps = [ ":${action_name}" ]
- if (defined(invoker.deps)) {
- deps += invoker.deps
- }
+ public_deps = [ ":$action_name" ]
if (defined(invoker.configs)) {
configs += invoker.configs
diff --git a/gd/hci/acl_manager.cc b/gd/hci/acl_manager.cc
index eefb83bc5..08b14dcac 100644
--- a/gd/hci/acl_manager.cc
+++ b/gd/hci/acl_manager.cc
@@ -220,6 +220,10 @@ void AclManager::AddDeviceToConnectList(AddressWithType address_with_type) {
CallOn(pimpl_->le_impl_, &le_impl::add_device_to_connect_list, address_with_type);
}
+void AclManager::RemoveDeviceFromConnectList(AddressWithType address_with_type) {
+ CallOn(pimpl_->le_impl_, &le_impl::remove_device_from_connect_list, address_with_type);
+}
+
void AclManager::AddDeviceToResolvingList(
AddressWithType address_with_type,
const std::array<uint8_t, 16>& peer_irk,
@@ -227,14 +231,14 @@ void AclManager::AddDeviceToResolvingList(
CallOn(pimpl_->le_impl_, &le_impl::add_device_to_resolving_list, address_with_type, peer_irk, local_irk);
}
-void AclManager::RemoveDeviceFromConnectList(AddressWithType address_with_type) {
- CallOn(pimpl_->le_impl_, &le_impl::remove_device_from_connect_list, address_with_type);
-}
-
void AclManager::RemoveDeviceFromResolvingList(AddressWithType address_with_type) {
CallOn(pimpl_->le_impl_, &le_impl::remove_device_from_resolving_list, address_with_type);
}
+void AclManager::ClearResolvingList() {
+ CallOn(pimpl_->le_impl_, &le_impl::clear_resolving_list);
+}
+
void AclManager::CentralLinkKey(KeyFlag key_flag) {
CallOn(pimpl_->classic_impl_, &classic_impl::central_link_key, key_flag);
}
diff --git a/gd/hci/acl_manager.h b/gd/hci/acl_manager.h
index effa6db3c..6791722cf 100644
--- a/gd/hci/acl_manager.h
+++ b/gd/hci/acl_manager.h
@@ -101,12 +101,13 @@ public:
virtual void CancelLeConnect(AddressWithType address_with_type);
virtual void AddDeviceToConnectList(AddressWithType address_with_type);
+ virtual void RemoveDeviceFromConnectList(AddressWithType address_with_type);
virtual void AddDeviceToResolvingList(
AddressWithType address_with_type,
const std::array<uint8_t, 16>& peer_irk,
const std::array<uint8_t, 16>& local_irk);
- virtual void RemoveDeviceFromConnectList(AddressWithType address_with_type);
virtual void RemoveDeviceFromResolvingList(AddressWithType address_with_type);
+ virtual void ClearResolvingList();
virtual void CentralLinkKey(KeyFlag key_flag);
virtual void SwitchRole(Address address, Role role);
diff --git a/gd/hci/acl_manager/le_impl.h b/gd/hci/acl_manager/le_impl.h
index 356db1c3a..f251f7ce7 100644
--- a/gd/hci/acl_manager/le_impl.h
+++ b/gd/hci/acl_manager/le_impl.h
@@ -625,6 +625,10 @@ struct le_impl : public bluetooth::hci::LeAddressManagerCallback {
}
}
+ void clear_resolving_list() {
+ le_address_manager_->ClearResolvingList();
+ }
+
void set_privacy_policy_for_initiator_address(
LeAddressManager::AddressPolicy address_policy,
AddressWithType fixed_address,
@@ -632,7 +636,12 @@ struct le_impl : public bluetooth::hci::LeAddressManagerCallback {
std::chrono::milliseconds minimum_rotation_time,
std::chrono::milliseconds maximum_rotation_time) {
le_address_manager_->SetPrivacyPolicyForInitiatorAddress(
- address_policy, fixed_address, rotation_irk, minimum_rotation_time, maximum_rotation_time);
+ address_policy,
+ fixed_address,
+ rotation_irk,
+ controller_->SupportsBlePrivacy(),
+ minimum_rotation_time,
+ maximum_rotation_time);
}
// TODO(jpawlowski): remove once we have config file abstraction in cert tests
diff --git a/gd/hci/cert/acl_manager_test.py b/gd/hci/cert/acl_manager_test.py
index 27d7c097b..05a1e60d3 100644
--- a/gd/hci/cert/acl_manager_test.py
+++ b/gd/hci/cert/acl_manager_test.py
@@ -39,23 +39,7 @@ class AclManagerTest(GdBaseTestClass, AclManagerTestBase):
AclManagerTestBase.test_cert_connects(self)
def test_reject_broadcast(self):
- dut_address = self.dut.hci_controller.GetMacAddressSimple()
- self.dut.neighbor.EnablePageScan(neighbor_facade.EnableMsg(enabled=True))
-
- self.dut_acl_manager.listen_for_an_incoming_connection()
- self.cert_hci.initiate_connection(dut_address)
- with self.dut_acl_manager.complete_incoming_connection() as dut_acl:
- cert_acl = self.cert_hci.complete_connection()
-
- cert_acl.send(hci_packets.PacketBoundaryFlag.FIRST_AUTOMATICALLY_FLUSHABLE,
- hci_packets.BroadcastFlag.ACTIVE_PERIPHERAL_BROADCAST,
- b'\x26\x00\x07\x00This is a Broadcast from the Cert')
- assertThat(dut_acl).emitsNone()
-
- cert_acl.send(hci_packets.PacketBoundaryFlag.FIRST_AUTOMATICALLY_FLUSHABLE,
- hci_packets.BroadcastFlag.POINT_TO_POINT,
- b'\x26\x00\x07\x00This is just SomeAclData from the Cert')
- assertThat(dut_acl).emits(lambda packet: b'SomeAclData' in packet.payload)
+ AclManagerTestBase.test_reject_broadcast(self)
def test_cert_connects_disconnects(self):
AclManagerTestBase.test_cert_connects_disconnects(self)
diff --git a/gd/hci/cert/acl_manager_test_lib.py b/gd/hci/cert/acl_manager_test_lib.py
index dcc8f701c..3ae284d4f 100644
--- a/gd/hci/cert/acl_manager_test_lib.py
+++ b/gd/hci/cert/acl_manager_test_lib.py
@@ -15,6 +15,7 @@
# limitations under the License.
from cert.truth import assertThat
+from datetime import timedelta
from neighbor.facade import facade_pb2 as neighbor_facade
from bluetooth_packets_python3 import hci_packets
from cert.py_hci import PyHci
@@ -60,6 +61,25 @@ class AclManagerTestBase():
assertThat(cert_acl).emits(lambda packet: b'SomeMoreAclData' in packet.payload)
assertThat(dut_acl).emits(lambda packet: b'SomeAclData' in packet.payload)
+ def test_reject_broadcast(self):
+ dut_address = self.dut.hci_controller.GetMacAddressSimple()
+ self.dut.neighbor.EnablePageScan(neighbor_facade.EnableMsg(enabled=True))
+
+ self.dut_acl_manager.listen_for_an_incoming_connection()
+ self.cert_hci.initiate_connection(dut_address)
+ with self.dut_acl_manager.complete_incoming_connection() as dut_acl:
+ cert_acl = self.cert_hci.complete_connection()
+
+ cert_acl.send(hci_packets.PacketBoundaryFlag.FIRST_AUTOMATICALLY_FLUSHABLE,
+ hci_packets.BroadcastFlag.ACTIVE_PERIPHERAL_BROADCAST,
+ b'\x26\x00\x07\x00This is a Broadcast from the Cert')
+ assertThat(dut_acl).emitsNone(timeout=timedelta(seconds=0.5))
+
+ cert_acl.send(hci_packets.PacketBoundaryFlag.FIRST_AUTOMATICALLY_FLUSHABLE,
+ hci_packets.BroadcastFlag.POINT_TO_POINT,
+ b'\x26\x00\x07\x00This is just SomeAclData from the Cert')
+ assertThat(dut_acl).emits(lambda packet: b'SomeAclData' in packet.payload)
+
def test_cert_connects_disconnects(self):
dut_address = self.dut.hci_controller.GetMacAddressSimple()
self.dut.neighbor.EnablePageScan(neighbor_facade.EnableMsg(enabled=True))
diff --git a/gd/hci/cert/le_acl_manager_test_lib.py b/gd/hci/cert/le_acl_manager_test_lib.py
index 2abff9f35..067bb7172 100644
--- a/gd/hci/cert/le_acl_manager_test_lib.py
+++ b/gd/hci/cert/le_acl_manager_test_lib.py
@@ -285,3 +285,66 @@ class LeAclManagerTestBase():
hci_packets.BroadcastFlag.POINT_TO_POINT, bytes(b'!'))
assertThat(self.dut_le_acl).emits(lambda packet: b'Hello!' in packet.payload)
+
+ def test_background_connection(self):
+ self.register_for_le_event(hci_packets.SubeventCode.CONNECTION_COMPLETE)
+ self.register_for_le_event(hci_packets.SubeventCode.ENHANCED_CONNECTION_COMPLETE)
+ self.set_privacy_policy_static()
+
+ # Start background and direct connection
+ token = self.dut_le_acl_manager.initiate_background_and_direct_connection(
+ remote_addr=common.BluetoothAddressWithType(
+ address=common.BluetoothAddress(address=bytes('0C:05:04:03:02:01', 'utf8')),
+ type=int(hci_packets.AddressType.RANDOM_DEVICE_ADDRESS)))
+
+ # Wait for direct connection timeout
+ self.dut_le_acl_manager.wait_for_connection_fail(token)
+
+ # Cert Advertises
+ advertising_handle = 0
+ self.enqueue_hci_command(
+ hci_packets.LeSetExtendedAdvertisingLegacyParametersBuilder(
+ advertising_handle,
+ hci_packets.LegacyAdvertisingProperties.ADV_IND,
+ 400,
+ 450,
+ 7,
+ hci_packets.OwnAddressType.RANDOM_DEVICE_ADDRESS,
+ hci_packets.PeerAddressType.PUBLIC_DEVICE_OR_IDENTITY_ADDRESS,
+ '00:00:00:00:00:00',
+ hci_packets.AdvertisingFilterPolicy.ALL_DEVICES,
+ 0xF8,
+ 1, #SID
+ hci_packets.Enable.DISABLED # Scan request notification
+ ))
+
+ self.enqueue_hci_command(
+ hci_packets.LeSetExtendedAdvertisingRandomAddressBuilder(advertising_handle, '0C:05:04:03:02:01'))
+
+ gap_name = hci_packets.GapData()
+ gap_name.data_type = hci_packets.GapDataType.COMPLETE_LOCAL_NAME
+ gap_name.data = list(bytes(b'Im_A_Cert'))
+
+ self.enqueue_hci_command(
+ hci_packets.LeSetExtendedAdvertisingDataBuilder(
+ advertising_handle, hci_packets.Operation.COMPLETE_ADVERTISEMENT,
+ hci_packets.FragmentPreference.CONTROLLER_SHOULD_NOT, [gap_name]))
+
+ gap_short_name = hci_packets.GapData()
+ gap_short_name.data_type = hci_packets.GapDataType.SHORTENED_LOCAL_NAME
+ gap_short_name.data = list(bytes(b'Im_A_C'))
+
+ self.enqueue_hci_command(
+ hci_packets.LeSetExtendedAdvertisingScanResponseBuilder(
+ advertising_handle, hci_packets.Operation.COMPLETE_ADVERTISEMENT,
+ hci_packets.FragmentPreference.CONTROLLER_SHOULD_NOT, [gap_short_name]))
+
+ enabled_set = hci_packets.EnabledSet()
+ enabled_set.advertising_handle = advertising_handle
+ enabled_set.duration = 0
+ enabled_set.max_extended_advertising_events = 0
+ self.enqueue_hci_command(
+ hci_packets.LeSetExtendedAdvertisingEnableBuilder(hci_packets.Enable.ENABLED, [enabled_set]))
+
+ # Check background connection complete
+ self.dut_le_acl_manager.complete_outgoing_connection(token) \ No newline at end of file
diff --git a/gd/hci/facade/le_acl_manager_facade.cc b/gd/hci/facade/le_acl_manager_facade.cc
index 52f182ab8..75c6aac6d 100644
--- a/gd/hci/facade/le_acl_manager_facade.cc
+++ b/gd/hci/facade/le_acl_manager_facade.cc
@@ -75,6 +75,25 @@ class LeAclManagerFacadeService : public LeAclManagerFacade::Service, public LeC
return per_connection_events_[current_connection_request_]->RunLoop(context, writer);
}
+ ::grpc::Status CreateBackgroundAndDirectConnection(
+ ::grpc::ServerContext* context,
+ const ::bluetooth::facade::BluetoothAddressWithType* request,
+ ::grpc::ServerWriter<LeConnectionEvent>* writer) override {
+ Address peer_address;
+ ASSERT(Address::FromString(request->address().address(), peer_address));
+ AddressWithType peer(peer_address, static_cast<AddressType>(request->type()));
+ // Create background connection first
+ acl_manager_->CreateLeConnection(peer, /* is_direct */ false);
+ acl_manager_->CreateLeConnection(peer, /* is_direct */ true);
+ wait_for_background_connection_complete = true;
+ if (per_connection_events_.size() > current_connection_request_) {
+ return ::grpc::Status(::grpc::StatusCode::RESOURCE_EXHAUSTED, "Only one outstanding request is supported");
+ }
+ per_connection_events_.emplace_back(std::make_unique<::bluetooth::grpc::GrpcEventQueue<LeConnectionEvent>>(
+ std::string("connection attempt ") + std::to_string(current_connection_request_)));
+ return per_connection_events_[current_connection_request_]->RunLoop(context, writer);
+ }
+
::grpc::Status CancelConnection(
::grpc::ServerContext* context,
const ::bluetooth::facade::BluetoothAddressWithType* request,
@@ -243,6 +262,7 @@ class LeAclManagerFacadeService : public LeAclManagerFacade::Service, public LeC
success.set_payload(builder_to_string(std::move(builder)));
per_connection_events_[current_connection_request_]->OnIncomingEvent(success);
}
+ wait_for_background_connection_complete = false;
current_connection_request_++;
}
@@ -252,7 +272,9 @@ class LeAclManagerFacadeService : public LeAclManagerFacade::Service, public LeC
LeConnectionEvent fail;
fail.set_payload(builder_to_string(std::move(builder)));
per_connection_events_[current_connection_request_]->OnIncomingEvent(fail);
- current_connection_request_++;
+ if (!wait_for_background_connection_complete) {
+ current_connection_request_++;
+ }
}
class Connection : public LeConnectionManagementCallbacks {
@@ -310,6 +332,7 @@ class LeAclManagerFacadeService : public LeAclManagerFacade::Service, public LeC
std::vector<std::shared_ptr<::bluetooth::grpc::GrpcEventQueue<LeConnectionEvent>>> per_connection_events_;
std::map<uint16_t, Connection> acl_connections_;
uint32_t current_connection_request_{0};
+ bool wait_for_background_connection_complete = false;
};
void LeAclManagerFacadeModule::ListDependencies(ModuleList* list) const {
diff --git a/gd/hci/facade/le_acl_manager_facade.proto b/gd/hci/facade/le_acl_manager_facade.proto
index ed1000ac5..20bb6c27f 100644
--- a/gd/hci/facade/le_acl_manager_facade.proto
+++ b/gd/hci/facade/le_acl_manager_facade.proto
@@ -7,6 +7,8 @@ import "facade/common.proto";
service LeAclManagerFacade {
rpc CreateConnection(bluetooth.facade.BluetoothAddressWithType) returns (stream LeConnectionEvent) {}
+ rpc CreateBackgroundAndDirectConnection(bluetooth.facade.BluetoothAddressWithType)
+ returns (stream LeConnectionEvent) {}
rpc CancelConnection(bluetooth.facade.BluetoothAddressWithType) returns (google.protobuf.Empty) {}
rpc Disconnect(LeHandleMsg) returns (google.protobuf.Empty) {}
rpc ConnectionCommand(LeConnectionCommandMsg) returns (google.protobuf.Empty) {}
diff --git a/gd/hci/le_address_manager.cc b/gd/hci/le_address_manager.cc
index a82bfb3fb..c8b3ec655 100644
--- a/gd/hci/le_address_manager.cc
+++ b/gd/hci/le_address_manager.cc
@@ -48,12 +48,14 @@ void LeAddressManager::SetPrivacyPolicyForInitiatorAddress(
AddressPolicy address_policy,
AddressWithType fixed_address,
crypto_toolbox::Octet16 rotation_irk,
+ bool supports_ble_privacy,
std::chrono::milliseconds minimum_rotation_time,
std::chrono::milliseconds maximum_rotation_time) {
ASSERT(address_policy_ == AddressPolicy::POLICY_NOT_SET);
ASSERT(address_policy != AddressPolicy::POLICY_NOT_SET);
ASSERT_LOG(registered_clients_.empty(), "Policy must be set before clients are registered.");
address_policy_ = address_policy;
+ supports_ble_privacy_ = supports_ble_privacy;
LOG_INFO("SetPrivacyPolicyForInitiatorAddress with policy %d", address_policy);
switch (address_policy_) {
@@ -381,10 +383,29 @@ void LeAddressManager::AddDeviceToResolvingList(
Address peer_identity_address,
const std::array<uint8_t, 16>& peer_irk,
const std::array<uint8_t, 16>& local_irk) {
+ // Disable Address resolution
+ auto disable_builder = hci::LeSetAddressResolutionEnableBuilder::Create(hci::Enable::DISABLED);
+ Command disable = {CommandType::SET_ADDRESS_RESOLUTION_ENABLE, std::move(disable_builder)};
+ cached_commands_.push(std::move(disable));
+
auto packet_builder = hci::LeAddDeviceToResolvingListBuilder::Create(
peer_identity_address_type, peer_identity_address, peer_irk, local_irk);
Command command = {CommandType::ADD_DEVICE_TO_RESOLVING_LIST, std::move(packet_builder)};
- handler_->BindOnceOn(this, &LeAddressManager::push_command, std::move(command)).Invoke();
+ cached_commands_.push(std::move(command));
+
+ if (supports_ble_privacy_) {
+ auto packet_builder =
+ hci::LeSetPrivacyModeBuilder::Create(peer_identity_address_type, peer_identity_address, PrivacyMode::DEVICE);
+ Command command = {CommandType::LE_SET_PRIVACY_MODE, std::move(packet_builder)};
+ cached_commands_.push(std::move(command));
+ }
+
+ // Enable Address resolution
+ auto enable_builder = hci::LeSetAddressResolutionEnableBuilder::Create(hci::Enable::ENABLED);
+ Command enable = {CommandType::SET_ADDRESS_RESOLUTION_ENABLE, std::move(enable_builder)};
+ cached_commands_.push(std::move(enable));
+
+ pause_registered_clients();
}
void LeAddressManager::RemoveDeviceFromConnectList(
@@ -396,10 +417,22 @@ void LeAddressManager::RemoveDeviceFromConnectList(
void LeAddressManager::RemoveDeviceFromResolvingList(
PeerAddressType peer_identity_address_type, Address peer_identity_address) {
+ // Disable Address resolution
+ auto disable_builder = hci::LeSetAddressResolutionEnableBuilder::Create(hci::Enable::DISABLED);
+ Command disable = {CommandType::SET_ADDRESS_RESOLUTION_ENABLE, std::move(disable_builder)};
+ cached_commands_.push(std::move(disable));
+
auto packet_builder =
hci::LeRemoveDeviceFromResolvingListBuilder::Create(peer_identity_address_type, peer_identity_address);
Command command = {CommandType::REMOVE_DEVICE_FROM_RESOLVING_LIST, std::move(packet_builder)};
- handler_->BindOnceOn(this, &LeAddressManager::push_command, std::move(command)).Invoke();
+ cached_commands_.push(std::move(command));
+
+ // Enable Address resolution
+ auto enable_builder = hci::LeSetAddressResolutionEnableBuilder::Create(hci::Enable::ENABLED);
+ Command enable = {CommandType::SET_ADDRESS_RESOLUTION_ENABLE, std::move(enable_builder)};
+ cached_commands_.push(std::move(enable));
+
+ pause_registered_clients();
}
void LeAddressManager::ClearConnectList() {
@@ -409,9 +442,21 @@ void LeAddressManager::ClearConnectList() {
}
void LeAddressManager::ClearResolvingList() {
+ // Disable Address resolution
+ auto disable_builder = hci::LeSetAddressResolutionEnableBuilder::Create(hci::Enable::DISABLED);
+ Command disable = {CommandType::SET_ADDRESS_RESOLUTION_ENABLE, std::move(disable_builder)};
+ cached_commands_.push(std::move(disable));
+
auto packet_builder = hci::LeClearResolvingListBuilder::Create();
Command command = {CommandType::CLEAR_RESOLVING_LIST, std::move(packet_builder)};
- handler_->BindOnceOn(this, &LeAddressManager::push_command, std::move(command)).Invoke();
+ cached_commands_.push(std::move(command));
+
+ // Enable Address resolution
+ auto enable_builder = hci::LeSetAddressResolutionEnableBuilder::Create(hci::Enable::ENABLED);
+ Command enable = {CommandType::SET_ADDRESS_RESOLUTION_ENABLE, std::move(enable_builder)};
+ cached_commands_.push(std::move(enable));
+
+ pause_registered_clients();
}
void LeAddressManager::OnCommandComplete(bluetooth::hci::CommandCompleteView view) {
diff --git a/gd/hci/le_address_manager.h b/gd/hci/le_address_manager.h
index df591d2ef..8aaa11f1f 100644
--- a/gd/hci/le_address_manager.h
+++ b/gd/hci/le_address_manager.h
@@ -57,6 +57,7 @@ class LeAddressManager {
AddressPolicy address_policy,
AddressWithType fixed_address,
crypto_toolbox::Octet16 rotation_irk,
+ bool supports_ble_privacy,
std::chrono::milliseconds minimum_rotation_time,
std::chrono::milliseconds maximum_rotation_time);
// TODO(jpawlowski): remove once we have config file abstraction in cert tests
@@ -104,7 +105,9 @@ class LeAddressManager {
CLEAR_CONNECT_LIST,
ADD_DEVICE_TO_RESOLVING_LIST,
REMOVE_DEVICE_FROM_RESOLVING_LIST,
- CLEAR_RESOLVING_LIST
+ CLEAR_RESOLVING_LIST,
+ SET_ADDRESS_RESOLUTION_ENABLE,
+ LE_SET_PRIVACY_MODE
};
struct Command {
@@ -143,6 +146,7 @@ class LeAddressManager {
uint8_t connect_list_size_;
uint8_t resolving_list_size_;
std::queue<Command> cached_commands_;
+ bool supports_ble_privacy_{false};
};
} // namespace hci
diff --git a/gd/os/android/parameter_provider.cc b/gd/os/android/parameter_provider.cc
index dcf7c407e..0d78067c0 100644
--- a/gd/os/android/parameter_provider.cc
+++ b/gd/os/android/parameter_provider.cc
@@ -16,6 +16,9 @@
#include "os/parameter_provider.h"
+#include <private/android_filesystem_config.h>
+#include <unistd.h>
+
#include <mutex>
#include <string>
@@ -27,6 +30,9 @@ std::mutex parameter_mutex;
std::string config_file_path;
std::string snoop_log_file_path;
std::string snooz_log_file_path;
+bluetooth_keystore::BluetoothKeystoreInterface* bt_keystore_interface = nullptr;
+bool is_common_criteria_mode = false;
+int common_criteria_config_compare_result = 0b11;
} // namespace
// On Android we always write a single default location
@@ -76,5 +82,35 @@ void ParameterProvider::OverrideSnoozLogFilePath(const std::string& path) {
snooz_log_file_path = path;
}
+bluetooth_keystore::BluetoothKeystoreInterface* ParameterProvider::GetBtKeystoreInterface() {
+ std::lock_guard<std::mutex> lock(parameter_mutex);
+ return bt_keystore_interface;
+}
+
+void ParameterProvider::SetBtKeystoreInterface(bluetooth_keystore::BluetoothKeystoreInterface* bt_keystore) {
+ std::lock_guard<std::mutex> lock(parameter_mutex);
+ bt_keystore_interface = bt_keystore;
+}
+
+bool ParameterProvider::IsCommonCriteriaMode() {
+ std::lock_guard<std::mutex> lock(parameter_mutex);
+ return (getuid() == AID_BLUETOOTH) && is_common_criteria_mode;
+}
+
+void ParameterProvider::SetCommonCriteriaMode(bool enable) {
+ std::lock_guard<std::mutex> lock(parameter_mutex);
+ is_common_criteria_mode = enable;
+}
+
+int ParameterProvider::GetCommonCriteriaConfigCompareResult() {
+ std::lock_guard<std::mutex> lock(parameter_mutex);
+ return common_criteria_config_compare_result;
+}
+
+void ParameterProvider::SetCommonCriteriaConfigCompareResult(int result) {
+ std::lock_guard<std::mutex> lock(parameter_mutex);
+ common_criteria_config_compare_result = result;
+}
+
} // namespace os
} // namespace bluetooth \ No newline at end of file
diff --git a/gd/os/bt_keystore.h b/gd/os/bt_keystore.h
new file mode 100644
index 000000000..be0975009
--- /dev/null
+++ b/gd/os/bt_keystore.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2020 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/hardware/bt_keystore.h>
+
+namespace bluetooth {
+namespace bluetooth_keystore {
+
+BluetoothKeystoreInterface* getBluetoothKeystoreInterface();
+
+} // namespace bluetooth_keystore
+} // namespace bluetooth \ No newline at end of file
diff --git a/gd/os/host/parameter_provider.cc b/gd/os/host/parameter_provider.cc
index 2220f940e..ef5ca4bc7 100644
--- a/gd/os/host/parameter_provider.cc
+++ b/gd/os/host/parameter_provider.cc
@@ -96,5 +96,23 @@ void ParameterProvider::OverrideSnoozLogFilePath(const std::string& path) {
snooz_log_file_path = path;
}
+bluetooth_keystore::BluetoothKeystoreInterface* ParameterProvider::GetBtKeystoreInterface() {
+ return nullptr;
+}
+
+void ParameterProvider::SetBtKeystoreInterface(bluetooth_keystore::BluetoothKeystoreInterface* bt_keystore) {}
+
+bool ParameterProvider::IsCommonCriteriaMode() {
+ return false;
+}
+
+void ParameterProvider::SetCommonCriteriaMode(bool enable) {}
+
+int ParameterProvider::GetCommonCriteriaConfigCompareResult() {
+ return 0b11;
+}
+
+void ParameterProvider::SetCommonCriteriaConfigCompareResult(int result) {}
+
} // namespace os
} // namespace bluetooth \ No newline at end of file
diff --git a/gd/os/linux/parameter_provider.cc b/gd/os/linux/parameter_provider.cc
index 4d1017598..10144ae67 100644
--- a/gd/os/linux/parameter_provider.cc
+++ b/gd/os/linux/parameter_provider.cc
@@ -76,5 +76,23 @@ std::string ParameterProvider::SnoozLogFilePath() {
return "/var/log/bluetooth/btsnooz_hci.log";
}
+bluetooth_keystore::BluetoothKeystoreInterface* ParameterProvider::GetBtKeystoreInterface() {
+ return nullptr;
+}
+
+void ParameterProvider::SetBtKeystoreInterface(bluetooth_keystore::BluetoothKeystoreInterface* bt_keystore) {}
+
+bool ParameterProvider::IsCommonCriteriaMode() {
+ return false;
+}
+
+void ParameterProvider::SetCommonCriteriaMode(bool enable) {}
+
+int ParameterProvider::GetCommonCriteriaConfigCompareResult() {
+ return 0b11;
+}
+
+void ParameterProvider::SetCommonCriteriaConfigCompareResult(int result) {}
+
} // namespace os
} // namespace bluetooth
diff --git a/gd/os/parameter_provider.h b/gd/os/parameter_provider.h
index 72557bbd5..111391853 100644
--- a/gd/os/parameter_provider.h
+++ b/gd/os/parameter_provider.h
@@ -18,6 +18,8 @@
#include <string>
+#include "os/bt_keystore.h"
+
namespace bluetooth {
namespace os {
@@ -37,6 +39,18 @@ class ParameterProvider {
static std::string SnoozLogFilePath();
static void OverrideSnoozLogFilePath(const std::string& path);
+
+ static bluetooth_keystore::BluetoothKeystoreInterface* GetBtKeystoreInterface();
+
+ static void SetBtKeystoreInterface(bluetooth_keystore::BluetoothKeystoreInterface* bt_keystore);
+
+ static bool IsCommonCriteriaMode();
+
+ static void SetCommonCriteriaMode(bool enable);
+
+ static int GetCommonCriteriaConfigCompareResult();
+
+ static void SetCommonCriteriaConfigCompareResult(int result);
};
} // namespace os
diff --git a/gd/rust/linux/client/src/dbus_iface.rs b/gd/rust/linux/client/src/dbus_iface.rs
index a0bba4f0d..801736e74 100644
--- a/gd/rust/linux/client/src/dbus_iface.rs
+++ b/gd/rust/linux/client/src/dbus_iface.rs
@@ -211,7 +211,7 @@ impl IBluetoothConnectionCallback for IBluetoothConnectionCallbackDBus {
#[dbus_method("OnDeviceConnected")]
fn on_device_connected(&self, remote_device: BluetoothDevice) {}
- #[dbus_method("OnDeviceDisconencted")]
+ #[dbus_method("OnDeviceDisconnected")]
fn on_device_disconnected(&self, remote_device: BluetoothDevice) {}
}
diff --git a/gd/rust/linux/service/src/iface_bluetooth.rs b/gd/rust/linux/service/src/iface_bluetooth.rs
index f9a9baa84..5954e72f8 100644
--- a/gd/rust/linux/service/src/iface_bluetooth.rs
+++ b/gd/rust/linux/service/src/iface_bluetooth.rs
@@ -64,7 +64,7 @@ impl IBluetoothConnectionCallback for BluetoothConnectionCallbackDBus {
#[dbus_method("OnDeviceConnected")]
fn on_device_connected(&self, remote_device: BluetoothDevice) {}
- #[dbus_method("OnDeviceDisconencted")]
+ #[dbus_method("OnDeviceDisconnected")]
fn on_device_disconnected(&self, remote_device: BluetoothDevice) {}
}
diff --git a/gd/rust/linux/stack/src/bluetooth.rs b/gd/rust/linux/stack/src/bluetooth.rs
index 4a02a8f9e..9e95b36f4 100644
--- a/gd/rust/linux/stack/src/bluetooth.rs
+++ b/gd/rust/linux/stack/src/bluetooth.rs
@@ -886,7 +886,6 @@ impl IBluetooth for Bluetooth {
== 0
}
- /// Set passkey on bonding device.
fn set_passkey(
&self,
device: BluetoothDevice,
diff --git a/gd/rust/linux/stack/src/bluetooth_media.rs b/gd/rust/linux/stack/src/bluetooth_media.rs
index c645f081e..6b3f17874 100644
--- a/gd/rust/linux/stack/src/bluetooth_media.rs
+++ b/gd/rust/linux/stack/src/bluetooth_media.rs
@@ -7,8 +7,12 @@ use bt_topshim::profiles::a2dp::{
PresentationPosition,
};
use bt_topshim::profiles::avrcp::{Avrcp, AvrcpCallbacks, AvrcpCallbacksDispatcher};
+use bt_topshim::profiles::hfp::{BthfConnectionState, Hfp, HfpCallbacks, HfpCallbacksDispatcher};
+
use bt_topshim::topstack;
+use log::warn;
+
use std::collections::HashMap;
use std::convert::TryFrom;
use std::sync::Arc;
@@ -28,6 +32,7 @@ pub trait IBluetoothMedia {
/// clean up media stack
fn cleanup(&mut self) -> bool;
+ // TODO (b/204488289): Accept and validate RawAddress instead.
fn connect(&mut self, device: String);
fn set_active_device(&mut self, device: String);
fn disconnect(&mut self, device: String);
@@ -78,6 +83,8 @@ pub struct BluetoothMedia {
a2dp: Option<A2dp>,
avrcp: Option<Avrcp>,
a2dp_states: HashMap<RawAddress, BtavConnectionState>,
+ hfp: Option<Hfp>,
+ hfp_states: HashMap<RawAddress, BthfConnectionState>,
selectable_caps: HashMap<RawAddress, Vec<A2dpCodecConfig>>,
}
@@ -92,6 +99,8 @@ impl BluetoothMedia {
a2dp: None,
avrcp: None,
a2dp_states: HashMap::new(),
+ hfp: None,
+ hfp_states: HashMap::new(),
selectable_caps: HashMap::new(),
}
}
@@ -165,6 +174,28 @@ impl BluetoothMedia {
}
}
+ pub fn dispatch_hfp_callbacks(&mut self, cb: HfpCallbacks) {
+ match cb {
+ HfpCallbacks::ConnectionState(state, addr) => {
+ if !self.hfp_states.get(&addr).is_none()
+ && state == *self.hfp_states.get(&addr).unwrap()
+ {
+ return;
+ }
+ match state {
+ BthfConnectionState::Connected => {
+ // TODO: Integrate with A2dp
+ }
+ BthfConnectionState::Connecting => {}
+ BthfConnectionState::Disconnected => {}
+ BthfConnectionState::Disconnecting => {
+ // TODO: Integrate with A2dp
+ }
+ }
+ }
+ }
+ }
+
fn for_all_callbacks<F: Fn(&Box<dyn IBluetoothMediaCallback + Send>)>(&self, f: F) {
for callback in &self.callbacks {
f(&callback.1);
@@ -194,6 +225,17 @@ fn get_avrcp_dispatcher(tx: Sender<Message>) -> AvrcpCallbacksDispatcher {
}
}
+fn get_hfp_dispatcher(tx: Sender<Message>) -> HfpCallbacksDispatcher {
+ HfpCallbacksDispatcher {
+ dispatch: Box::new(move |cb| {
+ let txl = tx.clone();
+ topstack::get_runtime().spawn(async move {
+ let _ = txl.send(Message::Hfp(cb)).await;
+ });
+ }),
+ }
+}
+
impl IBluetoothMedia for BluetoothMedia {
fn register_callback(&mut self, callback: Box<dyn IBluetoothMediaCallback + Send>) -> bool {
self.callback_last_id += 1;
@@ -216,11 +258,24 @@ impl IBluetoothMedia for BluetoothMedia {
let avrcp_dispatcher = get_avrcp_dispatcher(self.tx.clone());
self.avrcp = Some(Avrcp::new(&self.intf.lock().unwrap()));
self.avrcp.as_mut().unwrap().initialize(avrcp_dispatcher);
+
+ // HFP
+ let hfp_dispatcher = get_hfp_dispatcher(self.tx.clone());
+ self.hfp = Some(Hfp::new(&self.intf.lock().unwrap()));
+ self.hfp.as_mut().unwrap().initialize(hfp_dispatcher);
+
true
}
fn connect(&mut self, device: String) {
+ let addr = RawAddress::from_string(device.clone());
+ if addr.is_none() {
+ warn!("Invalid device string {}", device);
+ return;
+ }
+
self.a2dp.as_mut().unwrap().connect(device);
+ self.hfp.as_mut().unwrap().connect(addr.unwrap());
}
fn cleanup(&mut self) -> bool {
@@ -232,7 +287,14 @@ impl IBluetoothMedia for BluetoothMedia {
}
fn disconnect(&mut self, device: String) {
+ let addr = RawAddress::from_string(device.clone());
+ if addr.is_none() {
+ warn!("Invalid device string {}", device);
+ return;
+ }
+
self.a2dp.as_mut().unwrap().disconnect(device);
+ self.hfp.as_mut().unwrap().disconnect(addr.unwrap());
}
fn set_audio_config(
diff --git a/gd/rust/linux/stack/src/lib.rs b/gd/rust/linux/stack/src/lib.rs
index 498b56023..160363e88 100644
--- a/gd/rust/linux/stack/src/lib.rs
+++ b/gd/rust/linux/stack/src/lib.rs
@@ -23,7 +23,7 @@ use bt_topshim::{
btif::BaseCallbacks,
profiles::{
a2dp::A2dpCallbacks, avrcp::AvrcpCallbacks, gatt::GattClientCallbacks,
- gatt::GattServerCallbacks, hid_host::HHCallbacks, sdp::SdpCallbacks,
+ gatt::GattServerCallbacks, hfp::HfpCallbacks, hid_host::HHCallbacks, sdp::SdpCallbacks,
},
};
@@ -45,6 +45,7 @@ pub enum Message {
GattClient(GattClientCallbacks),
GattServer(GattServerCallbacks),
HidHost(HHCallbacks),
+ Hfp(HfpCallbacks),
Sdp(SdpCallbacks),
// Actions within the stack
@@ -100,6 +101,10 @@ impl Stack {
debug!("Unhandled Message::GattServer: {:?}", m);
}
+ Message::Hfp(hf) => {
+ bluetooth_media.lock().unwrap().dispatch_hfp_callbacks(hf);
+ }
+
Message::HidHost(_h) => {
// TODO(abps) - Handle hid host callbacks
debug!("Received HH callback");
diff --git a/gd/rust/shim/Android.bp b/gd/rust/shim/Android.bp
index 6db3bfde1..b5b60426f 100644
--- a/gd/rust/shim/Android.bp
+++ b/gd/rust/shim/Android.bp
@@ -26,8 +26,8 @@ cc_defaults {
},
}
-rust_ffi_static {
- name: "libbt_shim_ffi",
+rust_defaults {
+ name: "libbt_shim_defaults",
defaults: ["gd_rust_defaults"],
crate_name: "bt_shim",
srcs: ["src/lib.rs"],
@@ -51,8 +51,18 @@ rust_ffi_static {
],
}
+rust_library_rlib {
+ name: "libbt_shim",
+ defaults: ["libbt_shim_defaults"],
+}
+
+rust_ffi_static {
+ name: "libbt_shim_ffi",
+ defaults: ["libbt_shim_defaults"],
+}
+
cc_library_static {
- name: "libbluetooth_rust_interop",
+ name: "libbt_shim_bridge",
defaults: ["gd_ffi_defaults"],
generated_headers: [
"libbt_init_flags_bridge_header",
@@ -87,9 +97,16 @@ cc_library_static {
shared_libs: [
"libchrome",
],
+}
+
+cc_library_static {
+ name: "libbluetooth_rust_interop",
+ defaults: ["gd_ffi_defaults"],
whole_static_libs: [
+ "libbt_shim_bridge",
"libbt_shim_ffi",
],
+ host_supported: true,
}
cc_library_static {
diff --git a/gd/rust/topshim/Android.bp b/gd/rust/topshim/Android.bp
index dd77ec064..1e9677ec0 100644
--- a/gd/rust/topshim/Android.bp
+++ b/gd/rust/topshim/Android.bp
@@ -51,6 +51,7 @@ cc_library_static {
"btif/btif_shim.cc",
"gatt/gatt_shim.cc",
"hfp/hfp_shim.cc",
+ "controller/controller_shim.cc",
],
generated_headers: ["libbt_topshim_bridge_header", "cxx-bridge-header"],
generated_sources: ["libbt_topshim_bridge_code"],
@@ -74,7 +75,9 @@ gensrcs {
"src/btif.rs",
"src/profiles/a2dp.rs",
"src/profiles/avrcp.rs",
+ "src/profiles/hfp.rs",
"src/profiles/gatt.rs",
+ "src/controller.rs",
],
output_extension: "rs.h",
export_include_dirs: ["."],
@@ -88,7 +91,9 @@ gensrcs {
"src/btif.rs",
"src/profiles/a2dp.rs",
"src/profiles/avrcp.rs",
+ "src/profiles/hfp.rs",
"src/profiles/gatt.rs",
+ "src/controller.rs",
],
output_extension: "cc",
export_include_dirs: ["."],
diff --git a/gd/rust/topshim/BUILD.gn b/gd/rust/topshim/BUILD.gn
index 26f18e0c2..bf986cb4b 100644
--- a/gd/rust/topshim/BUILD.gn
+++ b/gd/rust/topshim/BUILD.gn
@@ -27,7 +27,9 @@ cxxbridge_header("btif_bridge_header") {
"src/btif.rs",
"src/profiles/a2dp.rs",
"src/profiles/avrcp.rs",
+ "src/profiles/hfp.rs",
"src/profiles/gatt.rs",
+ "src/controller.rs",
]
all_dependent_configs = [ ":rust_topshim_config" ]
deps = [":cxxlibheader"]
@@ -38,9 +40,11 @@ cxxbridge_cc("btif_bridge_code") {
"src/btif.rs",
"src/profiles/a2dp.rs",
"src/profiles/avrcp.rs",
+ "src/profiles/hfp.rs",
"src/profiles/gatt.rs",
+ "src/controller.rs",
]
- deps = [":btif_bridge_header"]
+ deps = [":btif_bridge_header", "//bt/gd:BluetoothGeneratedPackets_h"]
configs = [ "//bt/gd:gd_defaults" ]
}
@@ -49,10 +53,12 @@ source_set("btif_cxx_bridge_code") {
"btif/btif_shim.cc",
"btav/btav_shim.cc",
"btav_sink/btav_sink_shim.cc",
+ "hfp/hfp_shim.cc",
"gatt/gatt_shim.cc",
+ "controller/controller_shim.cc",
]
- deps = [":btif_bridge_header"]
+ deps = [":btif_bridge_header", "//bt/gd:BluetoothGeneratedPackets_h"]
configs += ["//bt/gd:gd_defaults"]
}
diff --git a/gd/rust/topshim/Cargo.toml b/gd/rust/topshim/Cargo.toml
index 2a46144e0..f9e405264 100644
--- a/gd/rust/topshim/Cargo.toml
+++ b/gd/rust/topshim/Cargo.toml
@@ -27,6 +27,7 @@ topshim_macros = { path = "macros" }
cxx = "*"
lazy_static = "*"
+log = "*"
proc-macro2 = "*"
num-derive = "*"
num-traits = "*"
diff --git a/gd/rust/topshim/btav_sink/btav_sink_shim.cc b/gd/rust/topshim/btav_sink/btav_sink_shim.cc
index f0816df71..53d943a5c 100644
--- a/gd/rust/topshim/btav_sink/btav_sink_shim.cc
+++ b/gd/rust/topshim/btav_sink/btav_sink_shim.cc
@@ -19,6 +19,8 @@
#include <memory>
#include "include/hardware/bluetooth.h"
+#include "rust/cxx.h"
+#include "src/profiles/a2dp.rs.h"
#include "types/raw_address.h"
namespace bluetooth {
@@ -37,6 +39,12 @@ btav_sink_callbacks_t g_a2dp_sink_callbacks = {
audio_state_cb,
audio_config_cb,
};
+
+static RawAddress from_rust_address(const RustRawAddress& raddr) {
+ RawAddress addr;
+ addr.FromOctets(raddr.address.data());
+ return addr;
+}
} // namespace internal
A2dpSinkIntf::~A2dpSinkIntf() {
@@ -54,11 +62,23 @@ std::unique_ptr<A2dpSinkIntf> GetA2dpSinkProfile(const unsigned char* btif) {
return a2dp_sink;
}
-int A2dpSinkIntf::init() {
+int A2dpSinkIntf::init() const {
return intf_->init(&internal::g_a2dp_sink_callbacks, 1);
}
-void A2dpSinkIntf::cleanup() {
+int A2dpSinkIntf::connect(RustRawAddress bt_addr) const {
+ return intf_->connect(internal::from_rust_address(bt_addr));
+}
+
+int A2dpSinkIntf::disconnect(RustRawAddress bt_addr) const {
+ return intf_->disconnect(internal::from_rust_address(bt_addr));
+}
+
+int A2dpSinkIntf::set_active_device(RustRawAddress bt_addr) const {
+ return intf_->set_active_device(internal::from_rust_address(bt_addr));
+}
+
+void A2dpSinkIntf::cleanup() const {
// TODO: Implement.
}
diff --git a/gd/rust/topshim/btav_sink/btav_sink_shim.h b/gd/rust/topshim/btav_sink/btav_sink_shim.h
index 2306ee14b..785c15043 100644
--- a/gd/rust/topshim/btav_sink/btav_sink_shim.h
+++ b/gd/rust/topshim/btav_sink/btav_sink_shim.h
@@ -18,20 +18,28 @@
#include <memory>
+#include "gd/rust/topshim/btav_sink/btav_sink_shim.h"
#include "include/hardware/bt_av.h"
+#include "rust/cxx.h"
+#include "types/raw_address.h"
namespace bluetooth {
namespace topshim {
namespace rust {
+struct RustRawAddress;
+
class A2dpSinkIntf {
public:
A2dpSinkIntf(const btav_sink_interface_t* intf) : intf_(intf){};
~A2dpSinkIntf();
// interface for Settings
- int init();
- void cleanup();
+ int init() const;
+ int connect(RustRawAddress bt_addr) const;
+ int disconnect(RustRawAddress bt_addr) const;
+ int set_active_device(RustRawAddress bt_addr) const;
+ void cleanup() const;
private:
const btav_sink_interface_t* intf_;
diff --git a/gd/rust/topshim/controller/controller_shim.cc b/gd/rust/topshim/controller/controller_shim.cc
new file mode 100644
index 000000000..cd04f4f8f
--- /dev/null
+++ b/gd/rust/topshim/controller/controller_shim.cc
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2021 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 "gd/rust/topshim/controller/controller_shim.h"
+
+#include <memory>
+
+#include "rust/cxx.h"
+#include "src/controller.rs.h"
+#include "types/raw_address.h"
+
+namespace bluetooth {
+namespace topshim {
+namespace rust {
+namespace internal {
+static ControllerIntf* g_controller_intf;
+
+static RustRawAddress to_rust_address(const RawAddress& address) {
+ RustRawAddress raddr;
+ std::copy(std::begin(address.address), std::end(address.address), std::begin(raddr.address));
+ return raddr;
+}
+} // namespace internal
+
+ControllerIntf::~ControllerIntf() {}
+
+std::unique_ptr<ControllerIntf> GetControllerInterface() {
+ if (internal::g_controller_intf) std::abort();
+ auto controller_intf = std::make_unique<ControllerIntf>();
+ internal::g_controller_intf = controller_intf.get();
+ return controller_intf;
+}
+
+RustRawAddress ControllerIntf::read_local_addr() const {
+ if (!controller_) std::abort();
+ return internal::to_rust_address(*controller_->get_address());
+}
+
+} // namespace rust
+} // namespace topshim
+} // namespace bluetooth
diff --git a/gd/rust/topshim/controller/controller_shim.h b/gd/rust/topshim/controller/controller_shim.h
new file mode 100644
index 000000000..b817ee913
--- /dev/null
+++ b/gd/rust/topshim/controller/controller_shim.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 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 GD_RUST_TOPSHIM_CONTROLLER_SHIM
+#define GD_RUST_TOPSHIM_CONTROLLER_SHIM
+
+#include <memory>
+
+#include "main/shim/controller.h"
+#include "rust/cxx.h"
+#include "types/raw_address.h"
+
+namespace bluetooth {
+namespace topshim {
+namespace rust {
+
+struct RustRawAddress;
+
+class ControllerIntf {
+ public:
+ ControllerIntf() : controller_(controller_get_interface()) {}
+ ~ControllerIntf();
+
+ RustRawAddress read_local_addr() const;
+
+ private:
+ const controller_t* controller_;
+};
+
+// ControllerIntf* GetControllerInterface();
+std::unique_ptr<ControllerIntf> GetControllerInterface();
+
+} // namespace rust
+} // namespace topshim
+} // namespace bluetooth
+
+#endif // GD_RUST_TOPSHIM_CONTROLLER_SHIM \ No newline at end of file
diff --git a/gd/rust/topshim/facade/Android.bp b/gd/rust/topshim/facade/Android.bp
index f0e185aaa..a92671bb3 100644
--- a/gd/rust/topshim/facade/Android.bp
+++ b/gd/rust/topshim/facade/Android.bp
@@ -12,6 +12,7 @@ rust_binary_host {
defaults: ["gd_rust_defaults"],
crate_name: "bt_topshim_facade",
srcs: ["src/main.rs"],
+ ld_flags: ["-fsanitize=undefined", "-fsanitize-minimal-runtime"],
rustlibs: [
"libbluetooth_rs",
"libbt_common",
@@ -25,17 +26,18 @@ rust_binary_host {
"libbt_facade_helpers",
"libbt_topshim",
"libbt_topshim_facade_protobuf",
+ "libbt_shim",
],
static_libs: [
"libbt_topshim_cxx",
"libbt-bta",
"libbt-common",
"libbtdevice",
- "libbtif",
+ "libbtif-static",
"libbt-hci",
"libbt-stack",
"libbt-utils",
- "libbtcore",
+ "libbtcore-static",
"libosi",
"libbt-protos-lite",
"libbte",
@@ -46,18 +48,19 @@ rust_binary_host {
"liblc3codec",
"libudrv-uipc",
"libbluetooth_gd", // Gabeldorsche
- "libbluetooth_rust_interop",
"libbluetooth-dumpsys",
+ "libbluetooth-types",
"libflatbuffers-cpp",
+ "libbt_shim_bridge",
],
shared_libs: [
"libcrypto",
- "libbluetooth",
"libchrome",
+ "liblog",
+ "libcutils",
+ "libgrpc++",
+ "libgrpc_wrap"
],
- sanitize: {
- never: true,
- },
proc_macros: [
"libpaste",
],
diff --git a/gd/rust/topshim/facade/src/main.rs b/gd/rust/topshim/facade/src/main.rs
index 073693333..44d27aa49 100644
--- a/gd/rust/topshim/facade/src/main.rs
+++ b/gd/rust/topshim/facade/src/main.rs
@@ -21,6 +21,18 @@ use tokio::runtime::Runtime;
mod adapter_service;
mod media_service;
+// This is needed for linking, libbt_shim_bridge needs symbols defined by
+// bt_shim, however bt_shim depends on rust crates (future, tokio) that
+// we use too, if we build and link them separately we ends with duplicate
+// symbols. To solve that we build bt_shim with bt_topshim_facade so the rust
+// compiler share the transitive dependencies.
+//
+// The `::*` is here to circuvent the single_component_path_imports from
+// clippy that is denied on the rust command line so we can't just allow it.
+// This is fine for now since bt_shim doesn't export anything
+#[allow(unused)]
+use bt_shim::*;
+
fn main() {
let sigint = install_sigint();
bt_common::init_logging();
diff --git a/gd/rust/topshim/facade/src/media_service.rs b/gd/rust/topshim/facade/src/media_service.rs
index a5f37d5af..1998af4b7 100644
--- a/gd/rust/topshim/facade/src/media_service.rs
+++ b/gd/rust/topshim/facade/src/media_service.rs
@@ -1,7 +1,9 @@
//! Media service facade
use bt_topshim::btif::BluetoothInterface;
-use bt_topshim::profiles::a2dp::{A2dp, A2dpCallbacksDispatcher, A2dpSink};
+use bt_topshim::profiles::a2dp::{
+ A2dp, A2dpCallbacksDispatcher, A2dpSink, A2dpSinkCallbacksDispatcher,
+};
use bt_topshim::profiles::avrcp::{Avrcp, AvrcpCallbacksDispatcher};
use bt_topshim_facade_protobuf::facade::{
A2dpSourceConnectRequest, A2dpSourceConnectResponse, StartA2dpRequest, StartA2dpResponse,
@@ -17,6 +19,10 @@ fn get_a2dp_dispatcher() -> A2dpCallbacksDispatcher {
A2dpCallbacksDispatcher { dispatch: Box::new(move |_cb| {}) }
}
+fn get_a2dp_sink_dispatcher() -> A2dpSinkCallbacksDispatcher {
+ A2dpSinkCallbacksDispatcher { dispatch: Box::new(move |_cb| {}) }
+}
+
fn get_avrcp_dispatcher() -> AvrcpCallbacksDispatcher {
AvrcpCallbacksDispatcher { dispatch: Box::new(move |_cb| {}) }
}
@@ -60,7 +66,7 @@ impl MediaService for MediaServiceImpl {
sink.success(StartA2dpResponse::default()).await.unwrap();
})
} else if req.start_a2dp_sink {
- self.btif_a2dp_sink.lock().unwrap().initialize();
+ self.btif_a2dp_sink.lock().unwrap().initialize(get_a2dp_sink_dispatcher());
ctx.spawn(async move {
sink.success(StartA2dpResponse::default()).await.unwrap();
})
diff --git a/gd/rust/topshim/facade/utils.proto b/gd/rust/topshim/facade/utils.proto
new file mode 100644
index 000000000..9f0fd5cf0
--- /dev/null
+++ b/gd/rust/topshim/facade/utils.proto
@@ -0,0 +1,13 @@
+syntax = "proto3";
+
+package blueberry;
+
+message Empty {}
+
+message BluetoothAddress {
+ bytes address = 1;
+}
+
+service ReadOnlyProperty {
+ rpc ReadLocalAddress(Empty) returns (BluetoothAddress) {}
+} \ No newline at end of file
diff --git a/gd/rust/topshim/hfp/hfp_shim.cc b/gd/rust/topshim/hfp/hfp_shim.cc
index d2742defe..d7e1d9987 100644
--- a/gd/rust/topshim/hfp/hfp_shim.cc
+++ b/gd/rust/topshim/hfp/hfp_shim.cc
@@ -14,21 +14,136 @@
* limitations under the License.
*/
-#include "hfp/hfp_shim.h"
+#include "gd/rust/topshim/hfp/hfp_shim.h"
#include "btif/include/btif_hf.h"
+#include "include/hardware/bt_hf.h"
+#include "src/profiles/hfp.rs.h"
+#include "types/raw_address.h"
+
+namespace rusty = ::bluetooth::topshim::rust;
+namespace bluetooth::topshim::rust::internal {
+static void connection_state_cb(bluetooth::headset::bthf_connection_state_t state, RawAddress* addr);
+} // namespace bluetooth::topshim::rust::internal
+
+namespace bluetooth::headset {
+class DBusHeadsetCallbacks : public Callbacks {
+ public:
+ static Callbacks* GetInstance() {
+ static Callbacks* instance = new DBusHeadsetCallbacks();
+ return instance;
+ }
+
+ void ConnectionStateCallback(bthf_connection_state_t state, RawAddress* bd_addr) override {
+ topshim::rust::internal::connection_state_cb(state, bd_addr);
+ }
+
+ void AudioStateCallback([[maybe_unused]] bthf_audio_state_t state, [[maybe_unused]] RawAddress* bd_addr) override {}
+
+ void VoiceRecognitionCallback([[maybe_unused]] bthf_vr_state_t state, [[maybe_unused]] RawAddress* bd_addr) override {
+ }
+
+ void AnswerCallCallback([[maybe_unused]] RawAddress* bd_addr) override {}
+
+ void HangupCallCallback([[maybe_unused]] RawAddress* bd_addr) override {}
+
+ void VolumeControlCallback(
+ [[maybe_unused]] bthf_volume_type_t type,
+ [[maybe_unused]] int volume,
+ [[maybe_unused]] RawAddress* bd_addr) override {}
+
+ void DialCallCallback([[maybe_unused]] char* number, [[maybe_unused]] RawAddress* bd_addr) override {}
+
+ void DtmfCmdCallback([[maybe_unused]] char tone, [[maybe_unused]] RawAddress* bd_addr) override {}
+
+ void NoiseReductionCallback([[maybe_unused]] bthf_nrec_t nrec, [[maybe_unused]] RawAddress* bd_addr) override {}
+
+ void WbsCallback([[maybe_unused]] bthf_wbs_config_t wbs, [[maybe_unused]] RawAddress* bd_addr) override {}
+
+ void AtChldCallback([[maybe_unused]] bthf_chld_type_t chld, [[maybe_unused]] RawAddress* bd_addr) override {}
+
+ void AtCnumCallback([[maybe_unused]] RawAddress* bd_addr) override {}
+
+ void AtCindCallback([[maybe_unused]] RawAddress* bd_addr) override {}
+
+ void AtCopsCallback([[maybe_unused]] RawAddress* bd_addr) override {}
+
+ void AtClccCallback([[maybe_unused]] RawAddress* bd_addr) override {}
+
+ void UnknownAtCallback([[maybe_unused]] char* at_string, [[maybe_unused]] RawAddress* bd_addr) override {}
+
+ void KeyPressedCallback([[maybe_unused]] RawAddress* bd_addr) override {}
+
+ void AtBindCallback([[maybe_unused]] char* at_string, [[maybe_unused]] RawAddress* bd_addr) override {}
+
+ void AtBievCallback(
+ [[maybe_unused]] bthf_hf_ind_type_t ind_id,
+ [[maybe_unused]] int ind_value,
+ [[maybe_unused]] RawAddress* bd_addr) override {}
+
+ void AtBiaCallback(
+ [[maybe_unused]] bool service,
+ [[maybe_unused]] bool roam,
+ [[maybe_unused]] bool signal,
+ [[maybe_unused]] bool battery,
+ [[maybe_unused]] RawAddress* bd_addr) override {}
+};
+} // namespace bluetooth::headset
namespace bluetooth {
namespace topshim {
namespace rust {
+namespace internal {
+static HfpIntf* g_hfpif;
+
+// TODO (b/204488136): Refactor to have a2dp, gatt and hfp share these helpers.
+static RustRawAddress to_rust_address(const RawAddress& addr) {
+ RustRawAddress raddr;
+ std::copy(std::begin(addr.address), std::end(addr.address), std::begin(raddr.address));
+ return raddr;
+}
+
+static RawAddress from_rust_address(const RustRawAddress& raddr) {
+ RawAddress addr;
+ addr.FromOctets(raddr.address.data());
+ return addr;
+}
+
+static void connection_state_cb(bluetooth::headset::bthf_connection_state_t state, RawAddress* addr) {
+ RustRawAddress raddr = to_rust_address(*addr);
+ rusty::hfp_connection_state_callback(state, raddr);
+}
+
+} // namespace internal
int HfpIntf::init() {
- intf_->Init(nullptr, 1, false);
- return 0;
+ return intf_->Init(headset::DBusHeadsetCallbacks::GetInstance(), 1, false);
+}
+
+int HfpIntf::connect(RustRawAddress bt_addr) {
+ RawAddress addr = internal::from_rust_address(bt_addr);
+ return intf_->Connect(&addr);
+}
+
+int HfpIntf::disconnect(RustRawAddress bt_addr) {
+ RawAddress addr = internal::from_rust_address(bt_addr);
+ return intf_->Disconnect(&addr);
}
void HfpIntf::cleanup() {}
+std::unique_ptr<HfpIntf> GetHfpProfile(const unsigned char* btif) {
+ if (internal::g_hfpif) std::abort();
+
+ const bt_interface_t* btif_ = reinterpret_cast<const bt_interface_t*>(btif);
+
+ auto hfpif = std::make_unique<HfpIntf>(const_cast<headset::Interface*>(
+ reinterpret_cast<const headset::Interface*>(btif_->get_profile_interface("handsfree"))));
+ internal::g_hfpif = hfpif.get();
+
+ return hfpif;
+}
+
} // namespace rust
} // namespace topshim
} // namespace bluetooth
diff --git a/gd/rust/topshim/hfp/hfp_shim.h b/gd/rust/topshim/hfp/hfp_shim.h
index 01981301f..3ce8db036 100644
--- a/gd/rust/topshim/hfp/hfp_shim.h
+++ b/gd/rust/topshim/hfp/hfp_shim.h
@@ -16,23 +16,32 @@
#pragma once
+#include <memory>
+
#include "btif/include/btif_hf.h"
+#include "include/hardware/bluetooth_headset_callbacks.h"
+#include "types/raw_address.h"
namespace bluetooth {
namespace topshim {
namespace rust {
+struct RustRawAddress;
+
class HfpIntf {
public:
- // interface for Settings
+ HfpIntf(headset::Interface* intf) : intf_(intf){};
+
int init();
+ int connect(RustRawAddress bt_addr);
+ int disconnect(RustRawAddress bt_addr);
void cleanup();
private:
- bluetooth::headset::Interface* intf_ = nullptr;
+ headset::Interface* intf_;
};
-std::unique_ptr<HfpIntf> GetHfpProfile();
+std::unique_ptr<HfpIntf> GetHfpProfile(const unsigned char* btif);
} // namespace rust
} // namespace topshim
diff --git a/gd/rust/topshim/src/btif.rs b/gd/rust/topshim/src/btif.rs
index c74fc31e9..91cf7d721 100644
--- a/gd/rust/topshim/src/btif.rs
+++ b/gd/rust/topshim/src/btif.rs
@@ -543,6 +543,7 @@ impl From<BluetoothProperty> for (Box<[u8]>, bindings::bt_property_t) {
pub enum SupportedProfiles {
HidHost,
+ Hfp,
A2dp,
Gatt,
Sdp,
@@ -552,6 +553,7 @@ impl From<SupportedProfiles> for Vec<u8> {
fn from(item: SupportedProfiles) -> Self {
match item {
SupportedProfiles::HidHost => "hidhost",
+ SupportedProfiles::Hfp => "hfp",
SupportedProfiles::A2dp => "a2dp",
SupportedProfiles::Gatt => "gatt",
SupportedProfiles::Sdp => "sdp",
diff --git a/gd/rust/topshim/src/controller.rs b/gd/rust/topshim/src/controller.rs
new file mode 100644
index 000000000..198492822
--- /dev/null
+++ b/gd/rust/topshim/src/controller.rs
@@ -0,0 +1,32 @@
+#[cxx::bridge(namespace = bluetooth::topshim::rust)]
+mod ffi {
+ pub struct RustRawAddress {
+ address: [u8; 6],
+ }
+
+ unsafe extern "C++" {
+ include!("controller/controller_shim.h");
+
+ type ControllerIntf;
+
+ fn GetControllerInterface() -> UniquePtr<ControllerIntf>;
+ fn read_local_addr(self: &ControllerIntf) -> RustRawAddress;
+ }
+}
+
+pub struct Controller {
+ internal: cxx::UniquePtr<ffi::ControllerIntf>,
+}
+
+unsafe impl Send for Controller {}
+
+impl Controller {
+ pub fn new() -> Controller {
+ let intf = ffi::GetControllerInterface();
+ Controller { internal: intf }
+ }
+
+ pub fn read_local_addr(&mut self) -> [u8; 6] {
+ self.internal.read_local_addr().address
+ }
+}
diff --git a/gd/rust/topshim/src/lib.rs b/gd/rust/topshim/src/lib.rs
index a28f2aa26..c240ba811 100644
--- a/gd/rust/topshim/src/lib.rs
+++ b/gd/rust/topshim/src/lib.rs
@@ -8,5 +8,6 @@ extern crate bitflags;
pub mod bindings;
pub mod btif;
+pub mod controller;
pub mod profiles;
pub mod topstack;
diff --git a/gd/rust/topshim/src/profiles/a2dp.rs b/gd/rust/topshim/src/profiles/a2dp.rs
index ec285c4d5..380ebdd33 100644
--- a/gd/rust/topshim/src/profiles/a2dp.rs
+++ b/gd/rust/topshim/src/profiles/a2dp.rs
@@ -182,8 +182,11 @@ pub mod ffi {
unsafe fn GetA2dpSinkProfile(btif: *const u8) -> UniquePtr<A2dpSinkIntf>;
- fn init(self: Pin<&mut A2dpSinkIntf>) -> i32;
- fn cleanup(self: Pin<&mut A2dpSinkIntf>);
+ fn init(self: &A2dpSinkIntf) -> i32;
+ fn connect(self: &A2dpSinkIntf, bt_addr: RustRawAddress) -> i32;
+ fn disconnect(self: &A2dpSinkIntf, bt_addr: RustRawAddress) -> i32;
+ fn set_active_device(self: &A2dpSinkIntf, bt_addr: RustRawAddress) -> i32;
+ fn cleanup(self: &A2dpSinkIntf);
}
extern "Rust" {
fn connection_state_callback(addr: RustRawAddress, state: u32);
@@ -335,6 +338,17 @@ impl A2dp {
}
}
+#[derive(Debug)]
+pub enum A2dpSinkCallbacks {
+ ConnectionState(RawAddress, BtavConnectionState),
+}
+
+pub struct A2dpSinkCallbacksDispatcher {
+ pub dispatch: Box<dyn Fn(A2dpSinkCallbacks) + Send>,
+}
+
+type A2dpSinkCb = Arc<Mutex<A2dpSinkCallbacksDispatcher>>;
+
pub struct A2dpSink {
internal: cxx::UniquePtr<ffi::A2dpSinkIntf>,
_is_init: bool,
@@ -353,11 +367,26 @@ impl A2dpSink {
A2dpSink { internal: a2dp_sink, _is_init: false }
}
- pub fn initialize(&mut self) -> bool {
- self.internal.pin_mut().init();
+ pub fn initialize(&mut self, callbacks: A2dpSinkCallbacksDispatcher) -> bool {
+ if get_dispatchers().lock().unwrap().set::<A2dpSinkCb>(Arc::new(Mutex::new(callbacks))) {
+ panic!("Tried to set dispatcher for A2dp Sink Callbacks while it already exists");
+ }
+ self.internal.init();
true
}
+ pub fn connect(&mut self, bt_addr: RawAddress) {
+ self.internal.connect(bt_addr.into());
+ }
+
+ pub fn disconnect(&mut self, bt_addr: RawAddress) {
+ self.internal.disconnect(bt_addr.into());
+ }
+
+ pub fn set_active_device(&mut self, bt_addr: RawAddress) {
+ self.internal.set_active_device(bt_addr.into());
+ }
+
pub fn cleanup(&mut self) {}
}
diff --git a/gd/rust/topshim/src/profiles/hfp.rs b/gd/rust/topshim/src/profiles/hfp.rs
new file mode 100644
index 000000000..10d3c8d26
--- /dev/null
+++ b/gd/rust/topshim/src/profiles/hfp.rs
@@ -0,0 +1,117 @@
+use crate::btif::{BluetoothInterface, RawAddress};
+use crate::topstack::get_dispatchers;
+
+use num_traits::cast::FromPrimitive;
+use std::sync::{Arc, Mutex};
+use topshim_macros::cb_variant;
+
+#[derive(Debug, FromPrimitive, PartialEq, PartialOrd)]
+#[repr(u32)]
+pub enum BthfConnectionState {
+ Disconnected = 0,
+ Connecting,
+ Connected,
+ Disconnecting,
+}
+
+impl From<u32> for BthfConnectionState {
+ fn from(item: u32) -> Self {
+ BthfConnectionState::from_u32(item).unwrap()
+ }
+}
+
+#[cxx::bridge(namespace = bluetooth::topshim::rust)]
+pub mod ffi {
+ #[derive(Debug, Copy, Clone)]
+ pub struct RustRawAddress {
+ address: [u8; 6],
+ }
+
+ unsafe extern "C++" {
+ include!("hfp/hfp_shim.h");
+
+ type HfpIntf;
+
+ unsafe fn GetHfpProfile(btif: *const u8) -> UniquePtr<HfpIntf>;
+
+ fn init(self: Pin<&mut HfpIntf>) -> i32;
+ fn connect(self: Pin<&mut HfpIntf>, bt_addr: RustRawAddress) -> i32;
+ fn disconnect(self: Pin<&mut HfpIntf>, bt_addr: RustRawAddress) -> i32;
+ fn cleanup(self: Pin<&mut HfpIntf>);
+
+ }
+ extern "Rust" {
+ fn hfp_connection_state_callback(state: u32, addr: RustRawAddress);
+ }
+}
+
+impl From<RawAddress> for ffi::RustRawAddress {
+ fn from(addr: RawAddress) -> Self {
+ ffi::RustRawAddress { address: addr.val }
+ }
+}
+
+impl Into<RawAddress> for ffi::RustRawAddress {
+ fn into(self) -> RawAddress {
+ RawAddress { val: self.address }
+ }
+}
+
+#[derive(Debug)]
+pub enum HfpCallbacks {
+ ConnectionState(BthfConnectionState, RawAddress),
+}
+
+pub struct HfpCallbacksDispatcher {
+ pub dispatch: Box<dyn Fn(HfpCallbacks) + Send>,
+}
+
+type HfpCb = Arc<Mutex<HfpCallbacksDispatcher>>;
+
+cb_variant!(
+ HfpCb,
+ hfp_connection_state_callback -> HfpCallbacks::ConnectionState,
+ u32 -> BthfConnectionState, ffi::RustRawAddress -> RawAddress, {
+ let _1 = _1.into();
+ }
+);
+
+pub struct Hfp {
+ internal: cxx::UniquePtr<ffi::HfpIntf>,
+ _is_init: bool,
+}
+
+// For *const u8 opaque btif
+unsafe impl Send for Hfp {}
+
+impl Hfp {
+ pub fn new(intf: &BluetoothInterface) -> Hfp {
+ let hfpif: cxx::UniquePtr<ffi::HfpIntf>;
+ unsafe {
+ hfpif = ffi::GetHfpProfile(intf.as_raw_ptr());
+ }
+
+ Hfp { internal: hfpif, _is_init: false }
+ }
+
+ pub fn initialize(&mut self, callbacks: HfpCallbacksDispatcher) -> bool {
+ if get_dispatchers().lock().unwrap().set::<HfpCb>(Arc::new(Mutex::new(callbacks))) {
+ panic!("Tried to set dispatcher for HFP callbacks while it already exists");
+ }
+ self.internal.pin_mut().init();
+ true
+ }
+
+ pub fn connect(&mut self, addr: RawAddress) {
+ self.internal.pin_mut().connect(addr.into());
+ }
+
+ pub fn disconnect(&mut self, addr: RawAddress) {
+ self.internal.pin_mut().disconnect(addr.into());
+ }
+
+ pub fn cleanup(&mut self) -> bool {
+ self.internal.pin_mut().cleanup();
+ true
+ }
+}
diff --git a/gd/rust/topshim/src/profiles/mod.rs b/gd/rust/topshim/src/profiles/mod.rs
index ea2f671fe..a12e7c504 100644
--- a/gd/rust/topshim/src/profiles/mod.rs
+++ b/gd/rust/topshim/src/profiles/mod.rs
@@ -1,5 +1,6 @@
pub mod a2dp;
pub mod avrcp;
pub mod gatt;
+pub mod hfp;
pub mod hid_host;
pub mod sdp;
diff --git a/gd/shim/BUILD.gn b/gd/shim/BUILD.gn
index e5a216d2a..39d46017c 100644
--- a/gd/shim/BUILD.gn
+++ b/gd/shim/BUILD.gn
@@ -20,7 +20,7 @@ source_set("BluetoothShimSources") {
]
deps = [
- "//bt/gd/dumpsys:BluetoothGeneratedDumpsysBundledSchema_cc",
+ "//bt/gd/dumpsys:libbluetooth-dumpsys",
"//bt/gd/dumpsys/bundler:BluetoothGeneratedBundlerSchema_h_bfbs",
]
diff --git a/gd/storage/config_cache.cc b/gd/storage/config_cache.cc
index fb897d5e8..b1dc1bd0b 100644
--- a/gd/storage/config_cache.cc
+++ b/gd/storage/config_cache.cc
@@ -21,10 +21,14 @@
#include <utility>
#include "hci/enum_helper.h"
+#include "os/parameter_provider.h"
#include "storage/mutation.h"
namespace {
+const std::unordered_set<std::string_view> kEncryptKeyNameList = {
+ "LinkKey", "LE_KEY_PENC", "LE_KEY_PID", "LE_KEY_LID", "LE_KEY_PCSRK", "LE_KEY_LENC", "LE_KEY_LCSRK"};
+
bool TrimAfterNewLine(std::string& value) {
std::string value_no_newline;
size_t newline_position = value.find_first_of('\n');
@@ -35,6 +39,10 @@ bool TrimAfterNewLine(std::string& value) {
return false;
}
+bool InEncryptKeyNameList(std::string key) {
+ return kEncryptKeyNameList.find(key) != kEncryptKeyNameList.end();
+}
+
} // namespace
namespace bluetooth {
@@ -48,6 +56,8 @@ const std::unordered_set<std::string_view> kClassicPropertyNames = {
const std::string ConfigCache::kDefaultSectionName = "Global";
+std::string kEncryptedStr = "encrypted";
+
ConfigCache::ConfigCache(size_t temp_device_capacity, std::unordered_set<std::string_view> persistent_property_names)
: persistent_property_names_(std::move(persistent_property_names)),
information_sections_(),
@@ -147,7 +157,11 @@ std::optional<std::string> ConfigCache::GetProperty(const std::string& section,
if (section_iter != persistent_devices_.end()) {
auto property_iter = section_iter->second.find(property);
if (property_iter != section_iter->second.end()) {
- return property_iter->second;
+ std::string value = property_iter->second;
+ if (os::ParameterProvider::GetBtKeystoreInterface() != nullptr && value == kEncryptedStr) {
+ return os::ParameterProvider::GetBtKeystoreInterface()->get_key(section + "-" + property);
+ }
+ return value;
}
}
section_iter = temporary_devices_.find(section);
@@ -187,6 +201,14 @@ void ConfigCache::SetProperty(std::string section, std::string property, std::st
}
}
if (section_iter != persistent_devices_.end()) {
+ bool is_encrypted = value == kEncryptedStr;
+ if ((!value.empty()) && os::ParameterProvider::GetBtKeystoreInterface() != nullptr &&
+ os::ParameterProvider::IsCommonCriteriaMode() && InEncryptKeyNameList(property) && !is_encrypted) {
+ if (os::ParameterProvider::GetBtKeystoreInterface()->set_encrypt_key_or_remove_key(
+ section + "-" + property, value)) {
+ value = kEncryptedStr;
+ }
+ }
section_iter->second.insert_or_assign(property, std::move(value));
PersistentConfigChangedCallback();
return;
@@ -239,6 +261,10 @@ bool ConfigCache::RemoveProperty(const std::string& section, const std::string&
}
if (value.has_value()) {
PersistentConfigChangedCallback();
+ if (os::ParameterProvider::GetBtKeystoreInterface() != nullptr && os::ParameterProvider::IsCommonCriteriaMode() &&
+ InEncryptKeyNameList(property)) {
+ os::ParameterProvider::GetBtKeystoreInterface()->set_encrypt_key_or_remove_key(section + "-" + property, "");
+ }
return true;
} else {
return false;
@@ -255,6 +281,35 @@ bool ConfigCache::RemoveProperty(const std::string& section, const std::string&
return false;
}
+void ConfigCache::ConvertEncryptOrDecryptKeyIfNeeded() {
+ std::lock_guard<std::recursive_mutex> lock(mutex_);
+ LOG_INFO("%s", __func__);
+ auto persistent_sections = GetPersistentSections();
+ for (const auto& section : persistent_sections) {
+ auto section_iter = persistent_devices_.find(section);
+ for (const auto& property : kEncryptKeyNameList) {
+ auto property_iter = section_iter->second.find(std::string(property));
+ if (property_iter != section_iter->second.end()) {
+ bool is_encrypted = property_iter->second == kEncryptedStr;
+ if ((!property_iter->second.empty()) && os::ParameterProvider::GetBtKeystoreInterface() != nullptr &&
+ os::ParameterProvider::IsCommonCriteriaMode() && !is_encrypted) {
+ if (os::ParameterProvider::GetBtKeystoreInterface()->set_encrypt_key_or_remove_key(
+ section + "-" + std::string(property), property_iter->second)) {
+ SetProperty(section, std::string(property), kEncryptedStr);
+ }
+ }
+ if (os::ParameterProvider::GetBtKeystoreInterface() != nullptr && is_encrypted) {
+ std::string value_str =
+ os::ParameterProvider::GetBtKeystoreInterface()->get_key(section + "-" + std::string(property));
+ if (!os::ParameterProvider::IsCommonCriteriaMode()) {
+ SetProperty(section, std::string(property), value_str);
+ }
+ }
+ }
+ }
+ }
+}
+
bool ConfigCache::IsDeviceSection(const std::string& section) {
return hci::Address::IsValidAddress(section);
}
diff --git a/gd/storage/config_cache.h b/gd/storage/config_cache.h
index 0d83b4125..f0e1fe456 100644
--- a/gd/storage/config_cache.h
+++ b/gd/storage/config_cache.h
@@ -100,6 +100,7 @@ class ConfigCache {
virtual void SetProperty(std::string section, std::string property, std::string value);
virtual bool RemoveSection(const std::string& section);
virtual bool RemoveProperty(const std::string& section, const std::string& property);
+ virtual void ConvertEncryptOrDecryptKeyIfNeeded();
// TODO: have a systematic way of doing this instead of specialized methods
// Remove sections with |property| set
virtual void RemoveSectionWithProperty(const std::string& property);
diff --git a/gd/storage/storage_module.cc b/gd/storage/storage_module.cc
index c7ef38346..7d737cacb 100644
--- a/gd/storage/storage_module.cc
+++ b/gd/storage/storage_module.cc
@@ -49,6 +49,11 @@ static const std::chrono::milliseconds kDefaultConfigSaveDelay = std::chrono::mi
// The config saving delay must be bigger than this value to avoid overwhelming the disk
static const std::chrono::milliseconds kMinConfigSaveDelay = std::chrono::milliseconds(20);
+const int kConfigFileComparePass = 1;
+const int kConfigBackupComparePass = 2;
+const std::string kConfigFilePrefix = "bt_config-origin";
+const std::string kConfigFileHash = "hash";
+
const std::string StorageModule::kInfoSection = "Info";
const std::string StorageModule::kFileSourceProperty = "FileSource";
const std::string StorageModule::kTimeCreatedProperty = "TimeCreated";
@@ -134,6 +139,12 @@ void StorageModule::SaveImmediately() {
ASSERT(LegacyConfigFile::FromPath(config_file_path_).Write(pimpl_->cache_));
// 3. now write back up to disk as well
ASSERT(LegacyConfigFile::FromPath(config_backup_path_).Write(pimpl_->cache_));
+ // 4. save checksum if it is running in common criteria mode
+ if (bluetooth::os::ParameterProvider::GetBtKeystoreInterface() != nullptr &&
+ bluetooth::os::ParameterProvider::IsCommonCriteriaMode()) {
+ bluetooth::os::ParameterProvider::GetBtKeystoreInterface()->set_encrypt_key_or_remove_key(
+ kConfigFilePrefix, kConfigFileHash);
+ }
}
void StorageModule::ListDependencies(ModuleList* list) const {
@@ -147,6 +158,12 @@ void StorageModule::Start() {
LegacyConfigFile::FromPath(config_file_path_).Delete();
LegacyConfigFile::FromPath(config_backup_path_).Delete();
}
+ if (!is_config_checksum_pass(kConfigFileComparePass)) {
+ LegacyConfigFile::FromPath(config_file_path_).Delete();
+ }
+ if (!is_config_checksum_pass(kConfigBackupComparePass)) {
+ LegacyConfigFile::FromPath(config_backup_path_).Delete();
+ }
auto config = LegacyConfigFile::FromPath(config_file_path_).Read(temp_devices_capacity_);
if (!config || !config->HasSection(kAdapterSection)) {
LOG_WARN("cannot load config at %s, using backup at %s.", config_file_path_.c_str(), config_backup_path_.c_str());
@@ -179,11 +196,17 @@ void StorageModule::Start() {
// TODO (b/158035889) Migrate metrics module to GD
pimpl_ = std::make_unique<impl>(GetHandler(), std::move(config.value()), temp_devices_capacity_);
SaveDelayed();
+ if (bluetooth::os::ParameterProvider::GetBtKeystoreInterface() != nullptr) {
+ bluetooth::os::ParameterProvider::GetBtKeystoreInterface()->ConvertEncryptOrDecryptKeyIfNeeded();
+ }
}
void StorageModule::Stop() {
std::lock_guard<std::recursive_mutex> lock(mutex_);
SaveImmediately();
+ if (bluetooth::os::ParameterProvider::GetBtKeystoreInterface() != nullptr) {
+ bluetooth::os::ParameterProvider::GetBtKeystoreInterface()->clear_map();
+ }
pimpl_.reset();
}
@@ -234,5 +257,9 @@ std::vector<Device> StorageModule::GetBondedDevices() {
return result;
}
+bool StorageModule::is_config_checksum_pass(int check_bit) {
+ return ((os::ParameterProvider::GetCommonCriteriaConfigCompareResult() & check_bit) == check_bit);
+}
+
} // namespace storage
} // namespace bluetooth
diff --git a/gd/storage/storage_module.h b/gd/storage/storage_module.h
index 349179633..8853a4e76 100644
--- a/gd/storage/storage_module.h
+++ b/gd/storage/storage_module.h
@@ -145,6 +145,7 @@ class StorageModule : public bluetooth::Module {
size_t temp_devices_capacity_;
bool is_restricted_mode_;
bool is_single_user_mode_;
+ static bool is_config_checksum_pass(int check_bit);
DISALLOW_COPY_AND_ASSIGN(StorageModule);
};
diff --git a/hci/include/hci_layer.h b/hci/include/hci_layer.h
index 28a9f1751..74ffc5ce7 100644
--- a/hci/include/hci_layer.h
+++ b/hci/include/hci_layer.h
@@ -62,11 +62,11 @@ typedef struct hci_t {
base::Callback<void(const base::Location&, BT_HDR*)> send_data_cb);
// Send a command through the HCI layer
- void (*transmit_command)(BT_HDR* command,
+ void (*transmit_command)(const BT_HDR* command,
command_complete_cb complete_callback,
command_status_cb status_cb, void* context);
- future_t* (*transmit_command_futured)(BT_HDR* command);
+ future_t* (*transmit_command_futured)(const BT_HDR* command);
// Send some data downward through the HCI layer
void (*transmit_downward)(uint16_t type, void* data);
diff --git a/include/hardware/bt_keystore.h b/include/hardware/bt_keystore.h
index 5442c980f..186334dd4 100644
--- a/include/hardware/bt_keystore.h
+++ b/include/hardware/bt_keystore.h
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+#pragma once
+
+#include "string"
+
namespace bluetooth {
namespace bluetooth_keystore {
@@ -36,8 +40,11 @@ class BluetoothKeystoreInterface {
/** Register the bluetooth keystore callbacks */
virtual void init(BluetoothKeystoreCallbacks* callbacks) = 0;
+ /** Get bonded devices number to get all bonded devices key */
+ virtual void ConvertEncryptOrDecryptKeyIfNeeded() = 0;
+
/** Interface for key encrypt or remove key */
- virtual void set_encrypt_key_or_remove_key(std::string prefix,
+ virtual bool set_encrypt_key_or_remove_key(std::string prefix,
std::string encryptedString) = 0;
/** Interface for get key. */
diff --git a/internal_include/bt_target.h b/internal_include/bt_target.h
index dd8c74eba..8784a02a5 100644
--- a/internal_include/bt_target.h
+++ b/internal_include/bt_target.h
@@ -1049,4 +1049,12 @@
#include "bt_trace.h"
+#ifndef BTM_DELAY_AUTH_MS
+#define BTM_DELAY_AUTH_MS 0
+#endif
+
+#ifndef BTM_DISABLE_CONCURRENT_PEER_AUTH
+#define BTM_DISABLE_CONCURRENT_PEER_AUTH FALSE
+#endif
+
#endif /* BT_TARGET_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 000000000..80466a5ee
--- /dev/null
+++ b/le_audio/certification_tool/bap_uclient_test_tool/Android.bp
@@ -0,0 +1,55 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_bt_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_bt_license"],
+}
+
+cc_binary {
+ name: "bap_uclient_test",
+ system_ext_specific: true,
+ enabled: false,
+ srcs: ["bap_uclient_test.cpp"],
+
+ include_dirs: [
+ ".",
+ "system/bt/include",
+ "system/bt/types",
+ "vendor/qcom/opensource/commonsys/system/bt/stack/l2cap",
+ "vendor/qcom/opensource/commonsys/system/bt/utils/include",
+ "vendor/qcom/opensource/commonsys/system/bt",
+ "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 000000000..78db7037b
--- /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 <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+#include <cstring>
+#include <stdio.h>
+#include <stdint.h>
+#include <dlfcn.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <string.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <sys/prctl.h>
+#include <sys/capability.h>
+#include <map>
+#include <iomanip>
+#include <private/android_filesystem_config.h>
+#include <android/log.h>
+#include <hardware/bt_gatt_types.h>
+#include <hardware/hardware.h>
+#include <hardware/bluetooth.h>
+#include <hardware/bt_bap_uclient.h>
+#include <hardware/bt_pacs_client.h>
+#include <hardware/bt_ascs_client.h>
+
+#include <signal.h>
+#include <time.h>
+
+#include <base/bind.h>
+#include <base/callback.h>
+
+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 <BdAddr as 00112233445566>", 0 },
+ { "pacs_discovery", do_pacs_discovery, "cmd :: pacs_discovery <BdAddr>", 0 },
+ { "ascs_discovery", do_ascs_discovery, "cmd :: ascs_discovery <BdAddr>", 0 },
+ { "bap_connect", do_bap_connect, "cmd :: bap_connect <CodecConfig> <AudioConfig> <BdAddr> <profile> <direction> <context>", 0 },
+ { "bap_disconnect", do_bap_disconnect, "cmd :: bap_disconnect <BdAddr> <profile> <direction> <context>", 0 },
+ { "bap_start", do_bap_start, "cmd :: bap_start <BdAddr> <profile> <direction> <context>", 0 },
+ { "bap_stop", do_bap_stop, "cmd :: bap_stop <BdAddr> <profile> <direction> <context>", 0 },
+ { "bap_disc_in_connecting", do_bap_disc_in_connecting, "cmd :: bap_disc_in_connecting <CodecConfig> <AudioConfig> <BdAddr> <profile> <direction> <context>", 0 },
+ { "bap_disc_in_starting", do_bap_disc_in_starting, "cmd :: bap_disc_in_starting <BdAddr> <profile> <direction> <context>", 0 },
+ { "bap_disc_in_stopping", do_bap_disc_in_stopping, "cmd :: bap_disc_in_stopping <BdAddr> <profile> <direction> <context>", 0 },
+ { "bap_stop_in_starting", do_bap_stop_in_starting, "cmd :: bap_stop_in_starting <BdAddr> <profile> <direction> <context>", 0 },
+ { "bap_update_stream", do_bap_update_stream, "cmd :: bap_update_stream <BdAddr> <profile> <direction> <new context>", 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<int>(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<bluetooth::bap::pacs::CodecConfig> sink_pac_records,
+ std::vector<bluetooth::bap::pacs::CodecConfig> 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<bluetooth::bap::ascs::AseOpStatus> 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<bluetooth::bap::ascs::AseParams> sink_ase_list,
+ std::vector<bluetooth::bap::ascs::AseParams> 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 <typename T>
+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<bluetooth::bap::ucast::StreamStateInfo> 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<int> (it->stream_state));
+ if(static_cast<int> (it->stream_state) == 2 ||
+ static_cast<int> (it->stream_state) == 0) {
+ bapConnectionComplete = 1;
+ }
+ }
+ }
+ void OnStreamConfig(const RawAddress &address,
+ std::vector<bluetooth::bap::ucast::StreamConfigInfo> 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<Servers> serv;
+} UserParms;
+
+typedef struct {
+ uint8_t audio_dir;
+ uint8_t stereo;
+} AudioType;
+
+typedef struct {
+ uint8_t num_servers;
+ uint8_t num_cises;
+ std::vector<AudioType> audio_type;
+} AudioConfigSettings;
+
+//
+
+std::map<std::string, AudioConfigSettings> 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 <std::string> 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<uint16_t>(getInt(token[4]));
+ serv1.direction = static_cast<uint16_t>(getInt(token[5]));
+ serv1.context = static_cast<uint16_t>(getInt(token[6]));
+ ptr->serv.push_back(serv1);
+ memcpy(serv2.bdAddr, token[7].c_str(), token[7].size());
+ serv2.profile = static_cast<uint16_t>(getInt(token[8]));
+ serv2.direction = static_cast<uint16_t>(getInt(token[9]));
+ serv2.context = static_cast<uint16_t>(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<uint16_t>(getInt(token[2]));
+ serv1.direction = static_cast<uint16_t>(getInt(token[3]));
+ serv1.context = static_cast<uint16_t>(getInt(token[4]));
+ ptr->serv.push_back(serv1);
+ memcpy(serv2.bdAddr, token[5].c_str(), token[5].size());
+ serv2.profile = static_cast<uint16_t>(getInt(token[6]));
+ serv2.direction = static_cast<uint16_t>(getInt(token[7]));
+ serv2.context = static_cast<uint16_t>(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<uint16_t>(getInt(token[4]));
+ serv1.direction = static_cast<uint16_t>(getInt(token[5]));
+ serv1.context = static_cast<uint16_t>(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<uint16_t>(getInt(token[2]));
+ serv1.direction = static_cast<uint16_t>(getInt(token[3]));
+ serv1.context = static_cast<uint16_t>(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<uint64_t>(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<uint64_t>(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<uint64_t>(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<int>(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<int>(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<std::string, int> 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<std::string, int>::iterator it =
+ octetPerFrame.begin(); it != octetPerFrame.end(); it++)
+ {
+ if (key.compare(it->first) == 0)
+ ret = it->second;
+ }
+ return ret;
+}
+
+std::map<std::string, int> 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<std::string, int>::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<std::string, AudioConfigSettings>::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<uint8_t>(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<uint16_t>(tport_latency);
+ cig_config.max_tport_latency_s_to_m = static_cast<uint16_t>(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<uint16_t>(max_sdu_m_to_s);
+ cis_config.max_sdu_s_to_m = static_cast<uint16_t>(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<uint16_t>(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<uint16_t>(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<uint8_t>(rtn);
+ cis_config.rtn_s_to_m = static_cast<uint8_t>(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<uint16_t>(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<uint16_t>(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<bluetooth::bap::ucast::StreamConnect> 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<RawAddress> 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<bluetooth::bap::ucast::StreamType> streams;
+ if (args.serv[i].direction & 1) {
+ bluetooth::bap::ucast::StreamType type_1 =
+ { .type = static_cast<uint8_t>(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<uint8_t>(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<bluetooth::bap::ucast::StreamType> streams;
+ if (args.serv[i].direction & 1) {
+ bluetooth::bap::ucast::StreamType type_1 =
+ { .type = static_cast<uint8_t>(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<uint8_t>(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<bluetooth::bap::ucast::StreamType> streams;
+ if (args.serv[i].direction & 1) {
+ bluetooth::bap::ucast::StreamType type_1 =
+ { .type = static_cast<uint8_t>(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<uint8_t>(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<bluetooth::bap::ucast::StreamUpdate> Update_Stream;
+ if (args.serv[i].direction & 1) {
+ bluetooth::bap::ucast::StreamType type_1 =
+ { .type = static_cast<uint8_t>(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<uint8_t>(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 000000000..90679df35
--- /dev/null
+++ b/le_audio/certification_tool/types/Android.bp
@@ -0,0 +1,44 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_bt_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_bt_license"],
+}
+
+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 000000000..d05f4370f
--- /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 <base/rand_util.h>
+#include <base/strings/stringprintf.h>
+#include <string.h>
+#include <algorithm>
+
+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 000000000..0fe5b5bd1
--- /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 <stdint.h>
+#include <array>
+#include <string>
+
+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<uint8_t, kNumBytes128>;
+
+ 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<bluetooth::Uuid> {
+ std::size_t operator()(const bluetooth::Uuid& key) const {
+ const auto& uuid_bytes = key.To128BitBE();
+ std::hash<std::string> hash_fn;
+ return hash_fn(std::string(reinterpret_cast<const char*>(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 000000000..775a412a3
--- /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 <base/strings/string_split.h>
+#include <base/strings/stringprintf.h>
+#include <stdint.h>
+#include <algorithm>
+#include <vector>
+
+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<uint16_t>(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<std::string> 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 000000000..b0fffc885
--- /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 <cstring>
+#include <string>
+
+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 000000000..8369f5f36
--- /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 <base/strings/string_split.h>
+#include <base/strings/stringprintf.h>
+#include <stdint.h>
+#include <algorithm>
+#include <vector>
+
+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<std::string> 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 000000000..ca750181b
--- /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 <array>
+#include <cstring>
+#include <string>
+
+/** 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<RawAddress> {
+ std::size_t operator()(const RawAddress& val) const {
+ static_assert(sizeof(uint64_t) >= RawAddress::kLength);
+ uint64_t int_addr = 0;
+ memcpy(reinterpret_cast<uint8_t*>(&int_addr), val.address,
+ RawAddress::kLength);
+ return std::hash<uint64_t>{}(int_addr);
+ }
+};
diff --git a/le_audio/frameworks/base/Android.bp b/le_audio/frameworks/base/Android.bp
new file mode 100644
index 000000000..95d4675f5
--- /dev/null
+++ b/le_audio/frameworks/base/Android.bp
@@ -0,0 +1,31 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_bt_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_bt_license"],
+}
+
+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 000000000..bbb5a3007
--- /dev/null
+++ b/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastAudioScanAssistCallback.java
@@ -0,0 +1,178 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 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<BleBroadcastSourceChannel> 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 000000000..c16bcb6fe
--- /dev/null
+++ b/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastAudioScanAssistManager.java
@@ -0,0 +1,635 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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.
+ * <p>
+ * Use {@link BleBroadcastAudioScanAssistManager()} to get an
+ * instance of {@link BleBroadcastAudioScanAssistManager}.
+ * <p>
+ * <b>Note:</b> 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
+ *
+ * <p>This intent will have two extra:
+ * <ul>
+ * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device for which broadcast reciver
+ * state information is broadcasted. It can
+ * be null if no device is active. </li>
+ * </ul>
+ * <ul>
+ * <li> {@link BleBroadcastSourceInfo#EXTRA_SOURCE_INFO} - The BleBroadcastSourceInfo Object
+ * having information Broadcast receiver state </li>
+ * </ul>
+ * <ul>
+ * <li> {@link BleBroadcastSourceInfo#EXTRA_SOURCE_INFO_INDEX} - Index of the BleBroadcastSourceInfo
+ * object broadcasted </li>
+ * </ul>
+ * <ul>
+ * <li> {@link BleBroadcastSourceInfo#EXTRA_MAX_NUM_SOURCE_INFOS} - Maximum number of source Informations
+ * that this Broadcast receiver can hold </li>
+ * </ul>
+ *
+ * <p>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<BleBroadcastSourceInfo> 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.size(); i++) {
+ if (srcInfo.matches(currentSourceInfos.get(i))) {
+ ret = false;
+ break;
+ }
+ }
+ }
+
+ log(TAG, "isValidBroadcastSourceInfo returns: " + ret);
+ return ret;
+ }
+
+ private boolean isValidSourceId (byte sourceId) {
+ boolean retVal = false;
+ List<BleBroadcastSourceInfo> currentSourceInfos =
+ mBluetoothSyncHelper.getAllBroadcastSourceInformation(mBluetoothDevice);
+ if (currentSourceInfos == null) {
+ retVal = false;
+ } else {
+ for (int i=0; i<currentSourceInfos.size(); i++) {
+ if (currentSourceInfos.get(i).getSourceId() == sourceId) {
+ retVal = true;
+ break;
+ }
+ }
+ }
+ log(TAG, "isValidSourceId returns: " + retVal);
+ return retVal;
+ }
+
+ private void printSelectedIndicies(List<BleBroadcastSourceChannel> selectedBISIndicies) {
+ if (selectedBISIndicies == null) {
+ log(TAG, "printSelectedIndicies : no selected indicies");
+ return;
+ }
+ for (int i=0; i<selectedBISIndicies.size(); i++) {
+ log(TAG, selectedBISIndicies.get(i).getDescription() + ": " + selectedBISIndicies.get(i).getStatus());
+ }
+ }
+ /**
+ * Adds a broadcast source information to the Scan delegator. This internally performs Periodic
+ * synchronization to the given Broadcast source device, upon acquision of Synchronization information,
+ * It will be written on to the "Scan delegators" Characteristics
+ *
+ * Result of addition of Broadcast source to the scan delegator will be delivered through
+ * {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastAudioSourceAdded}
+ *
+ * Successful addition Broadcast source 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. add broadcast source operation will be performed on behalf of
+ * all the Coordinated set devices
+ *
+ * Same Broadcast source Information will be written on to all the members of Coordinated set and
+ * PAST will be performed based on the request.
+ *
+ * In case of Group Operation, If there is any matching entry already present in any of coordinated set members,
+ * Add Broadcast source opeation will be failed and result will notified through
+ * {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastAudioSourceAdded}
+ *
+ * @param audioSource {@link #BluetoothDevice} object selected as Source which need to be synchronized with
+ * @param ScanResult {@link #ScanResult} result obtained from the {@link BleBroadcastAudioScanAssistCallback#onBleBroadcastSourceFound}
+ * @param syncState can be one of {@link #SYNC_METADATA},
+ * {@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 BleBroadcastSourceChannel} 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 add Broadcast source operation
+ * false otherwise
+ * @hide
+ */
+ public boolean addBroadcastSource (BluetoothDevice audioSource,
+ @BroadcastAssistSyncState int syncState,
+ List<BleBroadcastSourceChannel> 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<BleBroadcastSourceChannel> 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<BleBroadcastSourceInfo> 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 000000000..d8f3d61d8
--- /dev/null
+++ b/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastSourceChannel.java
@@ -0,0 +1,231 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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.
+ *
+ * <p>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<BleBroadcastSourceChannel> CREATOR =
+ new Parcelable.Creator<BleBroadcastSourceChannel>() {
+ 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 000000000..1e159017c
--- /dev/null
+++ b/le_audio/frameworks/base/core/java/android/bluetooth/BleBroadcastSourceInfo.java
@@ -0,0 +1,1052 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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
+ *
+ * <p>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}
+ *
+ * <p>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
+ *
+ * <p> 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
+ *
+ * <p> 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
+ *
+ * <p> 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<Integer, Integer> mAudioBisIndexList = new HashMap <Integer, Integer>();
+ private @BroadcastAssistEncryptionState int mEncyptionStatus;
+ private Map<Integer, byte[]> mMetadataList = new HashMap<Integer, byte[]>();
+ 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<BleBroadcastSourceChannel> 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<selectedBISIndicies.size(); i++) {
+ if (selectedBISIndicies.get(i).getStatus() == true) {
+ Integer audioBisIndex = 0;
+ int subGroupId = selectedBISIndicies.get(i).getSubGroupId();
+ if (mAudioBisIndexList.containsKey(subGroupId)) {
+ audioBisIndex = mAudioBisIndexList.get(subGroupId);
+ }
+ audioBisIndex = audioBisIndex | (1<<selectedBISIndicies.get(i).getIndex());
+ BASS_Debug(TAG, "index" + selectedBISIndicies.get(i).getIndex() + "is set");
+ mAudioBisIndexList.put(subGroupId, audioBisIndex);
+ }
+ }
+ }
+
+ //not valid info
+ mSourceId = BROADCAST_ASSIST_INVALID_SOURCE_ID;
+ mEncyptionStatus = BROADCAST_ASSIST_ENC_STATE_INVALID;
+ mBroadcastCode = null;
+ mBadBroadcastCode = null;
+ mNumSubGroups = 0;
+ }
+
+ /**
+ * 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 sourceId Source Id for this broadcast source info object
+ * @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
+ *
+ *
+ * @deprecated
+ * @hide
+ */
+ @Deprecated
+ public BleBroadcastSourceInfo (BluetoothDevice audioSource,
+ byte sourceId,
+ byte advSid,
+ int broadcasterId,
+ @BroadcastAssistAddressType int addressType,
+ @BroadcastAssistMetadataSyncState int metadataSyncstate,
+ @BroadcastAssistEncryptionState int encryptionStatus,
+ byte[] badCode,
+ byte numSubGroups,
+ @BroadcastAssistAudioSyncState int audioSyncstate,
+ Map<Integer, List<BleBroadcastSourceChannel>> selectedBISIndiciesList,
+ Map<Integer, byte[]> 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<Integer, List<BleBroadcastSourceChannel>> entry : selectedBISIndiciesList.entrySet()) {
+ List<BleBroadcastSourceChannel> selectedBISIndicies = entry.getValue();
+ if (selectedBISIndicies == null) {
+ //do nothing
+ BASS_Debug(TAG, "selectedBISIndiciesList is null");
+ } else {
+ for (int i=0; i<selectedBISIndicies.size(); i++) {
+ if (selectedBISIndicies.get(i).getStatus() == true) {
+ audioBisIndex = audioBisIndex | (1<<selectedBISIndicies.get(i).getIndex());
+ BASS_Debug(TAG, "index" + selectedBISIndicies.get(i).getIndex() + "is set");
+ }
+ }
+ }
+ BASS_Debug(TAG, "subGroupId:" + entry.getKey() + "audioBisIndex" + audioBisIndex);
+ mAudioBisIndexList.put(entry.getKey(), audioBisIndex);
+ }
+ }
+ if (metadataList != null) {
+ for (Map.Entry<Integer, byte[]> 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<BleBroadcastSourceChannel> 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<selectedBISIndicies.size(); i++) {
+ if (selectedBISIndicies.get(i).getStatus() == true) {
+ Integer audioBisIndex = 0;
+ int subGroupId = selectedBISIndicies.get(i).getSubGroupId();
+ if (mAudioBisIndexList.containsKey(subGroupId)) {
+ audioBisIndex = mAudioBisIndexList.get(subGroupId);
+ }
+ audioBisIndex = audioBisIndex | (1<<selectedBISIndicies.get(i).getIndex());
+ BASS_Debug(TAG, "index" + selectedBISIndicies.get(i).getIndex() + "is set");
+ BASS_Debug(TAG, "audioBisIndex" + audioBisIndex);
+ mAudioBisIndexList.put(subGroupId, audioBisIndex);
+ }
+ }
+
+ }
+ /*if (metadata != null && metadata.length != 0) {
+ mMetadata = new byte[metadata.length];
+ System.arraycopy(metadata, 0, mMetadata, 0, metadata.length);
+ }*/
+ mBroadcastCode = broadcastCode;
+ mBadBroadcastCode = null;
+ mNumSubGroups = 0;
+ }
+
+ /*package*/ BleBroadcastSourceInfo (BluetoothDevice device,
+ byte sourceId,
+ byte advSid,
+ int broadcasterId,
+ @BroadcastAssistAddressType int addressType,
+ @BroadcastAssistMetadataSyncState int metadataSyncstate,
+ @BroadcastAssistAudioSyncState int audioSyncstate,
+ @BroadcastAssistEncryptionState int encryptionStatus,
+ String broadcastCode,
+ byte[] badCode,
+ byte numSubGroups,
+ Map<Integer, Integer> bisIndiciesList,
+ Map<Integer, byte[]> 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<Integer, Integer> (bisIndiciesList);
+ mMetadataList = new HashMap<Integer, byte[]> (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<BleBroadcastSourceChannel> selectedBISIndicies) {
+ if (selectedBISIndicies == null) {
+ //set No preference
+ BASS_Debug(TAG, "selectedBISIndiciesList is null");
+ return;
+ }
+ for (int i=0; i<selectedBISIndicies.size(); i++) {
+ if (selectedBISIndicies.get(i).getStatus() == true) {
+ Integer audioBisIndex = 0;
+ int subGroupId = selectedBISIndicies.get(i).getSubGroupId();
+ if (mAudioBisIndexList.containsKey(subGroupId)) {
+ audioBisIndex = mAudioBisIndexList.get(subGroupId);
+ }
+ audioBisIndex = audioBisIndex | (1<<selectedBISIndicies.get(i).getIndex());
+ BASS_Debug(TAG, "index" + selectedBISIndicies.get(i).getIndex() + "is set");
+ mAudioBisIndexList.put(subGroupId, audioBisIndex);
+ }
+ }
+
+ }
+ /**
+ * Gets the Broadcast channels index and the sync status from BleBroadcastSourceInfo Object
+ * This maps the various broadcast source indicies and sync status of them
+ *
+ * @param int audio BIS index from the BleBroadcastSourceInfo object
+ * @hide
+ */
+ public List<BleBroadcastSourceChannel> getBroadcastChannelsSyncStatus () {
+ List<BleBroadcastSourceChannel> bcastIndicies = new ArrayList<BleBroadcastSourceChannel>();
+ for (int i=0; i<mNumSubGroups; i++) {
+ int bisIndexValue = mAudioBisIndexList.get(i);
+ int index =0;
+ while (bisIndexValue != 0) {
+ if ((bisIndexValue&0x01) == 0x01) {
+ BleBroadcastSourceChannel bI =
+ new BleBroadcastSourceChannel(index, String.valueOf(index), true, i, mMetadataList.get(i));
+ bcastIndicies.add(bI);
+ }
+ bisIndexValue = bisIndexValue>>1;
+ index++;
+ }
+ }
+
+ BASS_Debug(TAG, "returning Bisindicies:" + bcastIndicies);
+ return bcastIndicies;
+ }
+
+ @UnsupportedAppUsage
+ @Deprecated
+ public Map<Integer, Integer> 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<Integer, Integer> bisIndexList) {
+ dest.writeInt(bisIndexList.size());
+ for (Map.Entry<Integer, Integer> entry : bisIndexList.entrySet()) {
+ dest.writeInt(entry.getKey());
+ dest.writeInt(entry.getValue());
+ }
+ }
+
+ private static void readMapFromParcel(Parcel in, Map<Integer, Integer> 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<Integer, byte[]> metadataList) {
+ dest.writeInt(metadataList.size());
+ for (Map.Entry<Integer, byte[]> 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<Integer, byte[]> 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<BleBroadcastSourceInfo> CREATOR =
+ new Parcelable.Creator<BleBroadcastSourceInfo>() {
+ 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<Integer,Integer> bisIndexList = new HashMap <Integer, Integer>();
+ readMapFromParcel(in, bisIndexList);
+ Map<Integer,byte[]> metadataList = new HashMap <Integer, byte[]>();
+ 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 000000000..b709078cb
--- /dev/null
+++ b/le_audio/frameworks/base/core/java/android/bluetooth/BluetoothBroadcast.java
@@ -0,0 +1,280 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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.
+ *
+ * <p>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.
+ *
+ * <p>This intent will have 3 extras:
+ * <ul>
+ * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
+ * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
+ * </ul>
+ *
+ * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+ * {@link #STATE_Disabled}, {@link #Enabling},
+ * {@link #STATE_ENABLED}, {@link #STATE_DISABLING}.
+ *
+ * <p>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.
+ *
+ * <p>This intent will have 3 extras:
+ * <ul>
+ * <li> {@link #EXTRA_STATE} - The current audio state . </li>
+ * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous audio state.</li>
+ * </ul>
+ *
+ * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+ * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
+ *
+ * <p>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.
+ *
+ * <p>This intent will have 2 extras:
+ * <ul>
+ * <li> {@link #EXTRA_STATE} - The current audio state . </li>
+ * </ul>
+ *
+ * <p>{@link #EXTRA_STATE} can be any of
+ * {@link #TRUE}, {@link #FALSE},
+ *
+ * <p>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<IBluetoothBroadcast> 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<BluetoothDevice> 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<BluetoothDevice> 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 000000000..7072891d9
--- /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
+ *
+ * <p>BluetoothSyncHelper is a proxy object for controlling the Bluetooth Scan Offloader (BASS client)
+ * Service via IPC.
+ *
+ * <p> Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothScanOfflaoder proxy object. Use
+ * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
+ *
+ * <p> Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothSyncHelper proxy object. Use
+ * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
+ *
+ * <b>Note:</b> 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<BleBroadcastAudioScanAssistCallback,
+ IBleBroadcastAudioScanAssistCallback /* callbackWrapper */> mAppCallbackWrappers;
+
+ private Map<BluetoothDevice,
+ BleBroadcastAudioScanAssistManager> sBleAssistManagers = null;
+ private Context mContext = null;
+
+ /**
+ * Intent used to broadcast the change in connection state of the Bass client
+ * profile.
+ *
+ * <p>This intent will have 3 extras:
+ * <ul>
+ * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
+ * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
+ * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+ * </ul>
+ *
+ * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+ * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+ * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+ *
+ * <p>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<IBluetoothSyncHelper> 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<BleBroadcastAudioScanAssistCallback, IBleBroadcastAudioScanAssistCallback>();
+ sBleAssistManagers = new IdentityHashMap<BluetoothDevice, BleBroadcastAudioScanAssistManager>();
+ 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.
+ *
+ * <p> 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.
+ *
+ * <p>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
+ *
+ * <p> 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.
+ *
+ * <p> 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.
+ *
+ * <p>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<BluetoothDevice> 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<BluetoothDevice>();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return new ArrayList<BluetoothDevice>();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public @NonNull List<BluetoothDevice> 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<BluetoothDevice>();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return new ArrayList<BluetoothDevice>();
+ }
+ }
+
+ /**
+ * {@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.
+ *
+ * <p> 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
+ *
+ * <p> 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<BleBroadcastSourceChannel> 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<BleBroadcastSourceInfo> 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 000000000..ac4fdad86
--- /dev/null
+++ b/le_audio/frameworks/base/packages/SettingsLib/Android.bp
@@ -0,0 +1,31 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_bt_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_bt_license"],
+}
+
+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 000000000..677186eab
--- /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<BluetoothDevice> 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<BluetoothDevice> 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<BluetoothDevice> getConnectableDevices() {
+ return getDevicesByStates(new int[] {
+ BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING});
+ }
+
+ private List<BluetoothDevice> getDevicesByStates(int[] states) {
+ if (mService == null) {
+ return new ArrayList<BluetoothDevice>(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 000000000..d1d9c013c
--- /dev/null
+++ b/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastProfile.java
@@ -0,0 +1,180 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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 000000000..054623e92
--- /dev/null
+++ b/le_audio/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastSourceInfoHandler.java
@@ -0,0 +1,79 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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 000000000..f1d0afd9b
--- /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<Integer, BleBroadcastSourceInfo> mBleBroadcastReceiverStates
+ = new HashMap<Integer, BleBroadcastSourceInfo>();
+ private LocalBluetoothProfileManager mProfileManager = null;
+ static private Map<CachedBluetoothDevice,
+ VendorCachedBluetoothDevice> mVcbdEntries = new IdentityHashMap<CachedBluetoothDevice, VendorCachedBluetoothDevice>();
+
+ 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<Integer, BleBroadcastSourceInfo>();
+ InitializeSAManager();
+ }
+
+ VendorCachedBluetoothDevice(Context context, LocalBluetoothProfileManager profileManager,
+ BluetoothDevice device) {
+ super(context, profileManager, device);
+ mProfileManager = profileManager;
+ mBleBroadcastReceiverStates = new HashMap<Integer, BleBroadcastSourceInfo>();
+ 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<BleBroadcastSourceChannel> 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<BleBroadcastSourceInfo> srcInfo = mScanAssistManager.getAllBroadcastSourceInformation();
+ if (srcInfo != null) {
+ ret = srcInfo.size();
+ }
+ if (V) {
+ Log.d(TAG, "getNumberOfBleBroadcastReceiverStates:"+ ret);
+ }
+ return ret;
+ }
+
+ public Map<Integer, BleBroadcastSourceInfo> getAllBleBroadcastreceiverStates() {
+ if (mScanAssistManager == null) {
+ InitializeSAManager();
+ if (mScanAssistManager == null) {
+ Log.e(TAG, "SA Manager cant be initialized");
+ return null;
+ }
+ }
+ List<BleBroadcastSourceInfo> srcInfos = mScanAssistManager.getAllBroadcastSourceInformation();
+ if (srcInfos == null) {
+ Log.e(TAG, "getAllBleBroadcastreceiverStates: no src Info");
+ return null;
+ }
+ for (int i=0; i<srcInfos.size(); i++) {
+ BleBroadcastSourceInfo sI = srcInfos.get(i);
+ mBleBroadcastReceiverStates.put((int)sI.getSourceId(), sI);
+ }
+ return mBleBroadcastReceiverStates;
+ }
+
+ void onBroadcastReceiverStateChanged (BleBroadcastSourceInfo srcInfo, int index,
+ int maxSourceInfoNum) {
+ updateBroadcastreceiverStates(srcInfo, index, maxSourceInfoNum);
+ }
+
+ public void setScanResult(ScanResult res) {
+ mScanRes = res;
+ }
+
+ public ScanResult getScanResult() {
+ return mScanRes;
+ }
+
+ @androidx.annotation.Keep
+ public boolean isBroadcastAudioSynced() {
+ if (mScanAssistManager == null) {
+ InitializeSAManager();
+ if (mScanAssistManager == null) {
+ Log.e(TAG, "SA Manager cant be initialized");
+ return false;
+ }
+ }
+ List<BleBroadcastSourceInfo> srcInfos = mScanAssistManager.getAllBroadcastSourceInformation();
+ if (srcInfos == null) {
+ Log.e(TAG, "isBroadcastAudioSynced: no src Info");
+ return false;
+ }
+ for (int i=0; i<srcInfos.size(); i++) {
+ BleBroadcastSourceInfo sI = srcInfos.get(i);
+ if (sI.getAudioSyncState() ==
+ BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED) {
+ return true;
+ }
+ }
+ Log.d(TAG,"isAudioSynced: false");
+ return false;
+ }
+}
diff --git a/le_audio/packages/apps/Bluetooth/Android.bp b/le_audio/packages/apps/Bluetooth/Android.bp
new file mode 100644
index 000000000..8cc96c23a
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/Android.bp
@@ -0,0 +1,39 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_bt_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_bt_license"],
+}
+
+filegroup {
+ name: "bluetooth-apps-adva-srcs",
+ srcs: ["src/com/android/bluetooth/groupclient/*.java",
+ "src/com/android/bluetooth/bassclient/*.java",
+ "src/com/android/bluetooth/broadcast/*.java",
+ "src/com/android/bluetooth/acm/*.java",
+ "src/com/android/bluetooth/pacsclient/*.java",
+ "src/com/android/bluetooth/apm/*.java",
+ "src/com/android/bluetooth/vcp/*.java",
+ "src/com/android/bluetooth/mcp/*.java",
+ "src/com/android/bluetooth/cc/*.java"],
+}
diff --git a/le_audio/packages/apps/Bluetooth/jni/Android.bp b/le_audio/packages/apps/Bluetooth/jni/Android.bp
new file mode 100644
index 000000000..971d8cdf1
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/jni/Android.bp
@@ -0,0 +1,61 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_bt_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_bt_license"],
+}
+
+cc_defaults {
+ name: "libbluetoothqti_jni_adva_default",
+ header_libs: ["libbluetooth_headers", "libnativehelper_header_only"],
+ include_dirs: [
+ "system/bt/types",
+ "vendor/qcom/opensource/commonsys/packages/apps/Bluetooth/jni",
+ "vendor/qcom/opensource/commonsys/bluetooth_ext/vhal/include",
+ "vendor/qcom/opensource/commonsys/bluetooth_lea/vhal/include",
+ "vendor/qcom/opensource/commonsys-intf/bluetooth/include",
+ "vendor/qcom/opensource/commonsys/bluetooth_lea/vhal/include",
+ ],
+ shared_libs: [
+ "libandroid_runtime",
+ ],
+}
+
+// BT LE Audio static library for target
+// ========================================================
+cc_library_static {
+ name: "libbluetoothqti_jni_adva",
+ defaults: ["libbluetoothqti_jni_adva_default"],
+ enabled: false,
+ srcs: [
+ "com_android_bluetooth_btservice_AdapterServiceExt.cpp",
+ "com_android_bluetooth_pacs_client.cpp",
+ "com_android_bluetooth_broadcast.cpp",
+ "com_android_bluetooth_csip_client.cpp",
+ "com_android_bluetooth_acm.cpp",
+ "com_android_bluetooth_apm.cpp",
+ "com_android_bluetooth_vcp_controller.cpp",
+ "com_android_bluetooth_mcp.cpp",
+ "com_android_bluetooth_cc.cpp",
+ ],
+}
diff --git a/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_acm.cpp b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_acm.cpp
new file mode 100644
index 000000000..900af0efb
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_acm.cpp
@@ -0,0 +1,557 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 "BluetoothAcmServiceJni"
+
+#define LOG_NDEBUG 0
+
+#include "android_runtime/AndroidRuntime.h"
+#include "com_android_bluetooth.h"
+#include "hardware/bt_acm.h"
+#include "utils/Log.h"
+
+#include <string.h>
+#include <shared_mutex>
+
+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<std::shared_timed_mutex> lock(callbacks_mutex);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ ALOGE("%s: Fail to new jbyteArray bd addr", __func__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(
+ addr.get(), 0, sizeof(RawAddress),
+ reinterpret_cast<const jbyte*>(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<std::shared_timed_mutex> lock(callbacks_mutex);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ ALOGE("%s: Fail to new jbyteArray bd addr", __func__);
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(
+ addr.get(), 0, sizeof(RawAddress),
+ reinterpret_cast<const jbyte*>(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<CodecConfig> codecs_local_capabilities,
+ std::vector<CodecConfig> codecs_selectable_capabilities, uint16_t contextType) {
+ ALOGI("%s", __func__);
+
+ std::shared_lock<std::shared_timed_mutex> 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<jbyteArray> 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<const jbyte*>(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, "<init>", "(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<CodecConfig> prepareCodecPreferences(
+ JNIEnv* env, jobject object, jobjectArray codecConfigArray) {
+ std::vector<CodecConfig> 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<CodecIndex>(codecType),
+ .codec_priority =
+ static_cast<CodecPriority>(codecPriority),
+ .sample_rate = static_cast<CodecSampleRate>(sampleRate),
+ .bits_per_sample =
+ static_cast<CodecBPS>(bitsPerSample),
+ .channel_mode =
+ static_cast<CodecChannelMode>(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<std::shared_timed_mutex> interface_lock(interface_mutex);
+ std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
+ const bt_interface_t* btInf = getBluetoothInterface();
+ if (btInf == nullptr) {
+ 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<CodecConfig> 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<std::shared_timed_mutex> interface_lock(interface_mutex);
+ std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
+ const bt_interface_t* btInf = getBluetoothInterface();
+ if (btInf == nullptr) {
+ 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<std::shared_timed_mutex> 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<const uint8_t*>(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<std::shared_timed_mutex> 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<const uint8_t*>(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<std::shared_timed_mutex> 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<const uint8_t*>(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<std::shared_timed_mutex> 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<const uint8_t*>(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<std::shared_timed_mutex> 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<const uint8_t*>(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<std::shared_timed_mutex> 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<const uint8_t*>(addr));
+ std::vector<CodecConfig> 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<std::shared_timed_mutex> 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<const uint8_t*>(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 000000000..6ffa2daff
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_apm.cpp
@@ -0,0 +1,194 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 "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 <string.h>
+#include <shared_mutex>
+
+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<std::shared_timed_mutex> lock(callbacks_mutex);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return -1;
+
+ ScopedLocalRef<jbyteArray> 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<const jbyte*>(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<std::shared_timed_mutex> interface_lock(interface_mutex);
+ std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
+ const bt_interface_t* btInf = getBluetoothInterface();
+ if (btInf == nullptr) {
+ 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<std::shared_timed_mutex> interface_lock(interface_mutex);
+ std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
+ const bt_interface_t* btInf = getBluetoothInterface();
+ if (btInf == nullptr) {
+ 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<std::shared_timed_mutex> 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<const uint8_t*>(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<std::shared_timed_mutex> 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 000000000..75ea08504
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_broadcast.cpp
@@ -0,0 +1,470 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 "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 <string.h>
+#include <shared_mutex>
+
+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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<btav_a2dp_codec_config_t> codec_capabilities) {
+ ALOGI("%s", __func__);
+
+ std::shared_lock<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> lock(callbacks_mutex);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+ /*ScopedLocalRef<jbyteArray> 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<const jbyte*>(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<uint16_t> bis_handles) {
+ ALOGI("%s", __func__);
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+ ScopedLocalRef<jcharArray> 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<uint8_t> broadcast_id) {
+ ALOGI("%s", __func__);
+
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+ ALOGI("%s: broadcast_id size = %d",__func__,broadcast_id.size());
+ ScopedLocalRef<jbyteArray> 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, "<init>", "(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<btav_a2dp_codec_index_t>(codecType),
+ .codec_priority =
+ static_cast<btav_a2dp_codec_priority_t>(codecPriority),
+ .sample_rate = static_cast<btav_a2dp_codec_sample_rate_t>(sampleRate),
+ .bits_per_sample =
+ static_cast<btav_a2dp_codec_bits_per_sample_t>(bitsPerSample),
+ .channel_mode =
+ static_cast<btav_a2dp_codec_channel_mode_t>(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<std::shared_timed_mutex> interface_lock(interface_mutex);
+ std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
+ const bt_interface_t* btInf = getBluetoothInterface();
+ if (btInf == nullptr) {
+ 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<std::shared_timed_mutex> interface_lock(interface_mutex);
+ std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
+ const bt_interface_t* btInf = getBluetoothInterface();
+ if (btInf == nullptr) {
+ 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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 000000000..f763310d9
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterServiceExt.cpp
@@ -0,0 +1,76 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 "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 000000000..e8203eac2
--- /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 <base/bind.h>
+#include <base/callback.h>
+#include <map>
+#include <mutex>
+#include <shared_mutex>
+#include <vector>
+
+#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<std::shared_timed_mutex> 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<std::shared_timed_mutex> lock(callbacks_mutex);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state";
+ return;
+ }
+ sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+ (jbyte*)&bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_OnConnectionStateChanged,
+ (jint)state, addr.get());
+ }
+
+ void CallControlCallback(uint8_t op, std::vector<int32_t> p_indices, int count, std::vector<uint8_t> uri_data, const RawAddress& bd_addr) override {
+ LOG(INFO) << __func__;
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state";
+ return;
+ }
+ ScopedLocalRef<jintArray> indices(sCallbackEnv.get(), (jintArray)sCallbackEnv->NewIntArray(count));
+ ScopedLocalRef<jbyteArray> 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<std::shared_timed_mutex> interface_lock(interface_mutex);
+ std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
+ const bt_interface_t* btInf = getBluetoothInterface();
+ if (!btInf) {
+ 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<std::shared_timed_mutex> interface_lock(interface_mutex);
+ std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
+ const bt_interface_t* btInf = getBluetoothInterface();
+ if (btInf == nullptr) {
+ LOG(ERROR) << "Bluetooth module is not loaded";
+ return;
+ }
+
+ if (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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<uint8_t> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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 000000000..c3a8f6d68
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_csip_client.cpp
@@ -0,0 +1,409 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 "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 <shared_mutex>
+#include <string.h>
+
+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<uint8_t, Uuid::kNumBytes128> 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<jstring> 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<jstring> address(sCallbackEnv.get(),
+ bdaddr2newjstr(sCallbackEnv.get(), &bd_addr));
+
+ ALOGI("%s: new_set_found_callback: process sirk", __func__);
+ ScopedLocalRef<jbyteArray> 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<jstring> 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<RawAddress> 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<jstring> 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<jstring> 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<jbyteArray> jb(sCallbackEnv.get(), NULL);
+ jb.reset(sCallbackEnv->NewByteArray(24));
+ sCallbackEnv->SetByteArrayRegion(jb.get(), 0, 24, (jbyte*)sirk);
+
+ // address
+ ScopedLocalRef<jstring> 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<std::shared_timed_mutex> 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<const uint8_t*>(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<const uint8_t*>(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<RawAddress> 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 000000000..c82146d7b
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_ext.h
@@ -0,0 +1,44 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 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 000000000..5dc21227f
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/jni/com_android_bluetooth_mcp.cpp
@@ -0,0 +1,431 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 "BluetoothMCPService_jni"
+
+#define LOG_NDEBUG 0
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <map>
+#include <mutex>
+#include <shared_mutex>
+#include <vector>
+
+
+#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<std::shared_timed_mutex> lock(callbacks_mutex);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state";
+ return;
+ }
+ sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+ (jbyte*)&bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_OnConnectionStateChanged,
+ (jint)state, addr.get());
+ }
+
+
+ void MediaControlPointChangeReq(uint8_t state, const RawAddress& bd_addr) override {
+ LOG(INFO) << __func__;
+
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ LOG(ERROR) << "Failed to new jbyteArray bd addr for 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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";
+}
+
+//<TBD> uuid not fixed
+Uuid uuid = Uuid::FromString("00008fd1-0000-1000-8000-00805F9B34FB");
+
+static void initNative(JNIEnv* env, jobject object) {
+ std::unique_lock<std::shared_timed_mutex> interface_lock(interface_mutex);
+ std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
+ const bt_interface_t* btInf = getBluetoothInterface();
+ if (btInf == nullptr) {
+ LOG(ERROR) << "Bluetooth module is not loaded";
+ return;
+ }
+
+ if (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<std::shared_timed_mutex> interface_lock(interface_mutex);
+ std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
+ const bt_interface_t* btInf = getBluetoothInterface();
+ if (btInf == nullptr) {
+ LOG(ERROR) << "Bluetooth module is not loaded";
+ return;
+ }
+
+ if (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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<const uint8_t*>(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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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 000000000..c65a5d09d
--- /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 <string.h>
+#include <shared_mutex>
+
+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<std::shared_timed_mutex> 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<std::shared_timed_mutex> lock(callbacks_mutex);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state";
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+ (jbyte*)&bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged,
+ addr.get(), (jint)state);
+ }
+
+ void OnAudioContextAvailable(const RawAddress& bd_addr,
+ uint32_t available_contexts) override {
+ LOG(INFO) << __func__;
+
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ LOG(ERROR) << "Failed to new jbyteArray bd addr for 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<bluetooth::bap::pacs::CodecConfig> sink_pac_records,
+ std::vector<bluetooth::bap::pacs::CodecConfig> 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<std::shared_timed_mutex> 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<jbyteArray> 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, "<init>", "(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<std::shared_timed_mutex> interface_lock(interface_mutex);
+ std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
+ const bt_interface_t* btInf = getBluetoothInterface();
+ if (btInf == nullptr) {
+ LOG(ERROR) << "Bluetooth module is not loaded";
+ return;
+ }
+
+ if (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<std::shared_timed_mutex> interface_lock(interface_mutex);
+ std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
+ const bt_interface_t* btInf = getBluetoothInterface();
+ if (btInf == nullptr) {
+ LOG(ERROR) << "Bluetooth module is not loaded";
+ return;
+ }
+
+ if (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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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 000000000..552fd8ad6
--- /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 <string.h>
+#include <shared_mutex>
+
+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<std::shared_timed_mutex> lock(callbacks_mutex);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state";
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+ (jbyte*)&bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnectionStateChanged,
+ (jint)state, addr.get());
+ }
+
+
+ void OnVolumeStateChange(uint8_t volume, uint8_t mute,
+ const RawAddress& bd_addr) override {
+ LOG(INFO) << __func__;
+
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ LOG(ERROR) << "Failed to new jbyteArray bd addr for 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<std::shared_timed_mutex> lock(callbacks_mutex);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ LOG(ERROR) << "Failed to new jbyteArray bd addr for connection state";
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+ (jbyte*)&bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_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<std::shared_timed_mutex> interface_lock(interface_mutex);
+ std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
+ const bt_interface_t* btInf = getBluetoothInterface();
+ if (btInf == nullptr) {
+ LOG(ERROR) << "Bluetooth module is not loaded";
+ return;
+ }
+
+ if (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<std::shared_timed_mutex> interface_lock(interface_mutex);
+ std::unique_lock<std::shared_timed_mutex> callbacks_lock(callbacks_mutex);
+
+ const bt_interface_t* btInf = getBluetoothInterface();
+ if (btInf == nullptr) {
+ LOG(ERROR) << "Bluetooth module is not loaded";
+ return;
+ }
+
+ if (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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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<std::shared_timed_mutex> 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 000000000..6c79c663f
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmCodecConfig.java
@@ -0,0 +1,141 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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 000000000..a7f24dcc6
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmNativeInterface.java
@@ -0,0 +1,238 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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 000000000..ef264a11b
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmService.java
@@ -0,0 +1,1866 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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<BluetoothDevice, AcmStateMachine> mStateMachines =
+ new ConcurrentHashMap<>();
+ private HashMap<BluetoothDevice, BluetoothAcmDevice> mAcmDevices =
+ new HashMap<BluetoothDevice, BluetoothAcmDevice>();
+
+ // 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<BluetoothDevice> d = mCsipManager.getSetMembers(set_id);
+ if (d == null) {
+ Log.d(TAG, "No set member found");
+ return false;
+ }
+ Iterator<BluetoothDevice> 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<BluetoothDevice> 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<BluetoothDevice> 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<BluetoothDevice> d = mCsipManager.getSetMembers(set_id);
+ if (d == null) {
+ Log.d(TAG, "No set member found");
+ return false;
+ }
+ Iterator<BluetoothDevice> 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<BluetoothDevice> getConnectedDevices() {
+
+ synchronized (mStateMachines) {
+ List<BluetoothDevice> 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<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+
+ List<BluetoothDevice> 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<BluetoothDevice> getDevices() {
+ List<BluetoothDevice> 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<BluetoothDevice> members = mCsipManager.getSetMembers(setid);
+ if (members == null) {
+ Log.d(TAG, "No set member found");
+ return;
+ }
+ Iterator<BluetoothDevice> 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<BluetoothDevice> members = mCsipManager.getSetMembers(set_id);
+ if (members == null) {
+ Log.d(TAG, "No set member found");
+ }
+ Iterator<BluetoothDevice> 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<BluetoothDevice> members = mCsipManager.getSetMembers(set_id);
+ if (members == null) {
+ Log.d(TAG, "No set member found");
+ }
+ Iterator<BluetoothDevice> 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<BluetoothDevice> members = mCsipManager.getSetMembers(set_id);
+ if (members == null) {
+ Log.d(TAG, "No set member found");
+ return;
+ }
+ Iterator<BluetoothDevice> 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<BluetoothDevice> members = mCsipManager.getSetMembers(set_id);
+ if (members == null) {
+ Log.d(TAG, "No set member found");
+ return;
+ }
+ Iterator<BluetoothDevice> 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<BluetoothDevice> members = mCsipManager.getSetMembers(setid);
+ if (members == null) {
+ Log.d(TAG, "No set member found");
+ return isVcpPeerConnected;
+ }
+ Iterator<BluetoothDevice> 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<BluetoothDevice> members = mCsipManager.getSetMembers(setid);
+ if (members == null) {
+ Log.d(TAG, "No set member found");
+ return;
+ }
+ Iterator<BluetoothDevice> 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<BluetoothDevice> members = mCsipManager.getSetMembers(setid);
+ if (members == null) {
+ Log.d(TAG, "No set member found");
+ return;
+ }
+ Iterator<BluetoothDevice> 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<BluetoothDevice> 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<BluetoothDevice> 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<BluetoothDevice> 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 000000000..d2c39fbc5
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmStackEvent.java
@@ -0,0 +1,144 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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 000000000..293387cb5
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/acm/AcmStateMachine.java
@@ -0,0 +1,2354 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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<BluetoothDevice> members = new ArrayList<BluetoothDevice>();
+ 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<BluetoothDevice> i = mAcmService.getCsipManager().getSetMembers(mSetId).iterator();
+ List<BluetoothDevice> members = new ArrayList<BluetoothDevice>();
+ 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<BluetoothDevice> set = new ArrayList<BluetoothDevice>();
+ 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<BluetoothDevice> members = new ArrayList<BluetoothDevice>();
+ 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<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
+ 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<BluetoothDevice> members = new ArrayList<BluetoothDevice>();
+ Iterator<BluetoothDevice> 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<BluetoothDevice> members = new ArrayList<BluetoothDevice>();
+ Iterator<BluetoothDevice> 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<BluetoothDevice> members = new ArrayList<BluetoothDevice>();
+ 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<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
+ 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<BluetoothDevice> members = new ArrayList<BluetoothDevice>();
+ Iterator<BluetoothDevice> 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<BluetoothDevice> members = new ArrayList<BluetoothDevice>();
+ Iterator<BluetoothDevice> 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<BluetoothDevice> members = mAcmService.getCsipManager().getSetMembers(mSetId);
+ if (members == null) {
+ Log.d(TAG, "No set member found");
+ return d;
+ }
+ Iterator<BluetoothDevice> 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 000000000..dfcabf14f
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ActiveDeviceManagerService.java
@@ -0,0 +1,1444 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 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 000000000..45fab0dfa
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ApmConst.java
@@ -0,0 +1,70 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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 000000000..b4370237c
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/ApmNativeInterface.java
@@ -0,0 +1,131 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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 000000000..0d8469125
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/CallAudio.java
@@ -0,0 +1,758 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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<String, CallDevice> 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<String, CallDevice>();
+ 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<BluetoothDevice> getNonIdleAudioDevices() {
+ if(mCallDevicesMap.size() == 0) {
+ return new ArrayList<>(0);
+ }
+
+ ArrayList<BluetoothDevice> 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<BluetoothDevice> 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<BluetoothDevice> 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 000000000..40e5fd15c
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/CallControl.java
@@ -0,0 +1,164 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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 000000000..0901f9861
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/DeviceProfileMap.java
@@ -0,0 +1,814 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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<BluetoothDevice, Integer> mSupportedProfileMap = new HashMap();
+ Map<BluetoothDevice, int []> mActiveProfileMap = new HashMap();
+ Map<BluetoothDevice, Integer> 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<String, ?> allKeys = getSupportedProfileMap().getAll();
+ SharedPreferences.Editor mSupportedProfileMapEditor = getSupportedProfileMap().edit();
+
+ for (Map.Entry<String, ?> 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 000000000..057c0ebdf
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/MediaAudio.java
@@ -0,0 +1,1204 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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<String, MediaDevice> mMediaDevices;
+
+ final ArrayList <String> supported_codec = new ArrayList<String>( 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<String, MediaDevice>();
+ 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<BluetoothDevice> getConnectedDevices() {
+ Log.i(TAG, "getConnectedDevices: ");
+ if(mMediaDevices.size() == 0) {
+ return new ArrayList<>(0);
+ }
+
+ List<BluetoothDevice> connectedDevices = new ArrayList<>();
+ for(MediaDevice mMediaDevice : mMediaDevices.values()) {
+ if(mMediaDevice.deviceConnStatus == BluetoothProfile.STATE_CONNECTED) {
+ connectedDevices.add(mMediaDevice.mDevice);
+ }
+ }
+ return connectedDevices;
+ }
+
+ public List<BluetoothDevice> getDevicesMatchingConnectionStates(Integer[] states) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+
+ Log.i(TAG, "getDevicesMatchingConnectionStates: ");
+ List<BluetoothDevice> 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 "<ID>"*/
+
+ ArrayList <String> supported_codec_config = new ArrayList<String>( 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 <String, Integer> 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 "<ID>"*/
+ boolean enable = false;
+
+ ArrayList <String> supported_Qos_config = new ArrayList<String>( 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 000000000..1f197cbf2
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/MediaControlManager.java
@@ -0,0 +1,211 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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<AudioPlaybackConfiguration> 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<MediaSession.QueueItem> 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 000000000..fc4270377
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/StreamAudioService.java
@@ -0,0 +1,382 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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 000000000..a57195778
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/apm/VolumeManager.java
@@ -0,0 +1,779 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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<String, Integer> 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<String, Integer>();
+
+ 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<BleBroadcastSourceInfo> srcInfos =
+ mBCService.getAllBroadcastSourceInformation(device);
+ if (srcInfos == null || srcInfos.size() == 0) {
+ Log.e(TAG, "source Infos not available");
+ return false;
+ }
+
+ for (int i=0; i<srcInfos.size(); i++) {
+ if (srcInfos.get(i).getAudioSyncState() ==
+ BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED) {
+ Log.d(TAG, "Remote synced audio to broadcast source");
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void handleBroadcastAudioSynced(BluetoothDevice device) {
+ if (device == null) {
+ return;
+ }
+
+ AcmService mAcmService = AcmService.getAcmService();
+ BluetoothDevice mGroupDevice;
+ if(mAcmService != null) {
+ mGroupDevice = mAcmService.getGroup(device);
+ } else {
+ mGroupDevice = device;
+ }
+
+ int callAudioState = mCallAudio.getAudioState(device);
+ boolean isCall = (callAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTING ||
+ callAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTED);
+
+ if (mGroupDevice.equals(mMedia.mDevice) && mMediaAudio.isA2dpPlaying(device)) {
+ Log.d (TAG, "Active media device and streaming, not restore broadcast volume");
+ } else if (mGroupDevice.equals(mCall.mDevice) && isCall) {
+ Log.d (TAG, "Active call device and in call, not restore broadcast volume");
+ } else {
+ Log.d (TAG, "Restore broadcast volume while remote synced audio");
+ updateBroadcastVolume(device, getBassVolume(device));
+ }
+ }
+
+ void handleDeviceUnbond(BluetoothDevice device) {
+ if(device == mCall.mDevice) {
+ mCall.reset();
+ }
+ if(device == mMedia.mDevice) {
+ mMedia.reset();
+ }
+
+ mCall.removeDevice(device);
+ mMedia.removeDevice(device);
+ mBroadcast.removeDevice(device);
+ }
+
+ void handleDeviceShutdown() {
+ Log.i(TAG, "handleDeviceShutdown Save Volume start");
+ if(mCall.mDevice != null) {
+ mCall.saveVolume();
+ mCall.reset();
+ }
+ if(mMedia.mDevice != null) {
+ mMedia.saveVolume();
+ mMedia.reset();
+ }
+ mBroadcast.saveVolume();
+ Log.i(TAG, "handleDeviceShutdown Save Volume end");
+ }
+
+ class DeviceVolume {
+ BluetoothDevice mDevice;
+ int mVolume;
+ int mProfile;
+ boolean mSupportAbsoluteVolume;
+ Map<String, Integer> 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<String, Integer>();
+ }
+
+ Map<String, ?> allKeys = getVolumeMap().getAll();
+ SharedPreferences.Editor pref = getVolumeMap().edit();
+ for (Map.Entry<String, ?> 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<String, Integer> 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 000000000..ebcbf81b5
--- /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<BluetoothDevice, BassClientStateMachine> mStateMachines =
+ new HashMap<>();
+ private HandlerThread mStateMachinesThread;
+ private final Map<Integer, BassCsetManager> mSetManagers =
+ new HashMap<>();
+ private HandlerThread mSetManagerThread;
+
+ private AdapterService mAdapterService;
+
+ private Map<BluetoothDevice, ArrayList<IBleBroadcastAudioScanAssistCallback>> mAppCallbackMap =
+ new HashMap<BluetoothDevice, ArrayList<IBleBroadcastAudioScanAssistCallback>>();
+
+ 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<BluetoothDevice, BluetoothDevice> 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<BluetoothDevice, Integer> mSyncHandleMap;
+ /*syncHandle, parsed BaseData data*/
+ private Map<Integer, BaseData> mSyncHandleVsBaseInfo;
+ /*bcastSrcDevice, corresponding PAResultsMap*/
+ private Map<BluetoothDevice, PAResults> 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<BluetoothDevice> 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<BluetoothDevice, Integer>();
+ mPAResultsMap = new HashMap<BluetoothDevice, PAResults>();
+ mSyncHandleVsBaseInfo = new HashMap<Integer, BaseData>();
+ mActiveSourceMap = new HashMap<BluetoothDevice, BluetoothDevice>();
+
+ //*_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<BluetoothDevice, Integer> 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<BluetoothDevice> getConnectedDevices() {
+
+ synchronized (mStateMachines) {
+ List<BluetoothDevice> 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<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+
+ ArrayList<BluetoothDevice> 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.
+ *
+ * <p> 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<BleBroadcastSourceChannel> bChannels, int status){
+ ArrayList<IBleBroadcastAudioScanAssistCallback> 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<IBleBroadcastAudioScanAssistCallback> 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<IBleBroadcastAudioScanAssistCallback> 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<IBleBroadcastAudioScanAssistCallback> 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<IBleBroadcastAudioScanAssistCallback> 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<IBleBroadcastAudioScanAssistCallback> cbs = mAppCallbackMap.get(device);
+ if (cbs == null) {
+ Log.i(TAG, "registerAppCallback: entry exists");
+ cbs = new ArrayList<IBleBroadcastAudioScanAssistCallback>();
+ }
+ cbs.add(cb);
+ mAppCallbackMap.put(device, cbs);
+ return;
+ }
+
+ public void unregisterAppCallback (BluetoothDevice device, IBleBroadcastAudioScanAssistCallback cb) {
+
+ Log.i(TAG, "unregisterAppCallback" + device);
+
+ ArrayList<IBleBroadcastAudioScanAssistCallback> 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<IBleBroadcastAudioScanAssistCallback> 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<IBleBroadcastAudioScanAssistCallback> 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<BluetoothDevice> listOfDevices = new ArrayList<BluetoothDevice>();
+ 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<BluetoothDevice> 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<BluetoothDevice> 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<BluetoothDevice> 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<BleBroadcastSourceInfo> currentSourceInfos =
+ getAllBroadcastSourceInformation(dev);
+ if (currentSourceInfos == null) {
+ log("currentSourceInfos is null for " + dev);
+ continue;
+ }
+ for (int i=0; i<currentSourceInfos.size(); i++) {
+ if (srcInfo.matches(currentSourceInfos.get(i))) {
+ ret = false;
+ Log.e(TAG, "isValidBroadcastSourceAddition: fails for: " + dev + "&srcInfo" + srcInfo);
+ break;
+ }
+ }
+ }
+
+ log("isValidBroadcastSourceInfo returns: " + ret);
+ return ret;
+ }
+
+ private boolean isRoomForBroadcastSourceAddition(List<BluetoothDevice> 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<BleBroadcastSourceInfo> currentSourceInfos =
+ getAllBroadcastSourceInformation(dev);
+ for (int i=0; i<currentSourceInfos.size(); i++) {
+ BleBroadcastSourceInfo srcInfo = currentSourceInfos.get(i);
+ if (srcInfo.isEmptyEntry()) {
+ isRoomAvail = true;
+ continue;
+ }
+ }
+ if (isRoomAvail == false) {
+ Log.e(TAG, "isRoomForBroadcastSourceAddition: fails for: " + dev);
+ break;
+ }
+ }
+
+ log("isRoomForBroadcastSourceAddition returns: " + isRoomAvail);
+ return isRoomAvail;
+ }
+
+ public synchronized boolean addBroadcastSource (BluetoothDevice masterDevice, List<BluetoothDevice> 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<BleBroadcastSourceInfo> masterSrcInfos = getAllBroadcastSourceInformation(masterDevice);
+ List<BleBroadcastSourceInfo> 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<masterSrcInfos.size(); i++) {
+ if (masterSrcInfos.get(i).getSourceId() == masterSrcId) {
+ masterSrcDevice = masterSrcInfos.get(i).getSourceDevice();
+ break;
+ }
+ }
+ if (masterSrcDevice == null) {
+ Log.e(TAG, "No matching SRC Id for the operation in masterDevice");
+ return targetSrcId;
+ }
+
+ //look for this srcAddress in member to retrieve the srcId
+ for (int i=0; i<memberSrcInfos.size(); i++) {
+ if (masterSrcDevice.equals(memberSrcInfos.get(i).getSourceDevice())) {
+ targetSrcId = masterSrcInfos.get(i).getSourceId();
+ break;
+ }
+ }
+ if (targetSrcId == -1) {
+ Log.e(TAG, "No matching SRC Address in the member Src Infos");
+ }
+ return targetSrcId;
+ }
+
+ public synchronized boolean updateBroadcastSource (BluetoothDevice masterDevice, List<BluetoothDevice> 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<BluetoothDevice> 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<BluetoothDevice> 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<BleBroadcastSourceInfo> 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<BluetoothDevice> listOfDevices = new ArrayList<BluetoothDevice>();
+ 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<BluetoothDevice> listOfDevices = new ArrayList<BluetoothDevice>();
+ 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<BluetoothDevice> listOfDevices = new ArrayList<BluetoothDevice>();
+ 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<BluetoothDevice> listOfDevices = new ArrayList<BluetoothDevice>();
+ 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<BluetoothDevice> listOfDevices = new ArrayList<BluetoothDevice>();
+ 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<BluetoothDevice> listOfDevices = new ArrayList<BluetoothDevice>();
+ 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<BluetoothDevice> getConnectedDevices() {
+ BCService service = getService();
+ if (service == null) {
+ return new ArrayList<>();
+ }
+ return service.getConnectedDevices();
+ }
+
+ @Override
+ public List<BluetoothDevice> 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<BleBroadcastSourceInfo> 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 000000000..b004afa66
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BaseData.java
@@ -0,0 +1,861 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 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<BaseInformation> levelTwo = new ArrayList<BaseInformation>();
+ ArrayList<BaseInformation> levelThree = new ArrayList<BaseInformation>();
+ 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<String> keyCodecCfgDiff;
+ public LinkedHashSet<String> keyMetadataDiff;
+ public String diffText;
+ public String description;
+
+ public byte[] consolidatedCodecId;
+ public Set<String> consolidatedMetadata;
+ public Set<String> consolidatedCodecInfo;
+ public HashMap<Integer, String> consolidatedUniqueCodecInfo;
+ public HashMap<Integer, String> 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<String>();
+ keyMetadataDiff = new LinkedHashSet<String>();
+
+ consolidatedMetadata = new LinkedHashSet<String>();
+ consolidatedCodecInfo = new LinkedHashSet<String>();
+ consolidatedCodecId = new byte[5];
+ consolidatedUniqueMetadata = new HashMap<Integer, String>();
+ consolidatedUniqueCodecInfo = new HashMap<Integer, String>();
+ 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<String> itr = consolidatedCodecInfo.iterator();
+ for (int k=0; itr.hasNext(); k++) {
+ log("consolidatedCodecInfo:[" + k + "]:" + Arrays.toString(itr.next().getBytes()));
+ }
+ }
+
+ if (consolidatedMetadata != null) {
+ Iterator<String> itr = consolidatedMetadata.iterator();
+ for (int k=0; itr.hasNext(); k++) {
+ log("consolidatedMetadata:[" + k + "]:" + Arrays.toString(itr.next().getBytes()));
+ }
+ }*/
+
+ if (consolidatedUniqueCodecInfo != null) {
+ for (Map.Entry<Integer,String> entry : consolidatedUniqueCodecInfo.entrySet()) {
+ log("consolidatedUniqueCodecInfo:[" + entry.getKey() + "]:" + Arrays.toString(entry.getValue().getBytes()));
+ }
+ }
+
+ if (consolidatedUniqueMetadata != null) {
+ for (Map.Entry<Integer,String> 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<String> 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<String> 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<String> 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<BisInfo> colocatedBisInfo, Map<Integer, MetadataLtv> 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<BaseInformation>();
+ levelThree = new ArrayList<BaseInformation>();
+
+ levelOne.level = 1;
+ levelOne.numSubGroups = (byte)numSubGroups;
+
+ //create the level Two and update the Metadata Info
+ for (int i=0; i<numSubGroups; i++) {
+ BaseInformation a = new BaseInformation();
+ a.level = 2;
+ //get Metadata Ltv
+ byte[] metadataLtv = null;
+ if (metaInfo != null) {
+ Log.d(TAG, "metaInfo: " + metaInfo);
+ MetadataLtv obj = metaInfo.get(i);
+ if (obj != null) {
+ metadataLtv = obj.getByteArray();
+ Log.d(TAG, "metadataLtv: " + metadataLtv);
+ } else {
+ Log.d(TAG, "metadataLtv[" +i+"] is not available");
+ }
+ }
+ if (metadataLtv != null) {
+ a.metaData = new byte[(int)metadataLtv.length];
+ System.arraycopy(metadataLtv, 0, a.metaData, 0, (int)metadataLtv.length);
+ }
+ levelTwo.add(a);
+ }
+
+ if (colocatedBisInfo != null) {
+ mNumBISIndicies = colocatedBisInfo.size();
+ for (int i = 0; i < colocatedBisInfo.size(); i++) {
+ BisInfo bisInfo = colocatedBisInfo.get(i);
+ BaseInformation b = new BaseInformation();
+ b.level = 3;
+ b.subGroupId = bisInfo.mSubGroupId;
+ b.index = (byte)bisInfo.BisIndex;
+
+ b.consolidatedCodecId = bisInfo.mCodecId;
+
+ //get Metadata Ltv
+ byte[] metadataLtv = bisInfo.BisMetadata.getByteArray();
+ if (metadataLtv != null) {
+ int k = 0;
+ while (k<metadataLtv.length) {
+ byte length = metadataLtv[k++];
+ byte[] ltv = new byte[length+1];
+ ltv[0] = length;
+ System.arraycopy(metadataLtv, k, ltv, 1, length);
+ //put in type, ltv hashmap
+ String s = new String(ltv);
+ b.consolidatedUniqueMetadata.put((int)ltv[1], s);
+ log("add Metadata:::");
+ k = k+length;
+ }
+ }
+
+ //get CodecConfig ltv
+ byte[] codecConfigLtv = bisInfo.BisCodecConfig.getByteArray();
+ if (codecConfigLtv != null) {
+ int k = 0;
+ while (k<codecConfigLtv.length) {
+ byte length = codecConfigLtv[k++];
+ byte[] ltv = new byte[length+1];
+ ltv[0] = length;
+ System.arraycopy(codecConfigLtv, k, ltv, 1, length);
+ //put in type, ltv hashmap
+ String s = new String(ltv);
+ b.consolidatedUniqueCodecInfo.put((int)ltv[1], s);
+ log("add CodecConfig entry:::");
+ k = k+length;
+ }
+ }
+ //update description with "Chennel: X"
+ b.description = "Channel: " + String.valueOf(b.index);
+ levelThree.add(b);
+ }
+ }
+ }
+ //_BMS*/
+ BaseData(byte[] serviceData) {
+ if (serviceData == null) {
+ Log.e(TAG, "Invalid service data for BaseData construction");
+ throw new IllegalArgumentException("Basedata: serviceData is null");
+ }
+ levelOne = new BaseInformation();
+ levelTwo = new ArrayList<BaseInformation>();
+ levelThree = new ArrayList<BaseInformation>();
+ 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<mNumBISIndicies; k++) {
+ BaseInformation c = new BaseInformation();
+ c.level = 3;
+ c.index = serviceData[level3Index++];
+
+ c.codecConfigLength = serviceData[level3Index++];
+ if (c.codecConfigLength != 0) {
+ c.codecConfigInfo = new byte[(int)c.codecConfigLength];
+ System.arraycopy(serviceData, level3Index, c.codecConfigInfo, 0, (int)c.codecConfigLength);
+ level3Index = level3Index + (int)c.codecConfigLength;
+ }
+ levelThree.add(c);
+ }
+
+ consolidateBaseofLevelTwo();
+
+ //Detailed BASE parsing below
+ //log("calling updateUniquenessForLevelTwo");
+ //updateUniquenessForLevelTwo(levelOne.numSubGroups);
+ //updateDiffTextforNodes();
+ }
+
+ void consolidateBaseofLevelTwo() {
+ int startIdx = 0;
+ int children = 0;
+
+ for (int i=0; i<levelTwo.size(); i++) {
+ startIdx = startIdx+ children;
+ children = children + levelTwo.get(i).numSubGroups;
+
+ consolidateBaseofLevelThree(i, startIdx, levelTwo.get(i).numSubGroups);
+ }
+
+ //Eliminate Duplicates at Level 3
+ for (int i=0; i<levelThree.size(); i++) {
+ Map<Integer, String> uniqueMds = new HashMap<Integer, String> ();
+ Map<Integer, String> uniqueCcis = new HashMap<Integer, String> ();
+
+ Set<String> Csfs = levelThree.get(i).consolidatedCodecInfo;
+
+ if (Csfs.size() > 0) {
+ Iterator<String> 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<String> Mds = levelThree.get(i).consolidatedMetadata;
+ if (Mds.size() > 0) {
+ Iterator<String> 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<Integer, String>(uniqueMds);
+ levelThree.get(i).consolidatedUniqueCodecInfo = new HashMap<Integer, String>(uniqueCcis);
+
+ }
+ }
+
+ void consolidateBaseofLevelThree(int parentSubgroup, int startIdx, int numNodes) {
+
+ for (int i=startIdx; i<startIdx+numNodes||i<levelThree.size(); i++) {
+
+ levelThree.get(i).subGroupId = levelTwo.get(parentSubgroup).subGroupId;
+
+ log("Copy Codec Id from Level2 Parent" + parentSubgroup);
+ System.arraycopy(levelTwo.get(parentSubgroup).consolidatedCodecId,
+ 0 ,levelThree.get(i).consolidatedCodecId, 0, 5);
+
+ //Metadata clone from Parent
+ levelThree.get(i).consolidatedMetadata = new LinkedHashSet<String>(levelTwo.get(parentSubgroup).consolidatedMetadata);
+
+ //log("Parent Cons Info>>");
+ //levelTwo.get(parentSubgroup).printConsolidated();
+ //CCI clone from Parent
+ levelThree.get(i).consolidatedCodecInfo = new LinkedHashSet<String>(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<BaseInformation> getBISIndexInfos() {
+ return levelThree;
+ }
+ List<BleBroadcastSourceChannel> getBroadcastChannels() {
+ List<BleBroadcastSourceChannel> bChannels = new ArrayList<BleBroadcastSourceChannel>();
+ for (int k=0; k<mNumBISIndicies; k++) {
+ int index = levelThree.get(k).index;
+ String desc = levelThree.get(k).description;
+ //String desc = String.valueOf(index);
+ BleBroadcastSourceChannel bc = new BleBroadcastSourceChannel(index, desc, false,
+ levelThree.get(k).subGroupId, levelThree.get(k).metaData);
+ bChannels.add(bc);
+ }
+ return bChannels;
+ }
+
+ List<BleBroadcastSourceChannel> pickAllBroadcastChannels() {
+ List<BleBroadcastSourceChannel> bChannels = new ArrayList<BleBroadcastSourceChannel>();
+ for (int k=0; k<mNumBISIndicies; k++) {
+ int index = levelThree.get(k).index;
+ //String desc = levelThree.get(k).description;
+ //String desc = String.valueOf(index);
+ BleBroadcastSourceChannel bc = new BleBroadcastSourceChannel(index, String.valueOf(index), true,
+ levelThree.get(k).subGroupId, levelThree.get(k).metaData);
+ bChannels.add(bc);
+ }
+ return bChannels;
+ }
+ byte[] getMetadata(int subGroup) {
+ if (levelTwo != null) {
+ return levelTwo.get(subGroup).metaData;
+ }
+ return null;
+ }
+
+ String getMetadataString(byte[] metadataBytes) {
+ final int _LANGUAGE = 0;
+ //Different language
+ final int _ENGLISH = 1;
+ final int _SPANISH = 2;
+ final int _DESCRIPTION = 1;
+ String ret = new String();
+
+ switch(metadataBytes[1]) {
+ case _LANGUAGE:
+ switch (metadataBytes[2]) {
+ case _ENGLISH:
+ ret = "ENGLISH"; break;
+ case _SPANISH:
+ ret = "SPANISH"; break;
+ default:
+ ret = "UNKNOWN"; break;
+ }
+ break;
+ case _DESCRIPTION:
+ ret = "UNKNOWN";
+ break;
+ default:
+ ret = "UNKNOWN";
+ }
+ log("getMetadataString: " + ret);
+ return ret;
+ }
+
+ String getCodecParamString(byte[] csiBytes) {
+ final int LOCATION = 4;
+ final int LEFT = 0x01000000;
+ final int RIGHT =0x02000000;
+ String ret = new String();
+
+ //sample rate
+ final int SAMPLE_RATE = 1;
+
+ //frame duration
+ final int FRAME_DURATION = 2;
+
+ //Octets per frame
+ final int OCTETS_PER_FRAME = 8;
+ switch(csiBytes[1]) {
+ case LOCATION:
+ byte[] location = new byte[4];
+ System.arraycopy(csiBytes, 2, location, 0, 4);
+ ByteBuffer wrapped = ByteBuffer.wrap(location);
+ int audioLocation = wrapped.getInt();
+ log("audioLocation: " + audioLocation);
+
+ switch (audioLocation) {
+ case LEFT: ret = "LEFT"; break;
+ case RIGHT: ret = "RIGHT"; break;
+ case LEFT|RIGHT: ret = "LR"; break;
+ }
+ break;
+ case SAMPLE_RATE:
+ switch(csiBytes[2]) {
+ case 1:
+ ret = "8K"; break;
+ case 2:
+ ret = "16K"; break;
+ case 3:
+ ret = "24K"; break;
+ case 4:
+ ret = "32K"; break;
+ case 5:
+ ret = "44.1K"; break;
+ case 6:
+ ret = "48K"; break;
+ }
+ break;
+ case FRAME_DURATION:
+ switch(csiBytes[2]) {
+ case 1:
+ ret = "FD_1"; break;
+ }
+ break;
+ case OCTETS_PER_FRAME:
+ switch(csiBytes[2]) {
+ case 28:
+ ret = "OPF_28"; break;
+ case 64:
+ ret = "OPF_64"; break;
+ }
+ break;
+ default:
+ ret = "UNKNOWN";
+ }
+ log("getCodecParamString: " + ret);
+ return ret;
+ }
+
+ void updateDiffTextforNodes() {
+ for (int i=0; i<levelTwo.size(); i++) {
+ if (levelTwo.get(i).keyMetadataDiff != null) {
+ Iterator<String> 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<String> 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<levelThree.size(); i++) {
+ if (levelThree.get(i).keyCodecCfgDiff != null) {
+ Iterator<String> 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<levelTwo.size(); i++) {
+ startIdx = startIdx+ children;
+ children = children + levelTwo.get(i).numSubGroups;
+ for (int j=startIdx; j<startIdx+levelTwo.get(i).numSubGroups||j<levelThree.size(); j++) {
+ levelThree.get(j).description = levelTwo.get(i).diffText + levelThree.get(j).diffText;
+ }
+ }
+ }
+
+ void updateUniquenessForLevelTwo(int numNodes) {
+ log("updateUniquenessForLevelTwo: ENTER");
+ Set<String> uniqueCodecIds = new LinkedHashSet<String>();
+ Set<String> uniqueCsfs = new LinkedHashSet<String>();
+ Set<String> uniqueMetadatas = new LinkedHashSet<String>();
+
+ log("updateUniquenessForLevelTwo");
+
+ int startIdx = 0;
+ int children = 0;
+ for (int i=0; i<levelTwo.size(); i++) {
+ //levelTwo.get(i).print();
+ if (!levelTwo.get(i).isCodecIdUnknown()) {
+ log("add codecId of subg: " + i);
+ String s = new String(levelTwo.get(i).codecId);
+ uniqueCodecIds.add(s);
+ }
+
+ if (levelTwo.get(i).codecConfigLength != 0) {
+ log("add codecConfig of subg: " + i);
+ String s = new String(levelTwo.get(i).codecConfigInfo);
+ uniqueCsfs.add(s);
+ }
+
+ if (levelTwo.get(i).metaDataLength != 0) {
+ String s = new String(levelTwo.get(i).metaData);
+ log("add metadata of subg: " + i);
+ uniqueMetadatas.add(s);
+ }
+ startIdx = startIdx+ children;
+ children = children + levelTwo.get(i).numSubGroups;
+
+ updateUniquenessForLevelThree(i, startIdx, levelTwo.get(i).numSubGroups);
+ }
+
+ Set<String> uniqueCodecParams = new LinkedHashSet<String>();
+ Set<String> uniqueMetadataParams = new LinkedHashSet<String>();
+
+ if (uniqueCodecIds.size() > 0) log("LevelTwo: UniqueCodecIds");
+
+ if (uniqueCsfs.size() > 0) {
+ log("LevelTwo: uniqueCsfs");
+ //uniqueCodecParams =
+ Iterator<String> 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<String>();
+ Iterator<String> 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<String> 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<String> 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<String> itr = uniqueCodecParams.iterator();
+ for (int k=0; itr.hasNext(); k++) {
+ log("UniqueCodecParams:[" + k + "]" + Arrays.toString(itr.next().getBytes()));
+ }
+ }
+ if (uniqueMetadataParams != null) {
+ Iterator<String> 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<String> uniqueCodecIds = new LinkedHashSet<String>();
+ Set<String> uniqueCsfs = new LinkedHashSet<String>();
+ //Set<String> uniqueMetadatas = new LinkedHashSet<String>();
+
+ log("updateUniquenessForLevelThree: startIdx" + startIdx + "numNodes" + numNodes);
+ for (int i=startIdx; i<startIdx+numNodes||i<levelThree.size(); i++) {
+ if (levelThree.get(i).codecConfigLength != 0) {
+ String s = new String(levelThree.get(i).codecConfigInfo);
+ uniqueCsfs.add(s);
+ log("LEVEL3: add unique CSFs:");
+ }
+ }
+
+ Set<String> uniqueCodecParams = new LinkedHashSet<String>();
+ if (uniqueCsfs.size() > 0) {
+ log("LevelThree: uniqueCsfs");
+ //uniqueCodecParams =
+ Iterator<String> 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<String> 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<String> 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<levelTwo.size(); i++) {
+ levelTwo.get(i).print();
+ }
+ log("----- Level THREE BASE ----");
+ for (int i=0; i<levelThree.size(); i++) {
+ levelThree.get(i).print();
+ }
+ }
+
+ void printConsolidated() {
+ log("----- printConsolidated ----");
+ for (int i=0; i<levelThree.size(); i++) {
+ levelThree.get(i).printConsolidated();
+ }
+ }
+
+ static void log(String msg) {
+ if (BassClientStateMachine.BASS_DBG) {
+ Log.d(TAG, msg);
+ }
+ }
+}
diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassClientStateMachine.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassClientStateMachine.java
new file mode 100644
index 000000000..b35cfa9eb
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassClientStateMachine.java
@@ -0,0 +1,2388 @@
+/*
+ * 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 Bassclient 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.
+ * - "ConnectedProcessing" is an intermediate state to ensure, there is only
+ * one Gatt transaction from the profile at any point of time
+ *
+ *
+ * (Disconnected)
+ * | ^
+ * CONNECT | | DISCONNECTED
+ * V |
+ * (Connecting)<--->(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<BluetoothGattCharacteristic> mBroadcastReceiverStates;
+ private BluetoothGattCharacteristic mBroadcastScanControlPoint;
+ /*key is combination of sourceId, Address and advSid for this hashmap*/
+ private final Map<Integer, BleBroadcastSourceInfo> 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<BluetoothGattCharacteristic>();
+ mBleBroadcastSourceInfos = new HashMap<Integer, BleBroadcastSourceInfo>();
+
+ //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<BleBroadcastSourceInfo> currentSourceInfos =
+ getAllBroadcastSourceInformation();
+ BleBroadcastSourceInfo srcInfo = null;
+ for (int i=0; i<currentSourceInfos.size(); i++) {
+ BluetoothDevice device = currentSourceInfos.get(i).getSourceDevice();
+ if (device != null && device.equals(srcDevice)) {
+ srcInfo = currentSourceInfos.get(i);
+ Log.e(TAG, "getBroadcastSourceInfoForSourceDevice: returns for: " + srcDevice + "&srcInfo" + srcInfo);
+ return srcInfo;
+ }
+ }
+ return null;
+ }
+
+ BleBroadcastSourceInfo getBroadcastSourceInfoForSourceId (int srcId) {
+ List<BleBroadcastSourceInfo> currentSourceInfos =
+ getAllBroadcastSourceInformation();
+ BleBroadcastSourceInfo srcInfo = null;
+ for (int i=0; i<currentSourceInfos.size(); i++) {
+ int sId = currentSourceInfos.get(i).getSourceId();
+ if (srcId == sId) {
+ srcInfo = currentSourceInfos.get(i);
+ Log.e(TAG, "getBroadcastSourceInfoForSourceId: returns for: " + srcId + "&srcInfo" + srcInfo);
+ return srcInfo;
+ }
+ }
+ return null;
+ }
+
+ void parseBaseData(BluetoothDevice device, int syncHandle, byte[] serviceData) {
+ log("parseBaseData" + Arrays.toString(serviceData));
+ BaseData base_ = new BaseData(serviceData);
+ if (base_ != null) {
+ mService.updateBASE(syncHandle, base_);
+ base_.print();
+ base_.printConsolidated();
+ if (mAutoTriggerred == false) {
+ mService.sendBroadcastSourceSelectedCallback(device, base_.getBroadcastChannels(),
+ BleBroadcastAudioScanAssistCallback.BASS_STATUS_SUCCESS);
+ } else {
+ //successfull auto periodic synchrnization with source
+ log("auto triggered assist");
+ mAutoTriggerred = false;
+ //perform PAST with this device
+ BluetoothDevice srcDevice = mService.getDeviceForSyncHandle(syncHandle);
+ if (srcDevice != null) {
+ BleBroadcastSourceInfo srcInfo = getBroadcastSourceInfoForSourceDevice(srcDevice);
+ processPASyncState(srcInfo);
+ } else {
+ Log.w(TAG, "Autoassist: no matching device");
+ }
+ }
+ } else {
+ //
+ Log.e(TAG, "Seems BASE is not in parsable format");
+ if (mAutoTriggerred == false) {
+ mService.sendBroadcastSourceSelectedCallback(device, base_.getBroadcastChannels(),
+ BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_SELECTED);
+ BluetoothDevice srcDevice = mService.getDeviceForSyncHandle(syncHandle);
+ cancelActiveSync(srcDevice);
+ } else {
+ mAutoTriggerred = false;
+ }
+ }
+ }
+
+ void parseScanRecord(int syncHandle, ScanRecord record) {
+ log("parseScanRecord" + record);
+ BluetoothDevice srcDevice = mService.getDeviceForSyncHandle(syncHandle);
+ Map<ParcelUuid, byte[]> bmsAdvDataMap = record.getServiceData();
+ if (bmsAdvDataMap != null) {
+ for (Map.Entry<ParcelUuid,byte[]> 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<BleBroadcastSourceInfo> currentSourceInfos =
+ getAllBroadcastSourceInformation();
+ Log.i(TAG, "input srcInfo: " + srcInfo);
+ for (int i=0; i<currentSourceInfos.size(); i++) {
+ Log.i(TAG, "srcInfo: [" + i + "]" + currentSourceInfos.get(i));
+ if (srcInfo.matches(currentSourceInfos.get(i))) {
+ ret = false;
+ break;
+ }
+ }
+
+ log("isValidBroadcastSourceInfo returns: " + ret);
+ return ret;
+ }
+
+ public boolean selectBroadcastSource (ScanResult scanRes, boolean groupOp, boolean autoTriggerred) {
+ Log.i(TAG, "selectBroadcastSource for " + "isGroupOp:" + groupOp);
+ Log.i(TAG, "ScanResult " + scanRes);
+ int broadcastId = BCService.INVALID_BROADCAST_ID;
+ if (isLocalBroadcastSource(scanRes.getDevice()) != true) {
+ mAutoTriggerred = autoTriggerred;
+ mFirstTimeBisDiscovery = true;
+ mPASyncRetryCounter = 1;
+ //Cache Scan res for Retrys
+ mScanRes = scanRes;
+ /*This is an override case
+ if Previous sync is still active, cancel It
+ But dont stop the Scan offload as we still trying to assist remote*/
+ mNoStopScanOffload = true;
+ cancelActiveSync(null);
+ mService.getBassUtils().leScanControl(true);
+ try {
+ mPeriodicAdvManager.registerSync(scanRes, 0,
+ PSYNC_TIMEOUT, mPeriodicAdvCallback);
+ mSyncingOnBehalfOfGroup = groupOp;
+ } catch (IllegalArgumentException ex) {
+ Log.w(TAG, "registerSync:IllegalArguementException");
+ mService.sendBroadcastSourceSelectedCallback(mDevice, null,
+ BleBroadcastAudioScanAssistCallback.BASS_STATUS_INVALID_SOURCE_SELECTED);
+ mService.stopScanOffloadInternal(mDevice, false);
+ return false;
+ }
+ //updating mainly for Address type and PA Interval here
+ //extract BroadcastId from ScanResult
+ ScanRecord scanRecord = scanRes.getScanRecord();
+ if (scanRecord != null) {
+ Map<ParcelUuid, byte[]> 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<BleBroadcastSourceChannel> getListOfBisIndicies(int bisIndicies, int subGroupId, byte[] metaData) {
+ List<BleBroadcastSourceChannel> bcastIndicies = new ArrayList<BleBroadcastSourceChannel>();
+ 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 <subGroupId, List_OF_Broadcast_channel>
+ Map<Integer, List<BleBroadcastSourceChannel>> bisIndexList = new HashMap<Integer, List<BleBroadcastSourceChannel>>();
+ //Map for Metada <subGroupId, Metadata>
+ Map<Integer, byte[]> metadataList = new HashMap<Integer, byte[]>();
+ 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<BleBroadcastSourceInfo> 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<BluetoothGattCharacteristic> 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<a.length/2; i++){
+ byte tmp = a[i];
+ a[i] = a[a.length -i -1];
+ a[a.length -i -1] = tmp;
+ }
+
+ return a;
+ }
+
+ private byte[] BluetoothAddressToBytes (String s) {
+ log("BluetoothAddressToBytes: input string:" + s);
+ String[] splits = s.split(":");
+
+ byte[] addressBytes = new byte[6];
+ for (int i=0; i<6; i++) {
+ int hexValue = Integer.parseInt(splits[i], 16);
+ log("hexValue:" + hexValue);
+ addressBytes[i] = (byte)hexValue;
+ }
+
+ return addressBytes;
+
+ }
+
+ private int convertBisIndiciesToIntegerValue(List<BleBroadcastSourceChannel> bisIndicies, int subGroupId) {
+ int audioBisIndex = 0;
+ if (bisIndicies != null) {
+ for (int i=0; i<bisIndicies.size(); i++) {
+ if (bisIndicies.get(i).getStatus() == true && bisIndicies.get(i).getSubGroupId() == subGroupId) {
+ audioBisIndex = audioBisIndex | 1<<(bisIndicies.get(i).getIndex()-1);
+ log( "set the bit" + bisIndicies.get(i).getIndex() + "after:" + audioBisIndex);
+ }
+ }
+ } else {
+ log("bisIndicies Channels are null");
+ audioBisIndex = 0xFFFFFFFF;
+
+ }
+ log( "audioBisIndex" + audioBisIndex);
+
+ return audioBisIndex;
+ }
+
+ private byte[] convertSourceInfoToAddSourceByteArray(BleBroadcastSourceInfo srcInfo) {
+ byte[] res;
+ /*fixed length for add source op*/
+ int addSourceFixedLength = 16;
+ byte[] metaDataLength = null;
+ BluetoothDevice broadcastSource = null;
+ String localBcastAddr = null;
+ BCService.PAResults paRes = null;
+ log("Get PAresults for :" + srcInfo.getSourceDevice());
+ broadcastSource = srcInfo.getSourceDevice();
+
+ ///*_BMS
+ if (mPublicAddrForcSrc == false) {
+ if (isLocalBroadcastSource(broadcastSource)){
+ //update broadcastSource if it is colocated
+ if (mBAService != null) {
+ localBcastAddr = mBAService.BroadcastGetAdvAddress();
+ }
+ if (localBcastAddr == null) {
+ Log.w(TAG, "convertSourceInfoToAddSourceByteArray: localBCast not avaiable");
+ sendPendingCallbacks(ADD_BCAST_SOURCE,INVALID_SRC_ID,
+ BleBroadcastAudioScanAssistCallback.BASS_STATUS_SOURCE_UNAVAILABLE);
+ return null;
+ } else {
+ broadcastSource =
+ BluetoothAdapter.getDefaultAdapter().getRemoteDevice(localBcastAddr);
+ log("convertSourceInfoToAddSourceByteArray: colocated case: " + broadcastSource);
+ }
+ }
+ }
+ //_BMS*/
+ paRes = mService.getPAResults(broadcastSource);
+ if (paRes == null) {
+ Log.e(TAG, "No mathcing psync, scan res for this addition");
+ sendPendingCallbacks(ADD_BCAST_SOURCE,INVALID_SRC_ID,
+ BleBroadcastAudioScanAssistCallback.BASS_STATUS_SOURCE_UNAVAILABLE);
+ return null;
+ }
+
+ //populate metadata from BASE levelOne
+ BaseData base_ = mService.getBASE(paRes.mSyncHandle);
+ if (base_ == null) {
+ Log.e(TAG, "No valid base data populated for this device");
+ sendPendingCallbacks(ADD_BCAST_SOURCE,INVALID_SRC_ID,
+ BleBroadcastAudioScanAssistCallback.BASS_STATUS_SOURCE_UNAVAILABLE);
+ return null;
+ }
+ int numSubGroups = base_.getNumberOfSubgroupsofBIG();
+ metaDataLength = new byte[numSubGroups];
+ int totalMetadataLength = 0;
+ for (int i=0; i<numSubGroups; i++) {
+ if (base_.getMetadata(i) == null) {
+ Log.w(TAG, "no valid metadata from BASE");
+ metaDataLength[i] = 0;
+ } else {
+ metaDataLength[i] = (byte)base_.getMetadata(i).length;
+ log("metaDataLength updated:" + metaDataLength[i]);
+ }
+ totalMetadataLength = totalMetadataLength + metaDataLength[i];
+ }
+ res = new byte [addSourceFixedLength + numSubGroups*5 + totalMetadataLength];
+ srcInfo.setSourceDevice(broadcastSource);
+ srcInfo.setAdvAddressType((byte)paRes.mAddressType);
+ srcInfo.setAdvertisingSid((byte)paRes.mAdvSid);
+ srcInfo.setBroadcasterId(paRes.mBroadcastId);
+
+ if (isValidBroadcastSourceInfo(srcInfo) == false) {
+ log("Discarding this Add Broadcast source If It is DUP");
+ sendPendingCallbacks(ADD_BCAST_SOURCE,INVALID_SRC_ID,
+ BleBroadcastAudioScanAssistCallback.BASS_STATUS_DUPLICATE_ADDITION);
+ return null;
+ }
+
+ res[0] = BASS_ADD_SOURCE_OPCODE;
+ res[1] = (byte)paRes.mAddressType;
+ String address = broadcastSource.getAddress();
+ byte[] addrByteVal = BluetoothAddressToBytes(address);
+ log("Address bytes: " + Arrays.toString(addrByteVal));
+ byte[] revAddress= reverseBytes(addrByteVal);
+ log("reverse Address bytes: " + Arrays.toString(revAddress));
+ System.arraycopy(revAddress, 0, res, 2, 6);
+ res[8] = (byte)paRes.mAdvSid;
+
+ //System.arraycopy(paRes.mBroadcastId, 0, res, 9, BROADCAST_SOURCE_ID_LENGTH);
+ log("mBroadcastId: " + paRes.mBroadcastId);
+ res[9] = (byte)(paRes.mBroadcastId & 0x00000000000000FF);
+ res[10] = (byte)((paRes.mBroadcastId & 0x000000000000FF00) >>> 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<base_.getNumberOfSubgroupsofBIG(); i++) {
+
+ //Select based on PACs?
+ //int bisIndexValue = convertBisIndiciesToIntegerValue(srcInfo.getBroadcastChannelsSyncStatus());
+ int bisIndexValue = convertBisIndiciesToIntegerValue(mService.getBassUtils().selectBises(mDevice, srcInfo, base_), i);
+
+ res[offset++] = (byte)(bisIndexValue & 0x00000000000000FF);
+ res[offset++] = (byte)((bisIndexValue & 0x000000000000FF00)>>>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<Integer, Integer> 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<numSubGroups; i++) {
+
+ //Select based on PACs?
+ //int bisIndexValue = convertBisIndiciesToIntegerValue(srcInfo.getBroadcastChannelsSyncStatus());
+ if (bisIndexValue != 0xFFFFFFFF && bisIndexValue != 0) {
+ bisIndexValue = bisIndexList.get(i);
+ }
+ log("UPDATE_BCAST_SOURCE: bisIndexValue : " + bisIndexValue);
+
+ res[offset++] = (byte)(bisIndexValue & 0x00000000000000FF);
+ res[offset++] = (byte)((bisIndexValue & 0x000000000000FF00)>>>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<BleBroadcastSourceInfo> srcInfos = mBleBroadcastSourceInfos.values();
+ Iterator<BleBroadcastSourceInfo> 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 000000000..fd2be7c66
--- /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<BluetoothDevice> 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<BluetoothDevice>();
+
+ }
+
+ 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<BluetoothDevice> devices = (List<BluetoothDevice>)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<BluetoothDevice> devices = (List<BluetoothDevice>)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<BluetoothDevice> devices = (List<BluetoothDevice>)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 000000000..9289198a4
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/bassclient/BassUtils.java
@@ -0,0 +1,506 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 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<ArrayList<IBleBroadcastAudioScanAssistCallback>, ScanCallback> mLeAudioSourceScanCallbacks;
+ private final Map<BluetoothDevice, ScanCallback> 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<ArrayList<IBleBroadcastAudioScanAssistCallback>, ScanCallback>();
+ mBassAutoAssist = new HashMap<BluetoothDevice, ScanCallback>();
+ ///*_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<IBleBroadcastAudioScanAssistCallback> 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<IBleBroadcastAudioScanAssistCallback> 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<ParcelUuid, byte[]> 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<ScanFilter> filters = new ArrayList<ScanFilter>();
+ 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<IBleBroadcastAudioScanAssistCallback> 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<supportedConfigs.length; i++) {
+ if (actualSampleRate == supportedConfigs[i].getSampleRate()) {
+ ret = true;
+ }
+ }
+
+ log("isSampleRateSupported returns:" + ret);
+ _PACS*/
+ return ret;
+ }
+ public List<BleBroadcastSourceChannel> 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<BleBroadcastSourceChannel> bChannels = new ArrayList<BleBroadcastSourceChannel>();
+ //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<BaseData.BaseInformation> broadcastedCodecInfo = base.getBISIndexInfos();
+ if (broadcastedCodecInfo != null) {
+ for (int i=0; i<broadcastedCodecInfo.size(); i++) {
+ HashMap<Integer, String> 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<array.length; i++) {
+ log( "array[" + i + "] :" + Byte.toUnsignedInt(array[i]));
+ }
+ }
+}
diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/broadcast/BroadcastNativeInterface.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/broadcast/BroadcastNativeInterface.java
new file mode 100644
index 000000000..31340b64b
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/broadcast/BroadcastNativeInterface.java
@@ -0,0 +1,253 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.broadcast;
+
+import android.bluetooth.BluetoothBroadcast;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
+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;
+import java.util.Arrays;
+
+/**
+ * Broadcast Native Interface to/from JNI.
+ */
+
+public class BroadcastNativeInterface {
+ private static final String TAG = "BroadcastNativeInterface";
+ private static final boolean DBG = true;
+ private BluetoothAdapter mAdapter;
+ @GuardedBy("INSTANCE_LOCK")
+ private static BroadcastNativeInterface sInstance;
+ private static final Object INSTANCE_LOCK = new Object();
+
+ static {
+ classInitNative();
+ }
+
+ @VisibleForTesting
+ private BroadcastNativeInterface() {
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (mAdapter == null) {
+ Log.wtfStack(TAG, "No Bluetooth Adapter Available");
+ }
+ }
+
+ /**
+ * Get singleton instance.
+ */
+ public static BroadcastNativeInterface getInstance() {
+ synchronized (INSTANCE_LOCK) {
+ if (sInstance == null) {
+ sInstance = new BroadcastNativeInterface();
+ }
+ 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 maxBroadcast, BluetoothCodecConfig codecConfig, int mode) {
+ initNative(maxBroadcast, codecConfig, mode);
+ }
+
+ /**
+ * Cleanup the native interface.
+ */
+ public void cleanup() {
+ cleanupNative();
+ }
+
+ /**
+ * Sets a connected A2DP remote device as active.
+ *
+ * @param device the remote device
+ * @return true on success, otherwise false.
+ */
+ public boolean enableBroadcast(BluetoothCodecConfig mCodecConfig) {
+ Log.d(TAG, "enableBroadcast");
+ return enableBroadcastNative(mCodecConfig);
+ }
+ //Move SM to IDLE
+ public boolean disableBroadcast(int adv_id) {
+ Log.d(TAG, "disableBroadcast");
+ return disableBroadcastNative(adv_id);
+ }
+ //Remove ISO Data path for reconfiguration
+ public boolean SetupAudioPath(boolean enable, int adv_id, int BIG_handle, int num_bises, int[] bises) {
+ Log.d(TAG, "SetupAudioPath for BIG Handle: " + BIG_handle);
+ return setupAudioPathNative(enable, adv_id, BIG_handle, num_bises, bises);
+ }
+ //Star/End Session
+ public boolean setActiveDevice(boolean enable, int advID) {
+ Log.d(TAG, "SetActiveDevice");
+ return setActiveDeviceNative(enable, advID);
+ }
+ //Retrieve stored encryption key
+ public String GetEncryptionKey() {
+ Log.d(TAG, "GetEncryptionKey");
+ return getEncryptionKeyNative();
+ }
+ //Set new encyption key
+ public boolean SetEncryptionKey(boolean enabled, int length) {
+ Log.d(TAG, "SetEncryptionKey");
+ return setEncryptionKeyNative(enabled, length);
+ }
+ /**
+ * 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.
+ */
+ //Restart session with new codec config
+ public boolean setCodecConfigPreference(int adv_handle, BluetoothCodecConfig codecConfig) {
+ Log.d(TAG, "setCodecConfigPreference");
+ return setCodecConfigPreferenceNative(adv_handle, codecConfig);
+ }
+ private int translate_state_to_app(int event, int state) {
+ if (event == BroadcastStackEvent.EVENT_TYPE_BROADCAST_STATE_CHANGED) {
+ switch(state) {
+ case BroadcastStackEvent.STATE_IDLE:
+ return BluetoothBroadcast.STATE_DISABLED;
+ case BroadcastStackEvent.STATE_CONFIGURED:
+ return BluetoothBroadcast.STATE_ENABLED;
+ case BroadcastStackEvent.STATE_STREAMING:
+ return BluetoothBroadcast.STATE_STREAMING;
+ default:
+ return BluetoothBroadcast.STATE_DISABLED;
+ }
+ } else if (event == BroadcastStackEvent.EVENT_TYPE_BROADCAST_AUDIO_STATE_CHANGED) {
+ switch(state) {
+ case BroadcastStackEvent.STATE_STOPPED:
+ return BluetoothBroadcast.STATE_NOT_PLAYING;
+ case BroadcastStackEvent.STATE_STARTED:
+ return BluetoothBroadcast.STATE_PLAYING;
+ default:
+ return BluetoothBroadcast.STATE_NOT_PLAYING;
+ }
+ }
+ return BluetoothBroadcast.STATE_DISABLED;
+ }
+ private void sendMessageToService(BroadcastStackEvent event) {
+ BroadcastService service = BroadcastService.getBroadcastService();
+ if (service != null) {
+ service.messageFromNative(event);
+ } else {
+ Log.w(TAG, "Event ignored, service not available: " + event);
+ }
+ }
+
+ private void onBroadcastStateChanged(int adv_handle, int state) {
+ BroadcastStackEvent event =
+ new BroadcastStackEvent(BroadcastStackEvent.EVENT_TYPE_BROADCAST_STATE_CHANGED);
+ event.valueInt = translate_state_to_app(BroadcastStackEvent.EVENT_TYPE_BROADCAST_STATE_CHANGED,state);
+ event.advHandle = adv_handle;
+ if (DBG) {
+ Log.d(TAG, "onBroadcastStateChanged: " + event);
+ }
+ sendMessageToService(event);
+ }
+
+ private void onAudioStateChanged(int adv_handle, int state) {
+ BroadcastStackEvent event =
+ new BroadcastStackEvent(BroadcastStackEvent.EVENT_TYPE_BROADCAST_AUDIO_STATE_CHANGED);
+ event.valueInt = translate_state_to_app(BroadcastStackEvent.EVENT_TYPE_BROADCAST_AUDIO_STATE_CHANGED,state);
+ event.advHandle = adv_handle;
+ if (DBG) {
+ Log.d(TAG, "onAudioStateChanged: " + event);
+ }
+ sendMessageToService(event);
+ }
+
+ private void onEncryptionKeyGenerated(String key) {
+ BroadcastStackEvent event =
+ new BroadcastStackEvent(BroadcastStackEvent.EVENT_TYPE_ENC_KEY_GENERATED);
+ event.key = key;
+ if (DBG) {
+ Log.d(TAG, "onEncryptionKeyGenerated: " + event);
+ }
+ sendMessageToService(event);
+ }
+
+ private void onCodecConfigChanged(int adv_handle, BluetoothCodecConfig newCodecConfig,
+ BluetoothCodecConfig[] codecCapabilities) {
+ BroadcastStackEvent event =
+ new BroadcastStackEvent(BroadcastStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED);
+ event.codecStatus = new BluetoothCodecStatus(newCodecConfig, codecCapabilities, codecCapabilities);
+ event.advHandle = adv_handle;
+ if (DBG) {
+ Log.d(TAG, "onCodecConfigChanged: " + event);
+ }
+ sendMessageToService(event);
+ }
+
+ private void onSetupBIG(int setup, int adv_id, int big_handle, int num_bises, char[] bis_handles) {
+ BroadcastStackEvent event = new BroadcastStackEvent(BroadcastStackEvent.EVENT_TYPE_SETUP_BIG);
+ event.valueInt = setup;
+ event.advHandle = adv_id;
+ event.bigHandle = big_handle;
+ event.NumBises = num_bises;
+ if (DBG) {
+ Log.d(TAG, "onSetupBIG: " + event);
+ }
+ sendMessageToService(event);
+ }
+
+ private void onBroadcastIdGenerated(byte[] broadcast_id) {
+ BroadcastStackEvent event =
+ new BroadcastStackEvent(BroadcastStackEvent.EVENT_TYPE_BROADCAST_ID_GENERATED);
+ Log.d(TAG,"onBroadcastIdGenerated");
+ for(int i = 0; i < 3; i++) {
+ event.BroadcastId[i] = broadcast_id[i];
+ Log.d(TAG, "BroadcastID ["+i+"]" + " = " + event.BroadcastId[i]);
+ }
+ if (DBG) {
+ Log.d(TAG, "onBroadcastIdGenerated: " + event);
+ }
+ sendMessageToService(event);
+ }
+
+ // Native methods that call into the JNI interface
+ private static native void classInitNative();
+ private native void initNative(int maxBroadcast, BluetoothCodecConfig codecConfig, int mode);
+ private native void cleanupNative();
+ private native boolean setActiveDeviceNative(boolean enable, int adv_id);
+ private native boolean enableBroadcastNative(BluetoothCodecConfig codecConfig);
+ private native boolean disableBroadcastNative(int adv_id);
+ private native boolean setupAudioPathNative(boolean enable, int adv_id, int big_handle,
+ int num_bises, int[] bises);
+ private native String getEncryptionKeyNative();
+ private native boolean setEncryptionKeyNative(boolean enabled, int length);
+ private native boolean setCodecConfigPreferenceNative(int adv_id, BluetoothCodecConfig codecConfig);
+
+}
+
+
diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/broadcast/BroadcastService.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/broadcast/BroadcastService.java
new file mode 100644
index 000000000..96ccd06f7
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/broadcast/BroadcastService.java
@@ -0,0 +1,1960 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.broadcast;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothBroadcast;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.bluetooth.IBluetoothBroadcast;
+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.util.Log;
+import android.util.StatsLog;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import android.os.Handler;
+import android.os.Message;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+
+import com.android.bluetooth.BluetoothMetricsProto;
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.avrcp.Avrcp;
+import com.android.bluetooth.avrcp.Avrcp_ext;
+import com.android.bluetooth.avrcp.AvrcpTargetService;
+import com.android.bluetooth.a2dp.A2dpService;
+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.ba.BATService;
+import com.android.bluetooth.gatt.GattService;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.bluetooth.hfp.HeadsetService;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.AdvertisingSet;
+import android.bluetooth.le.AdvertisingSetCallback;
+import android.bluetooth.le.AdvertisingSetParameters;
+import android.bluetooth.le.BluetoothLeAdvertiser;
+import android.bluetooth.le.PeriodicAdvertisingParameters;
+import android.media.MediaMetadata;
+
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.UUID;
+import java.util.HashMap;
+import android.os.ParcelUuid;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+
+import com.android.bluetooth.apm.ActiveDeviceManagerService;
+import com.android.bluetooth.apm.ApmConst;
+import com.android.bluetooth.apm.DeviceProfileMap;
+import com.android.bluetooth.apm.MediaAudio;
+
+/**
+ * Provides Bluetooth Broadcast profile, as a service in the Bluetooth application.
+ * @hide
+ */
+public class BroadcastService extends ProfileService {
+ private static final boolean DBG = true;
+ private static final boolean VDBG = true;
+ private static final String TAG = "BroadcastService";
+ private final Object mBroadcastLock = new Object();
+ private static BroadcastService sBroadcastService;
+ private AdapterService mAdapterService;
+ @VisibleForTesting
+ BroadcastNativeInterface mBroadcastNativeInterface;
+ @VisibleForTesting
+ ServiceFactory mFactory = new ServiceFactory();
+ private AudioManager mAudioManager;
+ int mBroadcastState = BluetoothBroadcast.STATE_DISABLED;
+ int mBroadcastAudioState = BluetoothBroadcast.STATE_NOT_PLAYING;
+ private String mEncryptionString;
+ private byte[] mEncKey = new byte[16];
+ private byte [] BigBroadcastCode = new byte [16];
+ private byte [] mBroadcastID = new byte[3];
+ private final int mBroadcastIdLength = 3;
+ private boolean mEncryptionEnabled = true;
+ private boolean mPartialSimulcast = false;//dual quality simulcast
+ private boolean mEncKeyRefreshed = false;
+ private int mEncryptionLength =16;
+ private int mDefaultEncryptionLength = 16;
+ private int [] bis_handles;
+ private int mBIGHandle = -1;
+ private int mNumBises = -1;
+ private int mNumSubGrps = 1;
+ private int mPD = 0;
+ private boolean goingDown = false;
+ private boolean mIsAdvertising = false;
+ private BroadcastMessageHandler mHandler;
+ private AdvertisingSetCallback mCallback;
+ private AdvertisingSet mAdvertisingSet;
+ List <BisInfo> mBisInfo;
+ Map<Integer, MetadataLtv>mMetaInfo = 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 <String> broadcast_supported_config = new ArrayList<String>(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<BisInfo> BroadcastGetBisInfo() {
+ if (isBroadcastStreaming()) {
+ return mBisInfo;
+ }
+ Log.d(TAG,"BroadcastGetBisInfo: Broadcast is not active");
+ return mBisInfo;
+ }
+
+ public Map<Integer, MetadataLtv> 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 000000000..a1b5596d5
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/broadcast/BroadcastStackEvent.java
@@ -0,0 +1,120 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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 000000000..eb4df0bcb
--- /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 000000000..1e93c62fc
--- /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<CallControlState> callList) {
+ int len = callList.size();
+ byte[] cStateListBytes = new byte[len*3];
+ for (int i=0; i<len; i++) {
+ cStateListBytes[3*i+0] = (byte) callList.get(i).mIndex;
+ cStateListBytes[3*i+1] = (byte) CCHalConstants.getCCsCallState(callList.get(i).mState);
+ cStateListBytes[3*i+2] = (byte) callList.get(i).mFlags;
+ }
+ return callStateNative(len, cStateListBytes);
+ }
+
+ /**
+ * update CC Bearer name
+ * @param Bearer name
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean updateBearerProviderName(String name) {
+ return updateBearerNameNative(name);
+ }
+
+ /**
+ * update CC Bearer technology type
+ * @param Bearer technology
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean updateBearerTechnology(int tech_type) {
+ return updateBearerTechnologyNative(tech_type);
+
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean updateStatusFlags(int value) {
+ return updateStatusFlagsNative(value);
+ }
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean updateSignalStrength(int signal_value) {
+ return updateSignalStatusNative(signal_value);
+ }
+
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean updateIncomingCall(int index, String uri) {
+ updateIncomingCallNative(index, uri);
+ return true;
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean updateSupportedBearerList(String supportedBearers) {
+ return updateSupportedBearerListNative(supportedBearers);
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean callControlResponse(int op, int index, int status, BluetoothDevice device) {
+ return callControlResponseNative(op, index, status, getByteAddress(device));
+ }
+
+ /**
+ * update active device
+ * @param device
+ * @param setId
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean setActiveDevice(BluetoothDevice device, int setId) {
+ return setActiveDeviceNative(setId, getByteAddress(device));
+ }
+ /**
+ * Sets call content control id
+ * @param ccid
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean contentControlId(int ccid) {
+
+ return contentControlIdNative(ccid);
+ }
+
+ 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.
+ private void callControlInitializedCallback(int state) {
+ if (DBG) {
+ Log.d(TAG, "CallControlInitializedCallback: " + state);
+ }
+
+ CCService service = CCService.getCCService();
+ if (service != null) {
+ service.onCallControlInitialized(state);
+ }
+ }
+
+ private void onConnectionStateChanged(int state, byte[] address) {
+ if (DBG) {
+ Log.d(TAG, "OnConnectionStateChanged: " + state);
+ }
+ BluetoothDevice device = getDevice(address);
+
+ CCService service = CCService.getCCService();
+ if (service != null)
+ service.onConnectionStateChanged(device, state);
+ }
+
+ private void callControlPointChangedRequest(int op, int[]call_indices, int count, byte[] dialNumber, byte[] address) {
+ BluetoothDevice device = getDevice(address);
+ String dialUri = new String(dialNumber, StandardCharsets.UTF_8);
+ if (DBG) {
+ Log.d(TAG, "CallControlPointChangedRequest: " + op + "dialNumber: " + dialUri);
+ }
+ CCService service = CCService.getCCService();
+ if (service != null)
+ service.onCallControlPointChangedRequest(op, call_indices, count, dialUri, device);
+ }
+
+ // Native methods that call into the JNI interface
+ private static native void classInitNative();
+ private native void initializeNative(String uuid, int max_ccs_clients, boolean inbandRingingEnabled);
+ private native void cleanupNative();
+ private native boolean callControlPointOpcodeSupportedNative(int feature);
+ private native boolean callStateNative(int len, byte[] callStateList);
+ private native boolean updateBearerNameNative(String providerName);
+ private native boolean updateBearerTechnologyNative(int tech_type);
+ private native boolean updateSignalStatusNative(int signal);
+ private native boolean updateStatusFlagsNative(int value);
+ private native boolean setActiveDeviceNative(int setId, byte[] address);
+ private native boolean contentControlIdNative(int ccid);
+ private native boolean disconnectNative(byte[] address);
+ private native boolean callControlResponseNative(int op, int index, int status, byte[] address);
+ private native boolean updateSupportedBearerListNative(String supportedBearers);
+ private native boolean updateIncomingCallNative(int index, String uri);
+}
diff --git a/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCService.java b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCService.java
new file mode 100644
index 000000000..723bc2eca
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/cc/CCService.java
@@ -0,0 +1,864 @@
+/*
+ *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 static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.telephony.PhoneStateListener;
+import android.content.SharedPreferences;
+import android.util.Log;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Queue;
+import java.util.LinkedList;
+import java.util.HashMap;
+import java.util.Map;
+import android.os.Message;
+import android.os.Binder;
+import android.os.IBinder;
+
+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.SystemProperties;
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.apm.ApmConst;
+import com.android.bluetooth.apm.CallAudio;
+import com.android.bluetooth.apm.CallControl;
+import com.android.bluetooth.apm.ActiveDeviceManagerService;
+import java.util.Objects;
+
+/**
+ * Provides Bluetooth CC profile as a service in the Bluetooth application.
+ * @hide
+ */
+public class CCService extends ProfileService {
+
+ private static final String TAG = "CCService";
+ private static final String DISABLE_INBAND_RINGING_PROPERTY =
+ "persist.bluetooth.disableinbandringing";
+ private static final boolean DBG = true;
+ private static CCService sCCService;
+ private BroadcastReceiver mBondStateChangedReceiver;
+
+ private int mCCId = 0xFF;
+ private BluetoothDevice mActiveDevice;
+ private BluetoothDevice mCallOriginatedDevice = null;
+ private AdapterService mAdapterService;
+ private CCNativeInterface mNativeInterface;
+ private CallAudio mCallAudio = null;
+ private ActiveDeviceManagerService mActiveDevMgrService = null;
+ private Context mContext = null;;
+ private CcsMessageHandler mHandler;
+ private int mMaxConnectedAudioDevices = 1;
+ private boolean InBandRingtoneSupport = false;
+ private boolean mVirtualCallStarted = false;
+ private boolean mStarted;
+ private boolean mCreated;
+ private static int mLatestActiveCallIndex = 0;
+ private static int mLatestHeldCallIndex = 0;
+ private CallControlState mPrevTelephonyState = null;
+ private HashMap<Integer, CallControlState> mCallStateList = null;
+ private HashMap<Integer, CallControlState> mPrevCallStateList = null;
+ private Queue<Integer> mLccTobeQueued = null;
+ private Queue<Integer> 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<CallControlState> values = mCallStateList.values();
+ ArrayList<CallControlState> 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<CallControlState> listOfValues) {
+ Log.d(TAG, "updateCallState");
+ Message msg = mHandler.obtainMessage();
+ msg.what = UPDATE_CALL_STATE;
+ msg.obj = listOfValues;
+ mHandler.sendMessage(msg);
+ }
+
+ public void processAndUpdateCallState(ArrayList<CallControlState> 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<Integer, CallControlState> 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<CallControlState> values = mCallStateList.values();
+ ArrayList<CallControlState> 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<CallControlState> 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<CallControlState> 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<CallControlState>)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<CallControlState>)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 000000000..2feb65a97
--- /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 000000000..13375e49f
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupAppMap.java
@@ -0,0 +1,178 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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<GroupClientApp> mApps = Collections.synchronizedList(new ArrayList<GroupClientApp>());
+
+ ArrayList<UUID> appUuids = new ArrayList<UUID>();
+
+ /**
+ * 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<GroupClientApp> 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<GroupClientApp> 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<GroupClientApp> 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<GroupClientApp> 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 000000000..ce1076f48
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupClientNativeInterface.java
@@ -0,0 +1,251 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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<BluetoothDevice> 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<BluetoothDevice> lockMembers = new ArrayList<BluetoothDevice>();
+ 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 000000000..bc192b2f3
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupScanner.java
@@ -0,0 +1,529 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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<BluetoothDevice> scannedDevices = new ArrayList<BluetoothDevice>();
+
+ 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<BluetoothDevice> 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<ScanFilter> filters = new ArrayList<ScanFilter>();
+ 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 000000000..5f8ba52e5
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/groupclient/GroupService.java
@@ -0,0 +1,1107 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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<DeviceGroup> mCoordinatedSets
+ = new CopyOnWriteArrayList<DeviceGroup>();
+
+ private static HashMap<Integer, byte[]> setSirkMap = new HashMap<Integer, byte[]>();
+
+ 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<BluetoothDevice> members = new ArrayList<BluetoothDevice>();
+ 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<BluetoothDevice> 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<BluetoothDevice> 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<DeviceGroup> 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<BluetoothDevice> 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<BluetoothDevice> 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<DeviceGroup> getDiscoveredCoordinatedSets() {
+ return getDiscoveredCoordinatedSets(true);
+ }
+
+ /* returns all discovered coordinated sets */
+ public List<DeviceGroup> 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<DeviceGroup> coordinatedSets = new ArrayList<DeviceGroup>();
+ AdapterService adapterService = Objects.requireNonNull(
+ AdapterService.getAdapterService(),
+ "AdapterService cannot be null");
+ for (DeviceGroup set: mCoordinatedSets) {
+ DeviceGroup cSet = new DeviceGroup(
+ set.getDeviceGroupId(), set.getDeviceGroupSize(),
+ new ArrayList<BluetoothDevice>(),
+ 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<BluetoothDevice>(),
+ 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<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
+ 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<BluetoothDevice> 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 000000000..b850ce9e3
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/mcp/McpNativeInterface.java
@@ -0,0 +1,274 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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 000000000..316338296
--- /dev/null
+++ b/le_audio/packages/apps/Bluetooth/src/com/android/bluetooth/mcp/McpService.java
@@ -0,0 +1,842 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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<MusicPlayerDetail, String> 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; //<TBD> 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 000000000..f0847716f
--- /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<BluetoothDevice, PacsClientStateMachine> 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 000000000..b7a4c516b
--- /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 000000000..ff1be613f
--- /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 000000000..23ff16eac
--- /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 000000000..ab29aa031
--- /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<BluetoothDevice, VcpControllerStateMachine> mStateMachines =
+ new HashMap<>();
+ private HashMap<BluetoothDevice, Integer> 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 000000000..89cc69bf0
--- /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 000000000..fc35753dd
--- /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 000000000..e77f95d84
--- /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 000000000..47ec0c4b6
--- /dev/null
+++ b/le_audio/packages/apps/Settings/Android.bp
@@ -0,0 +1,31 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_bt_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_bt_license"],
+}
+
+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 000000000..7f79465de
--- /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 000000000..e470b29f0
--- /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<BleBroadcastSourceChannel> 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<Integer, BleBroadcastSourceInfo> srcInfos =
+ mVendorCachedDevice.getAllBleBroadcastreceiverStates();
+ if (srcInfos == null) {
+ return;
+ }
+ for (Map.Entry<Integer, BleBroadcastSourceInfo> 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<String> valueSet = ((MultiSelectListPreference)preference).getValues();
+
+ String[] selectedStrings = new String[((Set<String>) newValue).size()];
+
+ //noinspection unchecked
+ int j =0;
+ for (String value : (Set<String>) newValue) {
+ selectedStrings[j] = value;
+ for (int i=0; i<mBisIndicies.size(); i++) {
+ if (value.equals(mBisIndicies.get(i).getDescription())) {
+ BroadcastScanAssistanceUtils.debug(TAG, "Selected: value["+ i + "]- " + value);
+ if (mBisIndicies.get(i).getStatus() == true) {
+ mBisIndicies.get(i).setStatus(false);
+ } else {
+ mBisIndicies.get(i).setStatus(true);
+ }
+ }
+ }
+ BroadcastScanAssistanceUtils.debug(TAG, "value["+ j++ + "]- " + value);
+ }
+ mIsValueChanged = true;
+ }
+ mIsButtonRefreshOnly = true;
+ refresh();
+ return true;
+ }
+
+ /**
+ * When the pref for a ble broadcast source info details is clicked on, necessary action will be
+ * taken and updateBroadcastSourceInfo would be called as needed
+ */
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ String key = preference.getKey();
+ BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":onPreferenceClick");
+ mIsValueChanged = true;
+ if (mPAsyncCtrlNeeded) {
+ if (key.equals(KEY_SOURCE_METADATA_SWITCH)) {
+ SwitchPreference pref = (SwitchPreference)preference;
+ BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":Meta data sync state: " + pref.isChecked());
+ if (pref.isChecked()) mMetadataSyncState = BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC;
+ else mMetadataSyncState = BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IDLE;
+
+ //Update the audio sync state as well
+ if (mSourceAudioSyncSwitchPref != null) {
+ if (mSourceAudioSyncSwitchPref.isChecked()) {
+ mAudioSyncState = BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED;
+ }
+ else {
+ mAudioSyncState = BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED;
+ }
+ }
+ }
+ }
+ if (key.equals(KEY_SOURCE_AUDIOSYNC_SWITCH)) {
+ SwitchPreference pref = (SwitchPreference)preference;
+ BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":Audio sync state: " + pref.isChecked());
+
+ if (pref.isChecked()) mAudioSyncState = BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED;
+ else mAudioSyncState = BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED;
+
+ if (mPAsyncCtrlNeeded) {
+ //Update the metadata sync state as well
+ if (mSourceMetadataSyncSwitchPref != null) {
+ if (mSourceMetadataSyncSwitchPref.isChecked()) {
+ mMetadataSyncState = BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED;
+ }
+ else {
+ mMetadataSyncState = BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED;
+ }
+ }
+ }
+ } else if (key.equals(KEY_UPDATE_BCAST_CODE)) {
+ EditTextPreference pref = (EditTextPreference)preference;
+ BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":>>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<String> hashSet = new HashSet<String>();
+ for (int i=0; i<mBisIndicies.size(); i++) {
+ bisNames[i] = mBisIndicies.get(i).getDescription();
+ bisStatuses[i] = mBisIndicies.get(i).getStatus();
+ }
+ hashSet.addAll(Arrays.asList(bisNames));
+ mSourceAudioSyncStatusPref.setEntries(bisNames);
+ mSourceAudioSyncStatusPref.setEntryValues(bisNames);
+ mSourceAudioSyncStatusPref.setValues(hashSet);
+ }
+
+ //Reflect the controls based on the status
+ if (mPAsyncCtrlNeeded) {
+ mSourceMetadataSyncSwitchPref.setChecked(mBleBroadcastSourceInfo.getMetadataSyncState() ==
+ BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC);
+ }
+ mSourceAudioSyncSwitchPref.setChecked(mBleBroadcastSourceInfo.getAudioSyncState() ==
+ BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED);
+
+ int getFirstSyncedBisIndex = -1;
+ if (mBisIndicies != null) {
+ for (int i=0; i<mBisIndicies.size(); i++) {
+ if (mBisIndicies.get(i).getStatus() == true) {
+ getFirstSyncedBisIndex = i;
+ break;
+ }
+ }
+ }
+ byte[] metadata = null;
+ if (getFirstSyncedBisIndex != -1) {
+ metadata = mBisIndicies.get(getFirstSyncedBisIndex).getMetadata();
+ }
+ if (metadata != null) {
+ String metaDataStr = new String(metadata);
+ BroadcastScanAssistanceUtils.debug(TAG, mSourceInfoIndex + ":Metadata:" + metaDataStr);
+ mSourceMetadataPref.setSummary(metaDataStr);
+ } else {
+ mSourceMetadataPref.setSummary("NONE");
+ }
+ if (mBleBroadcastSourceInfo != null &&
+ (mBleBroadcastSourceInfo.getMetadataSyncState() != BleBroadcastSourceInfo.BROADCAST_ASSIST_PA_SYNC_STATE_IN_SYNC &&
+ mBleBroadcastSourceInfo.getAudioSyncState() != BleBroadcastSourceInfo.BROADCAST_ASSIST_AUDIO_SYNC_STATE_SYNCHRONIZED)) {
+ //Remove source Info button
+ mSourceUpdateSourceInfoPref.setButton2Enabled(true);
+ } else {
+ mSourceUpdateSourceInfoPref.setButton2Enabled(false);
+ }
+ }
+ //User can update OR remove only if the Source info is not an Empty Entry
+ if (mIsValueChanged || isBroadcastPINUpdated) {
+ //User can Update only if any of the entries are modified by user action
+ mSourceUpdateSourceInfoPref.setButton1Enabled(true);
+ } else {
+ mSourceUpdateSourceInfoPref.setButton1Enabled(false);
+ }
+ mIsButtonRefreshOnly = false;
+ }
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_SOURCE_INFO_GROUP;
+ }
+}
diff --git a/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoDetailsFragment.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoDetailsFragment.java
new file mode 100644
index 000000000..ec0d3a50c
--- /dev/null
+++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BleBroadcastSourceInfoDetailsFragment.java
@@ -0,0 +1,129 @@
+/*
+ * 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.BluetoothDevice;
+import android.bluetooth.BleBroadcastSourceInfo;
+import android.content.Context;
+import android.os.Bundle;
+import android.provider.DeviceConfig;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+
+import com.android.settings.R;
+import com.android.settings.core.SettingsUIDeviceConfig;
+import com.android.settings.dashboard.RestrictedDashboardFragment;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.slices.BlockingSlicePrefController;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class BleBroadcastSourceInfoDetailsFragment extends RestrictedDashboardFragment {
+ public static final String KEY_DEVICE_ADDRESS = "device_address";
+ public static final String KEY_SOURCE_INFO = "broadcast_source_info";
+ public static final String KEY_SOURCE_INFO_INDEX = "broadcast_source_index";
+ private static final String TAG = "SourceInfoDetailsFrg";
+ private Context mContext;
+
+ String mDeviceAddress;
+ CachedBluetoothDevice mCachedDevice;
+ LocalBluetoothManager mManager;
+ BleBroadcastSourceInfo mBleBroadcastSourceInfo;
+ Integer mSourceInfoIndex = -1;
+
+ public BleBroadcastSourceInfoDetailsFragment() {
+ super(DISALLOW_CONFIG_BLUETOOTH);
+ }
+
+ CachedBluetoothDevice getCachedDevice(String deviceAddress) {
+ BluetoothDevice remoteDevice =
+ mManager.getBluetoothAdapter().getRemoteDevice(deviceAddress);
+ return mManager.getCachedDeviceManager().findDevice(remoteDevice);
+ }
+
+ public static BleBroadcastSourceInfoDetailsFragment newInstance(String deviceAddress) {
+ Bundle args = new Bundle(1);
+ args.putString(KEY_DEVICE_ADDRESS, deviceAddress);
+ BleBroadcastSourceInfoDetailsFragment fragment = new BleBroadcastSourceInfoDetailsFragment();
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ mDeviceAddress = getArguments().getString(KEY_DEVICE_ADDRESS);
+ mManager = Utils.getLocalBtManager(context);
+ mBleBroadcastSourceInfo = getArguments().getParcelable(KEY_SOURCE_INFO);
+ mCachedDevice = getCachedDevice(mDeviceAddress);
+ mSourceInfoIndex = getArguments().getInt(KEY_SOURCE_INFO_INDEX);
+ super.onAttach(context);
+ if (mCachedDevice == null) {
+ // Close this page if device is null with invalid device mac address
+ Log.w(TAG, "onAttach() CachedDevice is null!");
+ finish();
+ return;
+ }
+ if (mBleBroadcastSourceInfo == null) {
+ Log.w(TAG, "onAttach() mBleBroadcastSourceInfo null!");
+ finish();
+ return;
+ }
+ if (mSourceInfoIndex == null) {
+ Log.w(TAG, "onAttach() mSourceInfoIndex null!");
+ finish();
+ return;
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return SettingsEnums.BLUETOOTH_DEVICE_DETAILS;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.bcast_source_info_details_fragment;
+ }
+
+ @Override
+ protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
+ ArrayList<AbstractPreferenceController> 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 000000000..50c4636a9
--- /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 000000000..8f9a4fb50
--- /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 000000000..6baa20a68
--- /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<Integer, Preference> 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<Integer, Preference>();
+ 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<Integer, BleBroadcastSourceInfo> srcInfos =
+ mVendorCachedDevice.getAllBleBroadcastreceiverStates();
+ if (srcInfos == null) {
+ Log.e(TAG, "srcInfos is null");
+ return;
+ }
+ for (Map.Entry<Integer, BleBroadcastSourceInfo> 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<Map.Entry<Integer, Preference>> entries = mPreferenceMap.entrySet().iterator();
+ while (entries.hasNext()) {
+ //for (Map.Entry<Integer, Preference> entry: mPreferenceMap.entrySet()) {
+ Map.Entry<Integer, Preference> 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 000000000..29f565c41
--- /dev/null
+++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastEnableController.java
@@ -0,0 +1,247 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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 000000000..500d94148
--- /dev/null
+++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastPinController.java
@@ -0,0 +1,237 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 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<key.length; i++) {
+ Log.d(TAG, "pin(" + Integer.toString(i) + "): " + String.format("%02X", key[i]));
+ }
+ keyStr = convertBytesToString(key);
+ }
+ if (keyStr.equals("")) summary = "No Broadcast code";
+ mPreference.setSummary(summary + keyStr);
+ mPreference.setVisible(true);
+ if (keyStr.equals("Unavailable")) {
+ mPreference.setEnabled(false);
+ } else {
+ mPreference.setEnabled(true);
+ }
+ } else {
+ mPreference.setSummary(summary + keyStr);
+ mPreference.setEnabled(false);
+ }
+ }
+
+ @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/BluetoothBroadcastPinFragment.java b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastPinFragment.java
new file mode 100644
index 000000000..eb8068b04
--- /dev/null
+++ b/le_audio/packages/apps/Settings/src/com/android/settings/bluetooth/BluetoothBroadcastPinFragment.java
@@ -0,0 +1,256 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.DialogFragment;
+
+import android.app.settings.SettingsEnums;
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.bluetooth.BluetoothBroadcastEnableController;
+import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.bluetooth.BroadcastProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothProfile;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Dialog fragment for renaming a Bluetooth device.
+ */
+public class BluetoothBroadcastPinFragment extends InstrumentedDialogFragment
+ implements RadioGroup.OnCheckedChangeListener {
+
+ public static BluetoothBroadcastPinFragment newInstance() {
+ Log.d(TAG, "newInstance");
+ BluetoothBroadcastPinFragment frag = new BluetoothBroadcastPinFragment();
+ return frag;
+ }
+
+ public static final String TAG = "BluetoothBroadcastPinFragment";
+
+ private Context mContext;
+ @VisibleForTesting
+ AlertDialog mAlertDialog = null;
+ private Dialog mDialog = null;
+ private Button mOkButton = null;
+ private TextView mCurrentPinView;
+
+ private String mCurrentPin = "4308";
+ private int mUserSelectedPinConfiguration = -1;
+
+ private List<Integer> mRadioButtonIds = new ArrayList<>();
+ private List<String> 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 000000000..110d9b7b1
--- /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 000000000..536bc1bfb
--- /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 000000000..174332cf2
--- /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<BleBroadcastSourceChannel> 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<BleBroadcastSourceChannel> 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<BleBroadcastSourceChannel> arrayAdapter =
+ new ArrayAdapter<BleBroadcastSourceChannel>(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 000000000..9883e70ae
--- /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 000000000..4744b11c3
--- /dev/null
+++ b/le_audio/system/bt/binder/Android.bp
@@ -0,0 +1,36 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.
+ *
+ ******************************************************************************/
+
+// AIDL interface between libbluetooth-binder and framework.jar
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_bt_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_bt_license"],
+}
+
+filegroup {
+ name: "libbluetooth-binder-aidl-adva",
+ srcs: [
+ "android/bluetooth/IBluetoothSyncHelper.aidl",
+ "android/bluetooth/IBleBroadcastAudioScanAssistCallback.aidl",
+ "android/bluetooth/IBluetoothBroadcast.aidl",
+ ],
+}
diff --git a/internal_include/bt_common.h b/le_audio/system/bt/binder/android/bluetooth/BleBroadcastSourceChannel.aidl
index 31de238de..4b94f9493 100644
--- a/internal_include/bt_common.h
+++ b/le_audio/system/bt/binder/android/bluetooth/BleBroadcastSourceChannel.aidl
@@ -1,6 +1,6 @@
/******************************************************************************
*
- * Copyright 2015 Google, Inc.
+ * Copyright 2021 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.
@@ -16,9 +16,8 @@
*
******************************************************************************/
-#pragma once
-#include "bt_target.h"
-#include "osi/include/allocator.h"
-#include "osi/include/compat.h"
-#include "stack/include/bt_types.h"
+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 000000000..8eb14da62
--- /dev/null
+++ b/le_audio/system/bt/binder/android/bluetooth/BleBroadcastSourceInfo.aidl
@@ -0,0 +1,22 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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;
+
+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 000000000..223f8135f
--- /dev/null
+++ b/le_audio/system/bt/binder/android/bluetooth/IBleBroadcastAudioScanAssistCallback.aidl
@@ -0,0 +1,48 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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<BleBroadcastSourceChannel>
+ 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 000000000..e3054e9aa
--- /dev/null
+++ b/le_audio/system/bt/binder/android/bluetooth/IBluetoothBroadcast.aidl
@@ -0,0 +1,35 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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 000000000..495c1ffb0
--- /dev/null
+++ b/le_audio/system/bt/binder/android/bluetooth/IBluetoothSyncHelper.aidl
@@ -0,0 +1,75 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.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<BluetoothDevice> getConnectedDevices();
+ List<BluetoothDevice> 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<BleBroadcastSourceInfo> 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 000000000..6daca85d8
--- /dev/null
+++ b/le_audio/system/bt/bta/Android.bp
@@ -0,0 +1,97 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_bt_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_bt_license"],
+}
+
+cc_defaults {
+ name: "fluoride_bta_defaults_qti_adva",
+ defaults: ["fluoride_defaults"],
+ local_include_dirs: [
+ "include",
+ ],
+ include_dirs: [
+ "vendor/qcom/opensource/commonsys/system/bt",
+ "vendor/qcom/opensource/commonsys/system/bt/bta/include",
+ "vendor/qcom/opensource/commonsys/system/bt/bta/ag",
+ "vendor/qcom/opensource/commonsys/system/bt/btcore/include",
+ "vendor/qcom/opensource/commonsys/system/bt/hci/include",
+ "vendor/qcom/opensource/commonsys/system/bt/internal_include",
+ "vendor/qcom/opensource/commonsys/system/bt/stack/include",
+ "vendor/qcom/opensource/commonsys/system/bt/stack/btm",
+ "vendor/qcom/opensource/commonsys/system/bt/udrv/include",
+ "vendor/qcom/opensource/commonsys/system/bt/vnd/include",
+ "vendor/qcom/opensource/commonsys/system/bt/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/system/bt/device/include",
+ "vendor/qcom/opensource/commonsys/system/bt/btif/include",
+ "vendor/qcom/opensource/commonsys/system/bt/bta/include",
+ "vendor/qcom/opensource/commonsys/system/bt/bta/sys",
+ "vendor/qcom/opensource/commonsys/bluetooth_lea/system/bt",
+ "vendor/qcom/opensource/commonsys/bluetooth_lea/system/bt/bta/include",
+ "vendor/qcom/opensource/commonsys/bluetooth_lea/vhal/include",
+ "vendor/qcom/opensource/commonsys/bluetooth_lea/system/bt/bta/bap",
+ "vendor/qcom/opensource/commonsys/bluetooth_lea/system/bt/btif/include",
+ "system/bt/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 000000000..1be5f7cc2
--- /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 <map>
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/logging.h>
+#include "stack/btm/btm_int.h"
+#include "device/include/controller.h"
+
+#include <vector>
+#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<uint8_t, std::string> 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<uint8_t, std::string> 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<AseParams> 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<Ase> sink_ase_list;
+ std::vector<Ase> 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<ProfileOperation> profile_queue;
+ std::vector<uint16_t> 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<AscsDevice> 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<AseCodecConfigOp> codec_configs) {
+ AscsDevice* dev = ascsDevices.FindByAddress(address);
+ std::vector<uint8_t> vect_val;
+ uint8_t opcode = static_cast<uint8_t> (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<AseQosConfigOp> qos_configs) {
+ AscsDevice* dev = ascsDevices.FindByAddress(address);
+ std::vector<uint8_t> vect_val;
+ uint8_t opcode = static_cast<uint8_t> (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<AseEnableOp> enable_ops) {
+ AscsDevice* dev = ascsDevices.FindByAddress(address);
+ std::vector<uint8_t> vect_val;
+ uint8_t opcode = static_cast<uint8_t> (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<AseStartReadyOp> start_ready_ops) {
+ AscsDevice* dev = ascsDevices.FindByAddress(address);
+ std::vector<uint8_t> vect_val;
+ uint8_t opcode = static_cast<uint8_t> (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<AseDisableOp> disable_ops) {
+ AscsDevice* dev = ascsDevices.FindByAddress(address);
+ std::vector<uint8_t> vect_val;
+ uint8_t opcode = static_cast<uint8_t> (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<AseStopReadyOp> stop_ready_ops) {
+ AscsDevice* dev = ascsDevices.FindByAddress(address);
+ std::vector<uint8_t> vect_val;
+ uint8_t opcode = static_cast<uint8_t> (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<AseReleaseOp> release_ops) {
+ AscsDevice* dev = ascsDevices.FindByAddress(address);
+ std::vector<uint8_t> vect_val;
+ uint8_t opcode = static_cast<uint8_t> (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<AseUpdateMetadataOp> metadata_ops) {
+ AscsDevice* dev = ascsDevices.FindByAddress(address);
+ std::vector<uint8_t> vect_val;
+ uint8_t opcode = static_cast<uint8_t> (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<int>(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<AseParams> ase_value_list;
+ callback->OnSearchComplete(0xFF, dev->address, ase_value_list,
+ ase_value_list);
+ }
+ return;
+ }
+
+ const std::vector<gatt::Service>* 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<AseParams> 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<uint8_t> 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<uint8_t> 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<int> (sizeof(presentation_delay_t)));
+ STREAM_TO_ARRAY(&(codec_config->pd_max), p,
+ static_cast<int> (sizeof(presentation_delay_t)));
+ STREAM_TO_ARRAY(&(codec_config->pref_pd_min), p,
+ static_cast<int> (sizeof(presentation_delay_t)));
+ STREAM_TO_ARRAY(&(codec_config->pref_pd_max), p,
+ static_cast<int> (sizeof(presentation_delay_t)));
+ STREAM_TO_ARRAY(&(codec_config->codec_id),
+ p, static_cast<int> (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<int> (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<int> (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<AseOpStatus> 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<uint16_t, AscsClientCallbacks *> 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 000000000..60dae7c36
--- /dev/null
+++ b/le_audio/system/bt/bta/bap/connected_iso.cc
@@ -0,0 +1,1556 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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_bap_uclient_api.h"
+#include "btm_int.h"
+#include <list>
+#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 <DataPathNode> 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<uint8_t> cis_ids;
+ std::vector<uint16_t> cis_handles;
+ RawAddress peer_bda;
+};
+
+struct CIG {
+ CIGConfig cig_config;
+ CigState cig_state;
+ std::map<RawAddress, uint8_t> clients_list; // address and count
+ std::map<uint8_t, CIS *> 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<CISConfig> &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<uint8_t> 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<uint16_t> 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, &param)) {
+ 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,
+ &param)) {
+ 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<uint8_t, CIG *> cig_list; // cig id to CIG structure
+ std::list <CreateCisNode> 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<CISConfig> &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, &param);
+ }
+}
+
+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, &param);
+ }
+}
+
+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, &param);
+ }
+}
+
+#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, &param);
+ }
+}
+#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 000000000..7c7b187d0
--- /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 <list>
+#include <unordered_map>
+#include <unordered_set>
+
+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<uint16_t, std::list<gatt_operation>>
+ GattOpsQueue::gatt_op_queue;
+std::unordered_set<uint16_t> GattOpsQueue::gatt_op_queue_executing;
+
+std::unordered_map<uint16_t, bool> 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_operation>& 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_operation>& 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_operation>& 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<uint8_t> 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<uint8_t> 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_operation>& 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 000000000..4dd952536
--- /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 <vector>
+
+#include <list>
+#include <unordered_map>
+#include <unordered_set>
+#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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint16_t, std::list<gatt_operation>> gatt_op_queue;
+
+ // maps connection id to congestion status of each device
+ static std::unordered_map<uint16_t, bool> congestion_queue;
+
+ // contain connection ids that currently execute operations
+ static std::unordered_set<uint16_t> 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 000000000..b84fe6593
--- /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 <list>
+#include <unordered_map>
+#include <unordered_set>
+
+namespace bluetooth {
+namespace bap {
+
+using gatts_operation = GattsOpsQueue::gatts_operation;
+using bluetooth::Uuid;
+
+constexpr uint8_t GATT_NOTIFY = 1;
+
+std::unordered_map<uint16_t, std::list<gatts_operation>>
+ GattsOpsQueue::gatts_op_queue;
+std::unordered_set<uint16_t> GattsOpsQueue::gatts_op_queue_executing;
+std::unordered_map<uint16_t, bool> 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_operation>& 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<uint8_t> 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_operation>& 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 000000000..6c0d5a855
--- /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 <vector>
+
+#include <list>
+#include <unordered_map>
+#include <unordered_set>
+#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<uint8_t> 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<uint8_t> 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<uint16_t, std::list<gatts_operation>> gatts_op_queue;
+
+ // maps connection id to congestion status of each device
+ static std::unordered_map<uint16_t, bool> congestion_queue;
+
+ // contain connection ids that currently execute operations
+ static std::unordered_set<uint16_t> 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 000000000..2123dcf0e
--- /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 <map>
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/logging.h>
+#include "stack/btm/btm_int.h"
+#include "device/include/controller.h"
+#include "osi/include/properties.h"
+
+#include <vector>
+#include "btif/include/btif_bap_config.h"
+#include "osi/include/log.h"
+#include "btif_util.h"
+#include <hardware/bt_bap_uclient.h>
+#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<uint8_t, CodecSampleRate> 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<uint8_t, uint8_t> 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<CodecConfig> sink_pac_records;
+ bool read_sink_pac_record;
+};
+
+struct SrcPacsData {
+ uint16_t src_pac_handle;
+ uint16_t src_pac_ccc_handle;
+ std::vector<CodecConfig> 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::vector<SinkPacsData>sink_info;
+ std::vector<SrcPacsData>src_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<CodecConfig> consolidated_sink_pac_records;
+ std::vector<CodecConfig> 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<ProfileOperation> profile_queue;
+ std::vector<uint16_t> 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<DevState>(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<PacsDevice> 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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(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<uint8_t> 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<gatt::Service>* 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<CodecConfig> 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<uint8_t> codec_caps;
+ uint8_t meta_data_len;
+ std::vector<uint8_t> 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<int> (sizeof(codec_id)));
+
+ processed_len += static_cast<int> (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<CodecChannelMode> (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<int> (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<uint16_t, PacsClientCallbacks *> 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<CodecConfig>(*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<CodecConfig>(*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 000000000..2d535fa2a
--- /dev/null
+++ b/le_audio/system/bt/bta/bap/ucast_client_int.h
@@ -0,0 +1,1276 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 <string>
+#include "state_machine.h"
+#include <list>
+#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<RawAddress> bd_addr;
+ bool is_direct;
+ std::vector<StreamConnect> streams;
+};
+
+struct BapDisconnect {
+ RawAddress bd_addr;
+ std::vector<StreamType> streams;
+};
+
+struct BapStart {
+ RawAddress bd_addr;
+ std::vector<StreamType> streams;
+};
+
+struct BapStop {
+ RawAddress bd_addr;
+ std::vector<StreamType> streams;
+};
+
+struct BapReconfig {
+ RawAddress bd_addr;
+ std::vector<StreamReconfig> streams;
+};
+
+struct BapStreamUpdate {
+ RawAddress bd_addr;
+ std::vector<StreamUpdate> 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<AseParams> sink_ases_list;
+ std::vector<AseParams> src_ases_list;
+};
+
+struct AscsState {
+ RawAddress bd_addr;
+ AseParams ase_params;
+};
+
+struct AscsOpFailed {
+ RawAddress bd_addr;
+ ascs::AseOpId ase_op_id;
+ std::vector<ascs::AseOpStatus> 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<CodecConfig> sink_pac_records;
+ std::vector<CodecConfig> 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<IntStrmTracker *> FindByCigId(uint8_t cig_id) {
+ std::vector<IntStrmTracker *> 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<IntStrmTracker *> FindByCigIdAndDir(uint8_t cig_id,
+ uint8_t direction) {
+ std::vector<IntStrmTracker *> 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<IntStrmTracker *> FindByCisId(uint8_t cig_id, uint8_t cis_id) {
+ std::vector<IntStrmTracker *> 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<IntStrmTracker *> *GetTrackerList() {
+ return &int_strm_trackers;
+ }
+
+ std::vector<IntStrmTracker *> GetTrackerListByDir(uint8_t direction) {
+ std::vector<IntStrmTracker *> 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<IntStrmTracker *> 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<IntStrmTracker *> *all_trackers,
+ std::vector<UcastAudioStream *> *streams,
+ uint8_t cis_count,
+ std::vector<AseCodecConfigOp> *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<StreamConnect> *connect_streams,
+ std::vector<StreamReconfig> *reconfig_streams,
+ std::vector<StreamType> *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<CodecQosConfig> *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<AseCodecConfigOp> *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<StreamType> *streams) {
+ other_streams = *streams;
+ return true;
+ }
+
+ bool UpdateConnStreams(
+ std::vector<StreamConnect> *connect_streams) {
+ conn_streams = *connect_streams;
+ return true;
+ }
+
+ bool UpdateReconfStreams(
+ std::vector<StreamReconfig> *reconfig_streams) {
+ reconf_streams = *reconfig_streams;
+ return true;
+ }
+
+ bool UpdateMetaUpdateStreams(
+ std::vector<StreamUpdate> *meta_streams) {
+ meta_update_streams = *meta_streams;
+ return true;
+ }
+
+ std::vector<StreamType> *GetStreams() {
+ return &other_streams;
+ }
+ std::vector<StreamConnect> *GetConnStreams() {
+ return &conn_streams;
+ }
+ std::vector<StreamReconfig> *GetReconfStreams() {
+ return &reconf_streams;
+ }
+
+ std::vector<StreamUpdate> *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<StreamConnect> conn_streams;
+ std::vector<StreamType> other_streams;
+ std::vector<StreamReconfig> reconf_streams;
+ std::vector<StreamUpdate> 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<CodecConfig> codec_configs;
+ //std::vector<QosConfig> qos_configs;
+};
+
+struct StreamOpReconfig {
+ StreamType stream_type;
+ //std::vector<CodecConfig> codec_configs;
+ //std::vector<QosConfig> 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<StreamIdType> 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<StreamContext *> FindByAseAttachedState(uint16_t ase_id,
+ StreamAttachedState state);
+
+
+ StreamContext* FindByAseId(uint16_t ase_id);
+
+ std::vector<StreamContext *> *GetAllContexts() {
+ return &strm_contexts;
+ }
+
+ size_t size() { return (strm_contexts.size()); }
+
+ private:
+ std::vector<StreamContext *> 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<StreamOpNode> queue;
+};
+
+class StreamTrackers {
+ public:
+ StreamTracker *FindOrAddByType(int init_state_id, UstreamManager *strm_mgr,
+ std::vector<StreamConnect> *connect_streams,
+ std::vector<StreamReconfig> *reconfig_streams,
+ std::vector<StreamType> *streams,
+ StreamControlType ops_type);
+
+ bool Remove(std::vector<StreamType> streams,
+ StreamControlType ops_type);
+
+ void RemoveByStates(std::vector<int> state_ids);
+
+ std::map<StreamTracker * , std::vector<StreamType> > GetTrackersByType(
+ std::vector<StreamType> *streams);
+
+ StreamTracker *FindByStreamsType(std::vector<StreamType> *streams);
+
+ std::vector<StreamTracker *> GetTrackersByStates(
+ std::vector<int> *state_ids);
+
+ bool ChangeOpType( StreamType stream_type,
+ StreamControlType new_ops_type);
+
+ bool IsStreamTrackerValid(StreamTracker* Tracker,
+ std::vector<int> *state_ids);
+
+ size_t size() { return (stream_trackers.size()); }
+
+ std::vector<StreamTracker *> 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<UcastAudioStream *> FindByCigId(uint8_t cig_id, int state) {
+ std::vector<UcastAudioStream *> 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<UcastAudioStream *> FindByCisId(uint8_t cig_id, uint8_t cis_id) {
+ std::vector<UcastAudioStream *> 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<UcastAudioStream *> GetStreamsByStates(
+ std::vector<int> state_ids,
+ uint8_t directions) {
+ std::vector<UcastAudioStream *> 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<UcastAudioStream *> *GetAllStreams() {
+ return &audio_streams;
+ }
+
+ size_t size() { return (audio_streams.size()); }
+ // UcastAudioStream
+ private:
+ std::vector<UcastAudioStream *> 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<int> *state_ids);
+
+ std::map<int , std::vector<StreamType> > SplitContextOnState(
+ std::vector<StreamType> *streams);
+
+ void ProcessEvent(uint32_t event, void* p_data);
+
+ uint16_t GetConnId();
+
+ std::list<uint16_t> GetCigId();
+
+ std::list<uint16_t> GetCisId();
+
+ void ReportStreamState (std::vector<StreamStateInfo> 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<UstreamManager *> *GetAllManagers();
+
+ void Remove(const RawAddress& address);
+
+ size_t size() { return (strm_mgrs.size()); }
+
+ std::vector<UstreamManager *> 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 000000000..cca6efac3
--- /dev/null
+++ b/le_audio/system/bt/bta/bap/uclient_alarm.cc
@@ -0,0 +1,99 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 "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 000000000..3e03d94b5
--- /dev/null
+++ b/le_audio/system/bt/bta/bap/uclient_alarm.h
@@ -0,0 +1,63 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 <stdbool.h>
+#include <stdint.h>
+#include "osi/include/alarm.h"
+#include <raw_address.h>
+
+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 000000000..0df1c39f6
--- /dev/null
+++ b/le_audio/system/bt/bta/bap/uclient_main.cc
@@ -0,0 +1,548 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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_bap_uclient_api.h"
+#include "ucast_client_int.h"
+#include "bta_pacs_client_api.h"
+#include "bta_ascs_client_api.h"
+#include <hardware/bt_pacs_client.h>
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/logging.h>
+#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<CodecConfig> sink_pac_records,
+ std::vector<CodecConfig> 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<AseOpStatus> 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<AseParams> sink_ase_list,
+ std::vector<AseParams> 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<RawAddress> address, bool is_direct,
+ std::vector<StreamConnect> 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<StreamType> 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<StreamType> 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<StreamType> 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<StreamReconfig> 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<StreamUpdate> 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<CodecConfig> sink_pac_records,
+ std::vector<CodecConfig> 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<AseOpStatus> 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<AseParams> sink_ase_list,
+ std::vector<AseParams> 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<UstreamManager *> *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<UstreamManager *> *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 000000000..ea069379a
--- /dev/null
+++ b/le_audio/system/bt/bta/bap/uclient_strm_mgr.cc
@@ -0,0 +1,1013 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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_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<StreamState, int> 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<StreamContext *> StreamContexts::FindByAseAttachedState(
+ uint16_t ase_id, StreamAttachedState state) {
+ std::vector<StreamContext *> contexts_list;
+ for(auto i = strm_contexts.begin(); i != strm_contexts.end();i++) {
+ if(static_cast<uint8_t>((*i)->attached_state) &
+ static_cast<uint8_t>(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<StreamConnect> *connect_streams,
+ std::vector<StreamReconfig> *reconfig_streams,
+ std::vector<StreamType> *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<StreamConnect> *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<StreamReconfig> *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<StreamType> *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<StreamType> streams,
+ StreamControlType ops_type) {
+ return true;
+}
+
+std::vector<StreamTracker *> StreamTrackers::GetTrackersByStates(
+ std::vector<int> *state_ids) {
+ vector<StreamTracker *> 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<int> 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<StreamTracker * , std::vector<StreamType> >
+ StreamTrackers::GetTrackersByType(
+ std::vector<StreamType> *streams) {
+ std::vector<StreamType> req_types = *streams;
+ std::vector<StreamType> all_types;
+ std::map<StreamTracker * , std::vector<StreamType> > 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<StreamConnect> *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<StreamReconfig> *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<StreamUpdate> *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<StreamType> *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<StreamType> 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<StreamType> *streams) {
+ bool found = false;
+ auto iter = stream_trackers.begin();
+ while (iter != stream_trackers.end()) {
+ if((*iter)->GetControlType() == StreamControlType::Connect) {
+ // compare connection streams
+ std::vector<StreamConnect> *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<StreamReconfig> *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<StreamUpdate> *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<StreamType> *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<int> *state_ids) {
+ vector<StreamTracker *> 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<int> *state_ids) {
+ vector<StreamTracker *> 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<int , std::vector<StreamType> > UstreamManager::SplitContextOnState(
+ std::vector<StreamType> *streams) {
+ StreamContexts *contexts = GetStreamContexts();
+ std::vector<StreamType> req_types = *streams;
+ std::map<int , std::vector<StreamType> > 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<int> stable_state_ids = {
+ StreamTracker::kStateConnected,
+ StreamTracker::kStateStreaming,
+ StreamTracker::kStateIdle
+ };
+
+ std::vector<int> 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<StreamConnect> 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<StreamType> 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<StreamTracker * , std::vector<StreamType> >
+ 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<int , std::vector<StreamType> >
+ 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<StreamType> 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<StreamType> 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<StreamUpdate> update_streams = evt_data->update_streams;
+ std::vector<StreamType> 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<StreamReconfig> reconf_streams = evt_data->streams;
+ std::vector<StreamType> 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<StreamContext *> *context_list = contexts->GetAllContexts();
+ std::vector<StreamType> 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<StreamContext *> *context_list = contexts->GetAllContexts();
+ std::vector<StreamType> 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<StreamTracker * , std::vector<StreamType> >
+ 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<int , std::vector<StreamType> >
+ 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<int> 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<int> 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<StreamType> 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<int> 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<StreamType> streams;
+
+ UcastAudioStreams *audio_strms = GetAudioStreams();
+ std::vector<UcastAudioStream *> 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<UstreamManager *> *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 000000000..3272e5633
--- /dev/null
+++ b/le_audio/system/bt/bta/bap/uclient_strm_tracker.cc
@@ -0,0 +1,4001 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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_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<CodecSampleRate, uint8_t> 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<uint8_t> directions = {
+ cis::DIR_FROM_AIR,
+ cis::DIR_TO_AIR
+};
+
+std::vector<uint32_t> locations = {
+ AUDIO_LOC_LEFT,
+ AUDIO_LOC_RIGHT
+};
+
+// common functions used from Stream tracker state Handlers
+uint8_t StreamTracker::ChooseBestCodec(StreamType stream_type,
+ std::vector<CodecQosConfig> *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<CodecConfig> *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<UcastAudioStream *> 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<StreamUpdate> *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<uint64_t>(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<UcastAudioStream *> *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<StreamType> streams;
+
+ switch(cur_state) {
+ case StreamTracker::kStateConnecting: {
+ std::vector<StreamConnect> *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<StreamReconfig> *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<uint8_t> (StreamAttachedState::PHYSICAL) |
+ static_cast<uint8_t> (StreamAttachedState::IDLE_TO_PHY) |
+ static_cast<uint8_t> (StreamAttachedState::VIR_TO_PHY));
+
+ std::vector<StreamContext *> 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<StreamType> streams;
+
+ int cur_state = StateId();
+ LOG(WARNING) << __func__ <<": cur_state: " << cur_state
+ <<", release: " << release;
+ switch(cur_state) {
+ case StreamTracker::kStateConnecting: {
+ std::vector<StreamConnect> *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<StreamReconfig> *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<AseReleaseOp> ase_ops;
+ std::vector<StreamType> *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<StreamType> 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<ascs::AseOpStatus> *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<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;
+ }
+
+ 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<IntStrmTracker *> *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<IntStrmTracker *> *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<AseUpdateMetadataOp> ase_meta_ops;
+ std::vector<StreamUpdate> *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<uint8_t> 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 <uint8_t> (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<AseDisableOp> ase_ops;
+ std::vector<StreamType> *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<AseReleaseOp> ase_ops;
+ std::vector<StreamType> *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<IntStrmTracker *> *all_trackers =
+ int_strm_trackers->GetTrackerList();
+
+ std::vector<AseQosConfigOp> 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<StreamType> *start_streams = GetStreams();
+ StreamContexts *contexts = strm_mgr_->GetStreamContexts();
+ std::vector<AseEnableOp> ase_ops;
+ // check for all trackers CIG is created or not
+ // if so proceed with QOS config operaiton
+ std::vector<IntStrmTracker *> *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<uint8_t> 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 <uint8_t> (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<IntStrmTracker *> 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<AseCodecConfigOp> *ase_ops,
+ UcastAudioStream *stream) {
+ std::vector<uint8_t> 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 <uint8_t> (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<int>(timeout->reason);
+
+ if (timeout->reason == TimeoutReason::STATE_TRANSITION) {
+ if (stream_tracker_id == StreamTracker::kStateConnecting) {
+ StreamContexts *contexts = strm_mgr_->GetStreamContexts();
+ std::vector<StreamConnect> *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<int>(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<StreamStateInfo> strms;
+ StreamControlType control_type = tracker_.GetControlType();
+
+ if(control_type != StreamControlType::Disconnect &&
+ control_type != StreamControlType::Connect) {
+ return;
+ }
+
+ if(control_type == StreamControlType::Disconnect) {
+ std::vector<StreamType> *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<StreamConnect> *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<StreamStateInfo> strms;
+ std::vector<StreamConnect> *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<uint64_t>(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<CodecConfig> *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<uint16_t> (dst->channel_mode) &
+ static_cast<uint16_t> (CodecChannelMode::CODEC_CHANNEL_MODE_STEREO)) {
+ max_chnl_count = 2;
+ } else if(!max_chnl_count &&
+ static_cast<uint16_t> (dst->channel_mode) &
+ static_cast<uint16_t> (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<IntStrmTracker *> *all_trackers,
+ std::vector<UcastAudioStream *> *streams,
+ uint8_t cis_count,
+ std::vector<AseCodecConfigOp> *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<StreamConnect> *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<AseReleaseOp> ase_ops;
+ std::vector<StreamType> *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<int>(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<uint64_t>(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<AseCodecConfigOp> ase_ops;
+ AscsClient *ascs_client = strm_mgr_->GetAscsClient();
+ std::vector<AseParams> sink_ase_list = ascs_discovery_.sink_ases_list;
+ std::vector<AseParams> 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<IntStrmTracker *> sink_int_trackers =
+ int_strm_trackers_.GetTrackerListByDir(ASE_DIRECTION_SINK);
+
+ std::vector<IntStrmTracker *> src_int_trackers =
+ int_strm_trackers_.GetTrackerListByDir(ASE_DIRECTION_SRC);
+
+ std::vector<int> state_ids = { StreamTracker::kStateIdle };
+
+ std::vector<UcastAudioStream *> idle_sink_streams =
+ audio_strms->GetStreamsByStates(state_ids,
+ ASE_DIRECTION_SINK);
+ std::vector<UcastAudioStream *> 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<int> 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<IntStrmTracker *> 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<UcastAudioStream *> 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<IntStrmTracker *> 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<UcastAudioStream *> 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<StreamStateInfo> strms;
+ std::vector<StreamConfigInfo> stream_configs;
+ UcastAudioStreams *audio_strms = strm_mgr_->GetAudioStreams();
+ PacsDiscovery *pacs_discovery_ = tracker_.GetPacsDiscovery();
+ StreamControlType control_type = tracker_.GetControlType();
+ std::vector<StreamType> conv_streams;
+
+ if(control_type != StreamControlType::Connect &&
+ control_type != StreamControlType::Stop &&
+ control_type != StreamControlType::Reconfig) {
+ return;
+ }
+
+ if(control_type == StreamControlType::Connect) {
+ std::vector<StreamConnect> *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<StreamReconfig> *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<StreamStateInfo> strms;
+ std::vector<StreamType> *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<uint64_t>(TimeoutVal::StartingTimeout));
+ if(!tout) {
+ tout = static_cast<uint64_t>(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<IntStrmTracker *> *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 <uint8_t>(stream->cis_pending_cmd));
+ LOG(WARNING) << __func__ << ": ase_pending_cmd "
+ << loghex(static_cast <uint8_t>(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<IntStrmTracker *> *all_trackers =
+ int_strm_trackers_.GetTrackerList();
+ bool ignore_event = false;
+
+ std::vector<StreamType> *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<uint64_t>(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<uint8_t>(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<IntStrmTracker *> *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<uint8_t> 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<IntStrmTracker *> 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 <uint8_t> (cis::DIR_FROM_AIR));
+
+ if(stream && int_strm_trackers_.FindByAseId(stream->ase_id)) {
+ LOG(INFO) << __func__ << ": Stream Direction "
+ << loghex(static_cast <uint8_t> (stream->direction));
+
+ LOG(INFO) << __func__ << ": Stream ASE Id "
+ << loghex(static_cast <uint8_t> (stream->ase_id));
+
+ AscsClient *ascs_client = strm_mgr_->GetAscsClient();
+ AseStartReadyOp start_ready_op = {
+ .ase_id = stream->ase_id
+ };
+ std::vector<AseStartReadyOp> 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<uint8_t> 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<StreamStateInfo> strms;
+ std::vector<StreamUpdate> *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<uint64_t>(TimeoutVal::UpdatingTimeout));
+ if(!tout) {
+ tout = static_cast<uint64_t>(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<StreamStateInfo> strms;
+ std::vector<StreamType> *start_streams = tracker_.GetStreams();
+ std::vector<StreamUpdate> *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<StreamStateInfo> strms;
+ std::vector<StreamType> *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<uint64_t>(TimeoutVal::StoppingTimeout));
+ if(!tout) {
+ tout = static_cast<uint64_t>(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<IntStrmTracker *> 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<IntStrmTracker *> *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<AseStopReadyOp> 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<IntStrmTracker *> 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<IntStrmTracker *> 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<StreamContext *> *all_contexts = contexts->GetAllContexts();
+ bool any_context_active = false;
+ bool disc_issued = false;
+ std::vector<int> ids = { StreamTracker::kStateIdle };
+ std::vector<UcastAudioStream *> 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<StreamStateInfo> strms;
+ uint8_t num_ases = 0;
+
+ std::vector<StreamType> *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<uint64_t>(TimeoutVal::DisconnectingTimeout));
+ if(!tout ||tout > static_cast<uint64_t>(MaxTimeoutVal::DisconnectingTimeout)) {
+ tout = static_cast<uint64_t>(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<IntStrmTracker *> *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<UcastAudioStream *> *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<AseReleaseOp> 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<ascs::AseOpStatus> *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<IntStrmTracker *> 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<IntStrmTracker *> 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 <BapTimeout *> (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<int>(timeout->reason);
+
+ std::vector<IntStrmTracker *> *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<StreamStateInfo> strms;
+ std::vector<StreamReconfig> *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<uint64_t>(TimeoutVal::ReconfiguringTimeout));
+ if(!tout ||tout > static_cast<uint64_t>(MaxTimeoutVal::ReconfiguringTimeout)){
+ tout = static_cast<uint64_t>(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<StreamReconfig> *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<uint64_t>(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<StreamContext *> phy_attached_contexts;
+ for (auto id = context->stream_ids.begin();
+ id != context->stream_ids.end(); id++) {
+ std::vector<StreamContext *> 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<IntStrmTracker *> *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<AseCodecConfigOp> 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 000000000..eedebcc9f
--- /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 <vector>
+#include <iostream>
+#include <string.h>
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/location.h>
+#include <hardware/bluetooth.h>
+
+#include <base/strings/string_number_conversions.h>
+
+#include <vector>
+#include <string.h>
+#include <algorithm>
+#include <map>
+
+#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<uint8_t, tCCS_CALL_STATE> CallStatelist;
+std::map<uint8_t, tCCS_BEARER_LIST_CURRENT_CALLS> 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<void(uint8_t status, int server_if,
+ std::vector<btgatt_db_element_t> service)>
+ OnGtbsServiceAdded;
+
+static void OnGtbsServiceAddedCb(uint8_t status, int serverIf,
+ std::vector<btgatt_db_element_t> 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<CallControllerDeviceList> GetRemoteDevices() {
+ return devices;
+ }
+ std::vector<CallControllerDeviceList> FindNotifyDevices(uint16_t handle) {
+ std::vector<CallControllerDeviceList> 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<uint8_t> _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<CallControllerDeviceList> 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<uint8_t> call_state_list) {
+ tCCS_CALL_STATE CallStateOp;
+ for (int k=0; k<len; k++) {
+ LOG(INFO) << __func__ << " " << k <<" : index: " << (unsigned)call_state_list[3*k + 0]
+ << " state: " << (unsigned) call_state_list[3*k + 1]
+ << " flags: " << (unsigned) call_state_list[3*k + 2];
+ CallStateOp.index = call_state_list[3*k + 0];
+ CallStateOp.state = call_state_list[3*k + 1];
+ CallStateOp.flags = call_state_list[3*k + 2];
+
+ BlccInfo.call_flags = CallStateOp.flags;
+ BlccInfo.call_index = CallStateOp.index;
+ BlccInfo.call_state = CallStateOp.state;
+
+ if (CallStateOp.state == CCS_STATE_INCOMING) {
+ int len = strlen((char *)IncomingCallInfo.incoming_uri);
+ memcpy(BlccInfo.call_uri, IncomingCallInfo.incoming_uri, len);
+ BlccInfo.list_length = 3 + len;
+ } else {
+ BlccInfo.list_length = 3;
+ }
+
+ if (CallStateOp.state == CCS_STATE_DISCONNECTED) {
+ //clear off the Incoming call related things as well
+ if (IncomingCallInfo.index == CallStateOp.index) {
+ IncomingCallInfo.index = 0;
+ memset(IncomingCallInfo.incoming_uri, 0, MAX_URI_LENGTH);
+ }
+ if (IncomingCallTargetUri.index == CallStateOp.index) {
+ IncomingCallTargetUri.index = 0;
+ memset(IncomingCallTargetUri.incoming_target_uri, 0, MAX_URI_LENGTH);
+ }
+
+ TerminationReason.index = CallStateOp.index;
+ if (gIsTerminatedInitiatedFromClient &&
+ CallStateOp.index == gIsTerminatedInitiatedFromClient) {
+ TerminationReason.reason = CC_TERM_END_FROM_CLIENT;
+ } else {
+ TerminationReason.reason = CC_TERM_END_FROM_SERVER;
+ }
+ gIsTerminatedInitiatedFromClient = false;
+ gTerminateIntiatedIndex = 0;
+ HandleCcsEvent(CCS_INCOMING_CALL_UPDATE, &IncomingCallInfo);
+ HandleCcsEvent(CCS_INCOMING_TARGET_URI_UPDATE, &IncomingCallTargetUri);
+ HandleCcsEvent(CCS_TERMINATION_REASON_UPDATE, &TerminationReason);
+
+ //erase the disconnected Indicies
+ BlccInfolist.erase(CallStateOp.index);
+ CallStatelist.erase(CallStateOp.index);
+ } else {
+ //keep appending the data
+ std::map<uint8_t, tCCS_BEARER_LIST_CURRENT_CALLS>::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<uint8_t, tCCS_CALL_STATE>::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: " <<tech_type;
+ tCCS_BEARER_PROVIDER_INFO bearerProviderOp;
+ BearerProviderInfo.technology_type = tech_type;
+
+ HandleCcsEvent(CCS_BEARER_TECHNOLOGY_UPDATE, &bearerProviderOp);
+ }
+ void UpdateBearerSignalStrength(int signal) {
+ LOG(INFO) << __func__<< " signal: " << signal;
+ tCCS_BEARER_PROVIDER_INFO BearerProviderOp;
+ BearerProviderOp.signal = signal;
+ HandleCcsEvent(CCS_BEARER_SIGNAL_STRENGTH_UPDATE, &BearerProviderOp);
+ }
+
+ void UpdateStatusFlags(uint8_t status_flag) {
+ LOG(INFO) << __func__ << " status_flag: " <<status_flag;
+ tCCS_STATUS_FLAGS StatusFlagsOp;
+ StatusFlagsOp.supported_flags = status_flag;
+ HandleCcsEvent(CCS_STATUS_FLAGS_UPDATE, &StatusFlagsOp);
+ }
+
+ void CallControlOptionalOpSupported(int feature) {
+ LOG(INFO) << __func__ << " feature: " << feature;
+ tCCS_SUPP_OPTIONAL_OPCODES opSupportedFeatures;
+ opSupportedFeatures.supp_opcode = (uint16_t)0x00FF&feature;
+
+ HandleCcsEvent(CCS_OPT_OPCODES, &opSupportedFeatures);
+ }
+
+ void UpdateSupportedBearerList(uint8_t* list) {
+ LOG(INFO) << __func__ << " list: " <<list;
+ tCCS_BEARER_PROVIDER_INFO BearerProviderOp;
+ BearerProviderOp.bearer_list_len = strlen((char *)list);
+ LOG(INFO) << __func__ << " list len:" << BearerProviderOp.bearer_list_len;
+ memcpy(BearerProviderOp.bearer_schemes_list, list, BearerProviderOp.bearer_list_len+1);
+ HandleCcsEvent(CCS_BEARER_URI_SCHEMES_SUPPORTED, &BearerProviderOp);
+ }
+
+ void UpdateIncomingCall(int index, uint8_t* Uri) {
+ LOG(INFO) << __func__ << " Uri: " << Uri;
+ std::map<uint8_t, tCCS_BEARER_LIST_CURRENT_CALLS>::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<uint8_t, tCCS_CALL_STATE>::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: " <<op << " count :" << count;
+ std::vector<uint8_t> uri_data;
+ if (uri != NULL) {
+ LOG(INFO) << __func__ <<" uri=" << uri;
+ uri_data.insert(uri_data.begin(), uri, uri + strlen(uri));
+ }
+ std::vector<int32_t> call_indicies;
+ for (int i=0; i<count; i++) {
+ call_indicies.push_back(indices[i]);
+
+ }
+ callbacks->CallControlCallback(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<btgatt_db_element_t> CcAddService(int server_if) {
+
+ std::vector<btgatt_db_element_t> 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<btgatt_db_element_t> 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<len; i++) {
+ LOG(INFO) << __func__ << " data[" << i << "] = " << std::hex << std::setfill('0') << std::setw(2) << data[i] << std::endl;
+ }
+}
+
+void ReverseByteOrder(unsigned char s[], int length)
+{
+ char revbytes_enabled[PROPERTY_VALUE_MAX] = "false";
+ osi_property_get("persist.bluetooth.ccp_rev", revbytes_enabled, "false");
+ bool revNeeded = strncmp(revbytes_enabled, "true", 4) == 0;;
+ if (revNeeded) {
+ int tmp, i, j;
+
+ for (i = 0, j = length-1; i < j; i++, j--)
+ {
+ tmp = s[i];
+ s[i] = s[j];
+ s[j] = tmp;
+ }
+ }
+}
+
+void HandleCcsEvent(uint32_t event, void* param) {
+ LOG(INFO) << __func__ << " event: " << bta_cc_event_str(event);
+ tBTA_GATTS* p_data = NULL;
+ tcc_resp_t *rsp = new tcc_resp_t();
+ if (rsp == NULL) {
+ LOG(INFO) << __func__ << " ccs handle return rsp not allocated ";
+ return;
+ }
+ std::vector<uint8_t> _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<btgatt_db_element_t> 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";
+ //<TBD> 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<uint8_t> 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<uint8_t> loc_desc_value;
+ std::map<uint8_t, tCCS_CALL_STATE>::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: " <<rsp_struct.attr_value.len;
+ memcpy(rsp_struct.attr_value.value, BearerProviderInfo.name, rsp_struct.attr_value.len);
+ } else if(p_data->req_data.p_data->read_req.handle ==
+ ccsControlServiceInfo.bearer_list_currentcalls_handle) {
+
+ std::vector<uint8_t> loc_desc_value;
+ std::map<uint8_t, tCCS_BEARER_LIST_CURRENT_CALLS>::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<uint8_t> 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<uint8_t, tCCS_CALL_STATE>::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<uint8_t, tCCS_BEARER_LIST_CURRENT_CALLS>::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<uint8_t, tCCS_CALL_STATE>::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<uint8_t, tCCS_BEARER_LIST_CURRENT_CALLS>::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 " <<BearerProviderInfo.bearer_list_len;
+ ReverseByteOrder(data->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: " <<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 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<std::string> out;
+ char *token;
+ char* rest = const_cast<char*>(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::vector<CallControllerDeviceList>notifyDevices = cc_instance->remoteDevices.FindNotifyDevices(p_data->handle);
+ std::vector<CallControllerDeviceList>::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<uint8_t, tCCS_CALL_STATE>::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<count; i++) {
+ indices[i] = start_of_indicies[i];
+ LOG(INFO) << __func__ << " Indicies: " << i << ": " <<indices[i];
+ std::map<uint8_t, tCCS_CALL_STATE>::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 000000000..ff83083b7
--- /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 <log/log.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <vector>
+#include <string>
+
+#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<tBTA_CSIP_DEV_CB> &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<RawAddress> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<RawAddress> 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<gatt::Service>* 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<gatt::Service>* 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 000000000..73a9054c9
--- /dev/null
+++ b/le_audio/system/bt/bta/csip/bta_csip_api.cc
@@ -0,0 +1,287 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.
+ *
+ ******************************************************************************/
+
+
+/******************************************************************************
+ *
+ * This file contains the CSIP API in the subsystem of BTA.
+ *
+ ******************************************************************************/
+
+#define LOG_TAG "bt_bta_csip"
+
+#include <base/bind.h>
+#include <base/bind_helpers.h>
+#include <base/callback.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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<tBTIF_CSIP_CSET>: (all discovered coordinated set)
+ *
+ *********************************************************************************/
+std::vector<tBTA_CSIP_CSET> 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 000000000..5000bbf6b
--- /dev/null
+++ b/le_audio/system/bt/bta/csip/bta_csip_int.h
@@ -0,0 +1,321 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.
+ *
+ ******************************************************************************/
+
+
+/******************************************************************************
+ *
+ * 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<uint8_t> lock_applist; /* Apps those have locked this set */
+ std::vector<uint8_t> unrsp_applist; /* Apps to which unresponsive res is sent */
+ std::vector<uint8_t> 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<uint8_t> 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<RawAddress> 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<uint8_t, RawAddress> ordered_members;
+ std::queue<tBTA_SET_LOCK_PARAMS> 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<tBTA_CSIP_DEV_CB> dev_cb; /* device control block */
+ tBTA_CSIP_RCB app_rcb[BTA_CSIP_MAX_SUPPORTED_APPS];
+ std::vector<tBTA_CSIP_CSET> 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<RawAddress> bta_csip_arrange_set_members_by_order(
+ uint8_t set_id, std::vector<RawAddress>& 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<RawAddress> 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 000000000..87111f19b
--- /dev/null
+++ b/le_audio/system/bt/bta/csip/bta_csip_main.cc
@@ -0,0 +1,296 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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_csip_int.h"
+
+#include <string.h>
+
+#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 000000000..b900c3abb
--- /dev/null
+++ b/le_audio/system/bt/bta/csip/bta_csip_utils.cc
@@ -0,0 +1,1318 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.
+ *
+ ******************************************************************************/
+
+
+/******************************************************************************
+ *
+ * This file contains the CSIP Client supporting functions
+ *
+ ******************************************************************************/
+
+#include <log/log.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <vector>
+
+#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<tBTA_CSIP_CSET> *csets = &bta_csip_cb.csets;
+ tBTA_CSIP_CSET cset;
+ bool is_valid_set = false;
+
+ std::vector<tBTA_CSIP_CSET>::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<RawAddress> 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<RawAddress> 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<uint8_t> &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<RawAddress> bta_csip_form_set_lock_order(tBTA_CSET_CB* cset_cb) {
+ std::vector<RawAddress> ordered_members;
+ std::vector<RawAddress> req_members = cset_cb->cur_lock_req.members_addr;
+ std::map<uint8_t, RawAddress> 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<RawAddress> bta_csip_arrange_set_members_by_order(
+ uint8_t set_id, std::vector<RawAddress>& req_sm, bool ascending) {
+ LOG(INFO) << __func__;
+
+ std::vector<RawAddress> ordered_req_sm;
+ std::vector<RawAddress> 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<RawAddress> bta_csip_get_set_member_by_order(uint8_t set_id,
+ bool ascending) {
+ std::vector<RawAddress> 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<uint8_t>& 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<uint8_t>& 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<RawAddress> 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<tBTA_SET_LOCK_PARAMS>& 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<uint8_t> 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 000000000..4ff1ea40f
--- /dev/null
+++ b/le_audio/system/bt/bta/dm/bta_dm_adv_audio.cc
@@ -0,0 +1,1186 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 "bt_bta_dm_adv_audio"
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/logging.h>
+#include <string.h>
+
+#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 <inttypes.h>
+#include "btif/include/btif_config.h"
+#include "device/include/device_iot_config.h"
+#include <btcommon_interface_defs.h>
+#include <controller.h>
+#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<bluetooth::Uuid> 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<gatt::Service>* 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<bluetooth::Uuid>::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<bluetooth::Uuid>::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<bluetooth::Uuid>::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<bluetooth::Uuid>::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<bluetooth::Uuid>::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<bluetooth::Uuid>::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<gatt::Service>* 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<gatt::Service>* 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 000000000..5bcf48e5c
--- /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 <string>
+#include <hardware/bt_ascs_client.h>
+
+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<AseCodecConfigOp> codec_configs);
+
+ virtual void QosConfig(uint16_t client_id, const RawAddress& address,
+ std::vector<AseQosConfigOp> qos_configs);
+
+ virtual void Enable(uint16_t client_id, const RawAddress& address,
+ std::vector<AseEnableOp> enable_ops);
+
+ virtual void Disable(uint16_t client_id, const RawAddress& address,
+ std::vector<AseDisableOp> disable_ops);
+
+ virtual void StartReady(uint16_t client_id, const RawAddress& address,
+ std::vector<AseStartReadyOp> start_ready_ops);
+
+ virtual void StopReady(uint16_t client_id, const RawAddress& address,
+ std::vector<AseStopReadyOp> stop_ready_ops);
+
+ virtual void Release(uint16_t client_id, const RawAddress& address,
+ std::vector<AseReleaseOp> release_ops);
+
+ virtual void UpdateStream(uint16_t client_id, const RawAddress& address,
+ std::vector<AseUpdateMetadataOp> 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 000000000..33bd53bf1
--- /dev/null
+++ b/le_audio/system/bt/bta/include/bta_bap_uclient_api.h
@@ -0,0 +1,67 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 <string>
+#include "connected_iso_api.h"
+#include <hardware/bt_bap_uclient.h>
+#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<RawAddress> address, bool is_direct,
+ std::vector<StreamConnect> streams) = 0;
+ virtual void Disconnect(const RawAddress& address,
+ std::vector<StreamType> streams) = 0;
+ virtual void Start(const RawAddress& address,
+ std::vector<StreamType> streams) = 0;
+ virtual void Stop(const RawAddress& address,
+ std::vector<StreamType> streams) = 0;
+ virtual void Reconfigure(const RawAddress& address,
+ std::vector<StreamReconfig> streams) = 0;
+ virtual void UpdateStream(const RawAddress& address,
+ std::vector<StreamUpdate> 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 000000000..f7dbd6929
--- /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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "bta_gatt_api.h"
+#include <hardware/bluetooth_callcontrol_callbacks.h>
+#include <hardware/bluetooth_callcontrol_interface.h>
+
+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<RawAddress> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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 000000000..4efb422b4
--- /dev/null
+++ b/le_audio/system/bt/bta/include/bta_csip_api.h
@@ -0,0 +1,396 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.
+ *
+ ******************************************************************************/
+
+
+/******************************************************************************
+ *
+ * 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 <bluetooth/uuid.h>
+#include <raw_address.h>
+
+#include "bta_gatt_api.h" //temp
+
+#include <vector>
+
+#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<RawAddress> 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<RawAddress> 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<RawAddress> 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<RawAddress> set_members;
+ bluetooth::Uuid p_srvc_uuid;
+} tBTA_CSIP_CSET;
+
+using BtaCsipAppRegisteredCb =
+ base::Callback<void(uint8_t /* status */, uint8_t /* app_id */)>;
+
+/*********************************************************************************
+ *
+ * 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<tBTIF_CSIP_CSET>: (all discovered coordinated set)
+ *
+ *********************************************************************************/
+extern std::vector<tBTA_CSIP_CSET> 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 000000000..d7fe588ae
--- /dev/null
+++ b/le_audio/system/bt/bta/include/bta_dm_adv_audio.h
@@ -0,0 +1,152 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 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 <memory>
+#include <map>
+#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<bluetooth::Uuid> 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 <RawAddress, RawAddress> dev_addr_map;
+ std::map <RawAddress, RawAddress> 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 000000000..de6dfc77d
--- /dev/null
+++ b/le_audio/system/bt/bta/include/bta_mcp_api.h
@@ -0,0 +1,489 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 BTA_MCP_API_H
+#define BTA_MCP_API_H
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <hardware/bt_mcp.h>
+#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<RawAddress> 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 000000000..1a364d8dc
--- /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 <string>
+#include <hardware/bt_pacs_client.h>
+
+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 000000000..8518262a9
--- /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 <base/callback_forward.h>
+#include <hardware/bt_vcp_controller.h>
+#include <deque>
+#include <future>
+#include <vector>
+
+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 000000000..74e16ccfc
--- /dev/null
+++ b/le_audio/system/bt/bta/include/connected_iso_api.h
@@ -0,0 +1,111 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 <string>
+#include <vector>
+
+#include "stack/include/bt_types.h"
+#include <hardware/bt_bap_uclient.h>
+
+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<CISConfig> &cis_configs) = 0;
+
+ virtual IsoHciStatus RemoveCig(RawAddress peer_bda,
+ uint8_t cig_id) = 0;
+
+ virtual IsoHciStatus CreateCis(uint8_t cig_id, std::vector<uint8_t> 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 000000000..b920ec25f
--- /dev/null
+++ b/le_audio/system/bt/bta/mcp/bta_mcp_main.cc
@@ -0,0 +1,3089 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.
+ *
+ ******************************************************************************/
+
+
+
+
+/******************************************************************************
+ *
+ * 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 <base/bind.h>
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/location.h>
+#include <hardware/bluetooth.h>
+#include <base/strings/string_number_conversions.h>
+#include <vector>
+#include <string.h>
+
+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<void(uint8_t status, int server_if,
+ std::vector<btgatt_db_element_t> service)>
+ OnMcpServiceAdded;
+
+static void OnMcpServiceAddedCb(uint8_t status, int serverIf,
+ std::vector<btgatt_db_element_t> 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<RemoteDevice> GetRemoteDevices() {
+ return devices;
+ }
+ std::vector<RemoteDevice> FindNotifyDevices(uint16_t handle) {
+ std::vector<RemoteDevice> 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<RemoteDevice> 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<btgatt_db_element_t> McpAddService(int server_if) {
+
+ std::vector<btgatt_db_element_t> 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<btgatt_db_element_t> 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<btgatt_db_element_t> 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 " <<p_data->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";
+ //<TBD> 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";
+ //<TBD> 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:
+ //<TBD> not required
+ break;
+
+ case BTA_GATTS_DELELTE_EVT:
+ //<TBD> not required
+ break;
+
+ case BTA_GATTS_READ_CHARACTERISTIC_EVT: {
+ p_data = (tBTA_GATTS*)param;
+ std::vector<uint8_t> 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.
+ //<TBD>
+ 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; //<TBD> 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; //<TBD>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;
+ //<TBD> 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::vector<RemoteDevice>notifyDevices = instance->remoteDevices.FindNotifyDevices(p_data->handle);
+ std::vector<RemoteDevice>::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<uint8_t> 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<uint8_t> 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::vector<RemoteDevice>notifyDevices = instance->remoteDevices.FindNotifyDevices(p_data->handle);
+ std::vector<RemoteDevice>::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<uint8_t> 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<uint8_t> 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::vector<RemoteDevice>notifyDevices =
+ instance->remoteDevices.FindNotifyDevices(p_data->handle);
+ if (notifyDevices.size() <= 0) {
+ LOG(INFO) << __func__ << " No device register for notification";
+ break;
+ }
+ std::vector<RemoteDevice>::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<uint8_t> 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<uint8_t> 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::vector<RemoteDevice>notifyDevices = instance->remoteDevices.FindNotifyDevices(p_data->handle);
+ std::vector<RemoteDevice>::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<uint8_t> 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) {
+ //<TBD> 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<uint8_t> 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<uint8_t> 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 000000000..5eb7d6c56
--- /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 <base/bind.h>
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <hardware/bt_vcp_controller.h>
+#include <vector>
+
+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<RendererDevice> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<gatt::Service>* 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<uint8_t> 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<uint8_t> 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 000000000..cb7bd022f
--- /dev/null
+++ b/le_audio/system/bt/btif/Android.bp
@@ -0,0 +1,107 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_bt_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_bt_license"],
+}
+
+cc_defaults {
+ name: "fluoride_btif_defaults_qti_adva",
+ defaults: ["fluoride_defaults"],
+ include_dirs: [
+ "vendor/qcom/opensource/commonsys/system/bt",
+ "vendor/qcom/opensource/commonsys/system/bt/bta/include",
+ "vendor/qcom/opensource/commonsys/system/bt/bta/ag",
+ "vendor/qcom/opensource/commonsys/system/bt/btcore/include",
+ "vendor/qcom/opensource/commonsys/system/bt/hci/include",
+ "vendor/qcom/opensource/commonsys/system/bt/internal_include",
+ "vendor/qcom/opensource/commonsys/system/bt/stack/include",
+ "vendor/qcom/opensource/commonsys/system/bt/stack/btm",
+ "vendor/qcom/opensource/commonsys/system/bt/udrv/include",
+ "vendor/qcom/opensource/commonsys/system/bt/vnd/include",
+ "vendor/qcom/opensource/commonsys/system/bt/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/system/bt/device/include",
+ "vendor/qcom/opensource/commonsys/system/bt/btif/include",
+ "vendor/qcom/opensource/commonsys/system/bt/bta/include",
+ "vendor/qcom/opensource/commonsys/system/bt/bta/sys",
+ "system/bt/common",
+ "vendor/qcom/opensource/commonsys/bluetooth_lea/system/bt/btif/include",
+ "vendor/qcom/opensource/commonsys/bluetooth_lea/system/bt/bta/include",
+ "vendor/qcom/opensource/commonsys/bluetooth_lea/system/bt",
+ "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 000000000..5fcc4100d
--- /dev/null
+++ b/le_audio/system/bt/btif/include/bluetooth_adv_audio.h
@@ -0,0 +1,33 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.
+ *
+ ******************************************************************************/
+
+ *
+ * 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 000000000..c11dcb7ad
--- /dev/null
+++ b/le_audio/system/bt/btif/include/btif_acm.h
@@ -0,0 +1,250 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 BTIF_ACM_H
+#define BTIF_ACM_H
+
+#include <vector>
+
+//#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<CodecConfig> 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<CodecConfig>& codecs_local_capabilities,
+ const std::vector<CodecConfig>&
+ 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 000000000..8203a6578
--- /dev/null
+++ b/le_audio/system/bt/btif/include/btif_acm_source.h
@@ -0,0 +1,35 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.
+ *
+ ******************************************************************************/
+
+
+#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 000000000..f2bf96dc4
--- /dev/null
+++ b/le_audio/system/bt/btif/include/btif_bap_broadcast.h
@@ -0,0 +1,92 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.
+ *
+ ******************************************************************************/
+
+ *
+ * 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 000000000..e557268dc
--- /dev/null
+++ b/le_audio/system/bt/btif/include/btif_bap_codec_utils.h
@@ -0,0 +1,90 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 <stdbool.h>
+#include <stddef.h>
+
+#include <hardware/bt_pacs_client.h>
+#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 000000000..a83b8e9bb
--- /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 <stdbool.h>
+#include <stddef.h>
+
+#include <hardware/bt_pacs_client.h>
+#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<CodecConfig> *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 000000000..d28863311
--- /dev/null
+++ b/le_audio/system/bt/btif/include/btif_dm_adv_audio.h
@@ -0,0 +1,33 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 BTIF_DM_ADV_AUDIO_H
+#define BTIF_DM_ADV_AUDIO_H
+
+extern std::unordered_map<RawAddress, uint32_t> 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 000000000..7eb72bf5c
--- /dev/null
+++ b/le_audio/system/bt/btif/include/btif_vmcp.h
@@ -0,0 +1,147 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 <vector>
+#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<CodecConfig> get_all_codec_configs(uint8_t profile, uint8_t context);
+vector<CodecConfig> get_preferred_codec_configs(uint8_t profile, uint8_t context);
+
+vector<QoSConfig> get_all_qos_params(uint8_t profile, uint8_t context);
+vector<QoSConfig> 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 000000000..0971d6fbb
--- /dev/null
+++ b/le_audio/system/bt/btif/leaudio_configs.xml
@@ -0,0 +1,727 @@
+<!--
+
+/******************************************************************************
+ *
+ * Copyright 2021 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.
+ *
+ ******************************************************************************/
+
+-->
+<LEAudioConfig>
+<VMCPRoles> </VMCPRoles>
+
+<VMCP>
+ <CodecCapabilitiesForVoice>
+ <CodecSettings>
+ <SamplingFrequencyInHz>8000</SamplingFrequencyInHz>
+ <FrameDurationInMicroSecs>7500</FrameDurationInMicroSecs>
+ <OctetsPerCodecFrame>26</OctetsPerCodecFrame>
+ <Mandatory>0</Mandatory>
+ </CodecSettings>
+ <CodecSettings>
+ <SamplingFrequencyInHz>8000</SamplingFrequencyInHz>
+ <FrameDurationInMicroSecs>10000</FrameDurationInMicroSecs>
+ <OctetsPerCodecFrame>30</OctetsPerCodecFrame>
+ <Mandatory>0</Mandatory>
+ </CodecSettings>
+ <CodecSettings>
+ <SamplingFrequencyInHz>32000</SamplingFrequencyInHz>
+ <FrameDurationInMicroSecs>7500</FrameDurationInMicroSecs>
+ <OctetsPerCodecFrame>60</OctetsPerCodecFrame>
+ <Mandatory>0</Mandatory>
+ </CodecSettings>
+ <CodecSettings>
+ <SamplingFrequencyInHz>32000</SamplingFrequencyInHz>
+ <FrameDurationInMicroSecs>10000</FrameDurationInMicroSecs>
+ <OctetsPerCodecFrame>80</OctetsPerCodecFrame>
+ <Mandatory>1</Mandatory>
+ </CodecSettings>
+ </CodecCapabilitiesForVoice>
+
+ <CodecCapabilitiesForMedia>
+ <CodecSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <FrameDurationInMicroSecs>7500</FrameDurationInMicroSecs>
+ <OctetsPerCodecFrame>75</OctetsPerCodecFrame>
+ <Mandatory>0</Mandatory>
+ </CodecSettings>
+ <CodecSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <FrameDurationInMicroSecs>10000</FrameDurationInMicroSecs>
+ <OctetsPerCodecFrame>100</OctetsPerCodecFrame>
+ <Mandatory>1</Mandatory>
+ </CodecSettings>
+ <CodecSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <FrameDurationInMicroSecs>7500</FrameDurationInMicroSecs>
+ <OctetsPerCodecFrame>90</OctetsPerCodecFrame>
+ <Mandatory>0</Mandatory>
+ </CodecSettings>
+ <CodecSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <FrameDurationInMicroSecs>10000</FrameDurationInMicroSecs>
+ <OctetsPerCodecFrame>120</OctetsPerCodecFrame>
+ <Mandatory>0</Mandatory>
+ </CodecSettings>
+ <CodecSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <FrameDurationInMicroSecs>7500</FrameDurationInMicroSecs>
+ <OctetsPerCodecFrame>117</OctetsPerCodecFrame>
+ <Mandatory>0</Mandatory>
+ </CodecSettings>
+ <CodecSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <FrameDurationInMicroSecs>10000</FrameDurationInMicroSecs>
+ <OctetsPerCodecFrame>155</OctetsPerCodecFrame>
+ <Mandatory>1</Mandatory>
+ </CodecSettings>
+ </CodecCapabilitiesForMedia>
+
+ <QosSettingsForLowLatencyVoice>
+ <QoSSettings>
+ <SamplingFrequencyInHz>8000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>26</MaxSDUSize>
+ <RetransmissionNumber>2</RetransmissionNumber>
+ <MaxTransportLatency>8</MaxTransportLatency>
+ <PresentationDelay>20000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>8000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>30</MaxSDUSize>
+ <RetransmissionNumber>2</RetransmissionNumber>
+ <MaxTransportLatency>10</MaxTransportLatency>
+ <PresentationDelay>20000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>32000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>60</MaxSDUSize>
+ <RetransmissionNumber>2</RetransmissionNumber>
+ <MaxTransportLatency>8</MaxTransportLatency>
+ <PresentationDelay>20000</PresentationDelay>
+ <Mandatory>1</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>32000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>80</MaxSDUSize>
+ <RetransmissionNumber>2</RetransmissionNumber>
+ <MaxTransportLatency>10</MaxTransportLatency>
+ <PresentationDelay>20000</PresentationDelay>
+ <Mandatory>1</Mandatory>
+ </QoSSettings>
+ </QosSettingsForLowLatencyVoice>
+ <QosSettingsForLowLatencyMedia>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>75</MaxSDUSize>
+ <RetransmissionNumber>5</RetransmissionNumber>
+ <MaxTransportLatency>15</MaxTransportLatency>
+ <PresentationDelay>20000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>100</MaxSDUSize>
+ <RetransmissionNumber>5</RetransmissionNumber>
+ <MaxTransportLatency>20</MaxTransportLatency>
+ <PresentationDelay>20000</PresentationDelay>
+ <Mandatory>1</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>90</MaxSDUSize>
+ <RetransmissionNumber>5</RetransmissionNumber>
+ <MaxTransportLatency>15</MaxTransportLatency>
+ <PresentationDelay>20000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>120</MaxSDUSize>
+ <RetransmissionNumber>5</RetransmissionNumber>
+ <MaxTransportLatency>20</MaxTransportLatency>
+ <PresentationDelay>20000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>117</MaxSDUSize>
+ <RetransmissionNumber>5</RetransmissionNumber>
+ <MaxTransportLatency>15</MaxTransportLatency>
+ <PresentationDelay>20000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>155</MaxSDUSize>
+ <RetransmissionNumber>5</RetransmissionNumber>
+ <MaxTransportLatency>20</MaxTransportLatency>
+ <PresentationDelay>20000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ </QosSettingsForLowLatencyMedia>
+ <QosSettingsForHighReliabilityMedia>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>75</MaxSDUSize>
+ <RetransmissionNumber>23</RetransmissionNumber>
+ <MaxTransportLatency>45</MaxTransportLatency>
+ <PresentationDelay>20000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>100</MaxSDUSize>
+ <RetransmissionNumber>23</RetransmissionNumber>
+ <MaxTransportLatency>60</MaxTransportLatency>
+ <PresentationDelay>20000</PresentationDelay>
+ <Mandatory>1</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>90</MaxSDUSize>
+ <RetransmissionNumber>23</RetransmissionNumber>
+ <MaxTransportLatency>45</MaxTransportLatency>
+ <PresentationDelay>20000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>120</MaxSDUSize>
+ <RetransmissionNumber>23</RetransmissionNumber>
+ <MaxTransportLatency>60</MaxTransportLatency>
+ <PresentationDelay>20000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>117</MaxSDUSize>
+ <RetransmissionNumber>23</RetransmissionNumber>
+ <MaxTransportLatency>45</MaxTransportLatency>
+ <PresentationDelay>20000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>155</MaxSDUSize>
+ <RetransmissionNumber>23</RetransmissionNumber>
+ <MaxTransportLatency>60</MaxTransportLatency>
+ <PresentationDelay>20000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ </QosSettingsForHighReliabilityMedia>
+</VMCP>
+
+<BAP>
+ <CodecCapabilitiesForVoice>
+ <CodecSettings>
+ <SamplingFrequencyInHz>32000</SamplingFrequencyInHz>
+ <FrameDurationInMicroSecs>10000</FrameDurationInMicroSecs>
+ <OctetsPerCodecFrame>80</OctetsPerCodecFrame>
+ <Mandatory>0</Mandatory>
+ </CodecSettings>
+ </CodecCapabilitiesForVoice>
+ <CodecCapabilitiesForMedia>
+ <CodecSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <FrameDurationInMicroSecs>10000</FrameDurationInMicroSecs>
+ <OctetsPerCodecFrame>100</OctetsPerCodecFrame>
+ <Mandatory>0</Mandatory>
+ </CodecSettings>
+ </CodecCapabilitiesForMedia>
+ <QosSettingsForLowLatencyVoice>
+ <QoSSettings>
+ <SamplingFrequencyInHz>32000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>80</MaxSDUSize>
+ <RetransmissionNumber>2</RetransmissionNumber>
+ <MaxTransportLatency>10</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ </QosSettingsForLowLatencyVoice>
+ <QosSettingsForLowLatencyMedia>
+ <QoSSettings>
+ <SamplingFrequencyInHz>8000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>26</MaxSDUSize>
+ <RetransmissionNumber>2</RetransmissionNumber>
+ <MaxTransportLatency>8</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>8000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>30</MaxSDUSize>
+ <RetransmissionNumber>2</RetransmissionNumber>
+ <MaxTransportLatency>10</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>16000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>30</MaxSDUSize>
+ <RetransmissionNumber>2</RetransmissionNumber>
+ <MaxTransportLatency>8</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>16000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>40</MaxSDUSize>
+ <RetransmissionNumber>2</RetransmissionNumber>
+ <MaxTransportLatency>10</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>24000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>45</MaxSDUSize>
+ <RetransmissionNumber>2</RetransmissionNumber>
+ <MaxTransportLatency>8</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>24000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>60</MaxSDUSize>
+ <RetransmissionNumber>2</RetransmissionNumber>
+ <MaxTransportLatency>10</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>75</MaxSDUSize>
+ <RetransmissionNumber>5</RetransmissionNumber>
+ <MaxTransportLatency>15</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>100</MaxSDUSize>
+ <RetransmissionNumber>15</RetransmissionNumber>
+ <MaxTransportLatency>70</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>90</MaxSDUSize>
+ <RetransmissionNumber>5</RetransmissionNumber>
+ <MaxTransportLatency>15</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>120</MaxSDUSize>
+ <RetransmissionNumber>5</RetransmissionNumber>
+ <MaxTransportLatency>20</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>32000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>60</MaxSDUSize>
+ <RetransmissionNumber>2</RetransmissionNumber>
+ <MaxTransportLatency>8</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>32000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>80</MaxSDUSize>
+ <RetransmissionNumber>2</RetransmissionNumber>
+ <MaxTransportLatency>10</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>117</MaxSDUSize>
+ <RetransmissionNumber>5</RetransmissionNumber>
+ <MaxTransportLatency>15</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>155</MaxSDUSize>
+ <RetransmissionNumber>13</RetransmissionNumber>
+ <MaxTransportLatency>100</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ </QosSettingsForLowLatencyMedia>
+ <QosSettingsForHighReliabilityMedia>
+ <QoSSettings>
+ <SamplingFrequencyInHz>8000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>26</MaxSDUSize>
+ <RetransmissionNumber>41</RetransmissionNumber>
+ <MaxTransportLatency>45</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>8000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>30</MaxSDUSize>
+ <RetransmissionNumber>53</RetransmissionNumber>
+ <MaxTransportLatency>60</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>16000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>30</MaxSDUSize>
+ <RetransmissionNumber>41</RetransmissionNumber>
+ <MaxTransportLatency>45</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>16000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>40</MaxSDUSize>
+ <RetransmissionNumber>47</RetransmissionNumber>
+ <MaxTransportLatency>60</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>1</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>24000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>45</MaxSDUSize>
+ <RetransmissionNumber>35</RetransmissionNumber>
+ <MaxTransportLatency>45</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>24000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>60</MaxSDUSize>
+ <RetransmissionNumber>41</RetransmissionNumber>
+ <MaxTransportLatency>60</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>32000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>60</MaxSDUSize>
+ <RetransmissionNumber>29</RetransmissionNumber>
+ <MaxTransportLatency>45</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>32000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>80</MaxSDUSize>
+ <RetransmissionNumber>35</RetransmissionNumber>
+ <MaxTransportLatency>60</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>75</MaxSDUSize>
+ <RetransmissionNumber>23</RetransmissionNumber>
+ <MaxTransportLatency>45</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>100</MaxSDUSize>
+ <RetransmissionNumber>23</RetransmissionNumber>
+ <MaxTransportLatency>60</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>90</MaxSDUSize>
+ <RetransmissionNumber>23</RetransmissionNumber>
+ <MaxTransportLatency>45</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>120</MaxSDUSize>
+ <RetransmissionNumber>23</RetransmissionNumber>
+ <MaxTransportLatency>60</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>117</MaxSDUSize>
+ <RetransmissionNumber>23</RetransmissionNumber>
+ <MaxTransportLatency>45</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>155</MaxSDUSize>
+ <RetransmissionNumber>13</RetransmissionNumber>
+ <MaxTransportLatency>100</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ </QosSettingsForHighReliabilityMedia>
+</BAP>
+
+<GCP>
+ <CodecCapabilitiesForVoice>
+ <CodecSettings>
+ <SamplingFrequencyInHz>16000</SamplingFrequencyInHz>
+ <FrameDurationInMicroSecs>7500</FrameDurationInMicroSecs>
+ <OctetsPerCodecFrame>60</OctetsPerCodecFrame>
+ <Mandatory>1</Mandatory>
+ </CodecSettings>
+ <CodecSettings>
+ <SamplingFrequencyInHz>16000</SamplingFrequencyInHz>
+ <FrameDurationInMicroSecs>7500</FrameDurationInMicroSecs>
+ <OctetsPerCodecFrame>60</OctetsPerCodecFrame>
+ <Mandatory>0</Mandatory>
+ </CodecSettings>
+ <CodecSettings>
+ <SamplingFrequencyInHz>16000</SamplingFrequencyInHz>
+ <FrameDurationInMicroSecs>7500</FrameDurationInMicroSecs>
+ <OctetsPerCodecFrame>30</OctetsPerCodecFrame>
+ <Mandatory>1</Mandatory>
+ </CodecSettings>
+ </CodecCapabilitiesForVoice>
+
+ <CodecCapabilitiesForMedia>
+ <CodecSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <FrameDurationInMicroSecs>7500</FrameDurationInMicroSecs>
+ <OctetsPerCodecFrame>75</OctetsPerCodecFrame>
+ <Mandatory>0</Mandatory>
+ </CodecSettings>
+ </CodecCapabilitiesForMedia>
+
+ <QosSettingsForLowLatencyVoice>
+ <QoSSettings>
+ <SamplingFrequencyInHz>16000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>15000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>60</MaxSDUSize>
+ <RetransmissionNumber>2</RetransmissionNumber>
+ <MaxTransportLatency>8</MaxTransportLatency>
+ <PresentationDelay>25000</PresentationDelay>
+ <Mandatory>1</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>16000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>15000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>60</MaxSDUSize>
+ <RetransmissionNumber>7</RetransmissionNumber>
+ <MaxTransportLatency>25</MaxTransportLatency>
+ <PresentationDelay>25000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>16000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>30</MaxSDUSize>
+ <RetransmissionNumber>11</RetransmissionNumber>
+ <MaxTransportLatency>33</MaxTransportLatency>
+ <PresentationDelay>25000</PresentationDelay>
+ <Mandatory>1</Mandatory>
+ </QoSSettings>
+ </QosSettingsForLowLatencyVoice>
+
+ <QosSettingsForLowLatencyMedia>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>75</MaxSDUSize>
+ <RetransmissionNumber>5</RetransmissionNumber>
+ <MaxTransportLatency>12</MaxTransportLatency>
+ <PresentationDelay>25000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ </QosSettingsForLowLatencyMedia>
+</GCP>
+
+<WMCP>
+ <CodecCapabilitiesForMedia>
+ <CodecSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <FrameDurationInMicroSecs>10000</FrameDurationInMicroSecs>
+ <OctetsPerCodecFrame>100</OctetsPerCodecFrame>
+ <Mandatory>0</Mandatory>
+ </CodecSettings>
+ </CodecCapabilitiesForMedia>
+ <QosSettingsForHighReliabilityMedia>
+ <QoSSettings>
+ <SamplingFrequencyInHz>16000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>40</MaxSDUSize>
+ <RetransmissionNumber>13</RetransmissionNumber>
+ <MaxTransportLatency>95</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>16000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>30</MaxSDUSize>
+ <RetransmissionNumber>13</RetransmissionNumber>
+ <MaxTransportLatency>75</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>32000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>80</MaxSDUSize>
+ <RetransmissionNumber>13</RetransmissionNumber>
+ <MaxTransportLatency>95</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>32000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>60</MaxSDUSize>
+ <RetransmissionNumber>13</RetransmissionNumber>
+ <MaxTransportLatency>75</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>10000</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>100</MaxSDUSize>
+ <RetransmissionNumber>11</RetransmissionNumber>
+ <MaxTransportLatency>40</MaxTransportLatency>
+ <PresentationDelay>25000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ <QoSSettings>
+ <SamplingFrequencyInHz>48000</SamplingFrequencyInHz>
+ <SDUIntervalInMicroSecs>7500</SDUIntervalInMicroSecs>
+ <Framing>0</Framing>
+ <MaxSDUSize>75</MaxSDUSize>
+ <RetransmissionNumber>13</RetransmissionNumber>
+ <MaxTransportLatency>75</MaxTransportLatency>
+ <PresentationDelay>40000</PresentationDelay>
+ <Mandatory>0</Mandatory>
+ </QoSSettings>
+ </QosSettingsForHighReliabilityMedia>
+</WMCP>
+
+</LEAudioConfig>
+
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 000000000..ad1b1accf
--- /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 <base/logging.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <hardware/bluetooth.h>
+#include <hardware/bt_csip.h>
+#include <hardware/bt_apm.h>
+#include <hardware/bt_acm.h>
+#include <hardware/bt_pacs_client.h>
+#include <hardware/bt_ascs_client.h>
+#include <hardware/bt_bap_uclient.h>
+#include <hardware/bt_vcp_controller.h>
+#include <hardware/bt_mcp.h>
+#include <hardware/bluetooth_callcontrol_interface.h>
+#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 <hardware/bt_bap_ba.h>
+
+/*******************************************************************************
+ * 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 000000000..0bc01247e
--- /dev/null
+++ b/le_audio/system/bt/btif/src/btif_acm.cc
@@ -0,0 +1,6672 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 "btif_acm"
+#include "btif_acm.h"
+#include <base/bind.h>
+#include <base/bind_helpers.h>
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <map>
+#include <future>
+#include "bta_closure_api.h"
+#include "btif_storage.h"
+#include <hardware/bluetooth.h>
+#include <hardware/bt_acm.h>
+#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 <base/callback.h>
+#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 <base/threading/thread.h>
+#include "osi/include/thread.h"
+#include <pthread.h>
+#include "bta_api.h"
+#include <hardware/bt_pacs_client.h>
+#include <hardware/bt_bap_uclient.h>
+#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 <inttypes.h>
+
+/*****************************************************************************
+ * 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<CodecConfig>& 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<uint8_t, int>::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<void> 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<std::mutex> 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(&current_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<RawAddress>::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<std::mutex> 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<RawAddress>::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<CodecConfig>& codec_user_preferences,
+ std::promise<void> peer_ready_promise);
+
+
+ void UpdateCodecConfig(
+ const RawAddress& peer_address,
+ const std::vector<CodecConfig>& codec_preferences,
+ int contextType,
+ int profileType,
+ std::promise<void> 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<RawAddress, BtifAcmPeer*>& Peers() const { return peers_; }
+ // const std::map<uint8_t, BtifAcmPeer*>& SetPeers() const { return set_peers_; }
+
+ std::vector<RawAddress> 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<RawAddress, BtifAcmPeer*> peers_;
+ std::map<RawAddress, uint8_t> addr_setid_pair;
+ std::map<uint8_t, uint8_t> set_cig_pair;//setid and cig id pair
+ std::map<RawAddress, std::map<uint8_t, uint8_t> > cig_cis_pair;//cig id and cis id pair
+ std::map<uint8_t, int> set_lock_status_;
+};
+
+
+/*****************************************************************************
+ * Static variables
+ *****************************************************************************/
+static BtifAcmInitiator btif_acm_initiator;
+std::vector<CodecConfig> 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<RawAddress> 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<std::mutex> 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<void> 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<uint8_t, uint8_t>()));
+ 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<RawAddress>::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<RawAddress>::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<StreamStateInfo> 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<int> (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<StreamConfigInfo> 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<CodecConfig>& 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(&current_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<StreamType> 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<StreamConnect> 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<RawAddress> 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<StreamType> 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<uint32_t>(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[0]) |
+ static_cast<uint32_t>(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[1] << 8) |
+ static_cast<uint32_t>(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(&current_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(&current_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<RawAddress>::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<RawAddress>::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<StreamType> 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<StreamConnect> 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<RawAddress> 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<StreamType> 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<StreamType> 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<void> peer_ready_promise;
+ std::future<void> 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<StreamType> 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<StreamReconfig> 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<uint32_t>(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[0]) |
+ static_cast<uint32_t>(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[1] << 8) |
+ static_cast<uint32_t>(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(&current_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(&current_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<RawAddress>::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<RawAddress>::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<StreamType> 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<StreamType> 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<StreamType> 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<StreamType> 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<uint32_t>(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[0]) |
+ static_cast<uint32_t>(p_acm_data->config_info.qos_config.ascs_configs[0].presentation_delay[1] << 8) |
+ static_cast<uint32_t>(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(&current_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(&current_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<uint32_t>(p_acm->config_info.qos_config.ascs_configs[0].presentation_delay[0]) |
+ static_cast<uint32_t>(p_acm->config_info.qos_config.ascs_configs[0].presentation_delay[1] << 8) |
+ static_cast<uint32_t>(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(&current_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(&current_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<StreamReconfig> 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<CodecConfig>& codecs_local_capabilities,
+ const std::vector<CodecConfig>&
+ 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<RawAddress> 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<RawAddress> 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<RawAddress>::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<btif_acm_sm_event_t>(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<RawAddress> streaming_devices;
+ std::vector<RawAddress> 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<RawAddress>::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<CodecConfig>& 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<void> 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<RawAddress>::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<void> peer_ready_promise;
+ std::future<void> 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<RawAddress>::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<RawAddress>::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<CodecConfig> 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<void> peer_ready_promise;
+ std::future<void> 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<RawAddress>::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<RawAddress>::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<QoSConfig> 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<uint16_t>(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<uint8_t>(sdu_interval & 0xFF),
+ static_cast<uint8_t>((sdu_interval >> 8)& 0xFF),
+ static_cast<uint8_t>((sdu_interval >> 16)& 0xFF)
+ },
+ .sdu_interval_s_to_m = {
+ static_cast<uint8_t>(sdu_interval & 0xFF),
+ static_cast<uint8_t>((sdu_interval >> 8)& 0xFF),
+ static_cast<uint8_t>((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<uint8_t>(presen_delay & 0xFF),
+ static_cast<uint8_t>((presen_delay >> 8)& 0xFF),
+ static_cast<uint8_t>((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<uint16_t>(CodecSampleRate::CODEC_SAMPLE_RATE_48000)) {
+ result_config->sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_48000;
+ return true;
+ }
+ if (samp_freq & static_cast<uint16_t>(CodecSampleRate::CODEC_SAMPLE_RATE_44100)) {
+ result_config->sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_44100;
+ return true;
+ }
+ if (samp_freq & static_cast<uint16_t>(CodecSampleRate::CODEC_SAMPLE_RATE_32000)) {
+ result_config->sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_32000;
+ return true;
+ }
+ if (samp_freq & static_cast<uint16_t>(CodecSampleRate::CODEC_SAMPLE_RATE_24000)) {
+ result_config->sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_24000;
+ return true;
+ }
+ if (samp_freq & static_cast<uint16_t>(CodecSampleRate::CODEC_SAMPLE_RATE_16000)) {
+ result_config->sample_rate = CodecSampleRate::CODEC_SAMPLE_RATE_16000;
+ return true;
+ }
+ if (samp_freq & static_cast<uint16_t>(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<uint8_t>(CodecFrameDuration::FRAME_DUR_10)) {
+ BTIF_TRACE_DEBUG("%s: selecting 10ms as best frame duration", __func__);
+ UpdateFrameDuration(result_config,
+ static_cast<uint8_t>(CodecFrameDuration::FRAME_DUR_10));
+ return true;
+ }
+
+ if ((frame_dura &
+ static_cast<uint8_t>(CodecFrameDuration::FRAME_DUR_7_5)) == 0) {
+ BTIF_TRACE_DEBUG("%s: selecting 7.5ms as best frame duration", __func__);
+ UpdateFrameDuration(result_config,
+ static_cast<uint8_t>(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<CodecConfig> pac_record;
+ std::vector<CodecConfig> 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<uint16_t>(it->sample_rate);
+ } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_44100) {
+ peer_samp_freq |= static_cast<uint16_t>(it->sample_rate);
+ } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_32000) {
+ peer_samp_freq |= static_cast<uint16_t>(it->sample_rate);
+ } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_24000) {
+ peer_samp_freq |= static_cast<uint16_t>(it->sample_rate);
+ } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_16000) {
+ peer_samp_freq |= static_cast<uint16_t>(it->sample_rate);
+ } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_8000) {
+ peer_samp_freq |= static_cast<uint16_t>(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<uint16_t>(it->sample_rate);
+ } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_44100) {
+ vmcp_samp_freq |= static_cast<uint16_t>(it->sample_rate);
+ } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_32000) {
+ vmcp_samp_freq |= static_cast<uint16_t>(it->sample_rate);
+ } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_24000) {
+ vmcp_samp_freq |= static_cast<uint16_t>(it->sample_rate);
+ } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_16000) {
+ vmcp_samp_freq |= static_cast<uint16_t>(it->sample_rate);
+ } else if (it->sample_rate == CodecSampleRate::CODEC_SAMPLE_RATE_8000) {
+ vmcp_samp_freq |= static_cast<uint16_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<CodecConfig> 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<uint16_t>(current_active_config.sample_rate);
+ } else {
+ BTIF_TRACE_DEBUG("[ACM]:%s: default sample_rate = %d",
+ __func__, default_config.sample_rate);
+ return static_cast<uint16_t>(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<uint8_t>(current_active_config.channel_mode);
+ } else {
+ BTIF_TRACE_DEBUG("[ACM]:%s: channel mode = %d",
+ __func__, default_config.channel_mode);
+ return static_cast<uint8_t>(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<int>(GetOctsPerFrame(&current_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<uint16_t>(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<uint16_t>(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(&current_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(&current_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(&current_active_config);
+}
+
+uint8_t btif_acm_lc3q_ver() {
+ BTIF_TRACE_DEBUG("[ACM]%s",__func__);
+ return GetVendorMetaDataLc3QVer(&current_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 000000000..f9b99bf41
--- /dev/null
+++ b/le_audio/system/bt/btif/src/btif_acm_source.cc
@@ -0,0 +1,242 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 <hardware/bluetooth.h>
+#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 000000000..02f5a6f54
--- /dev/null
+++ b/le_audio/system/bt/btif/src/btif_apm.cc
@@ -0,0 +1,189 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 "btif_apm"
+
+#include <base/logging.h>
+#include <string.h>
+
+#include <hardware/bluetooth.h>
+#include <hardware/bt_apm.h>
+
+#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<std::mutex> 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<std::mutex> 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 000000000..d5853e6ee
--- /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 <base/bind.h>
+#include <base/callback.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <hardware/bluetooth.h>
+#include <hardware/bt_ascs_client.h>
+#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<AscsClientInterface> 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<AseOpStatus> 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<AseParams> sink_ase_list,
+ std::vector<AseParams> 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<AseCodecConfigOp> 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<AseQosConfigOp> 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<AseEnableOp> 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<AseDisableOp> 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<AseStartReadyOp> 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<AseStopReadyOp> 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<AseReleaseOp> 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<AseUpdateMetadataOp> 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 000000000..0bd393ca1
--- /dev/null
+++ b/le_audio/system/bt/btif/src/btif_bap_broadcast.cc
@@ -0,0 +1,1822 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 "btif_bap_broadcast"
+
+#include "btif_bap_broadcast.h"
+
+#include <base/logging.h>
+#include <string.h>
+#include <base/bind.h>
+#include <hardware/bluetooth.h>
+#include <hardware/bt_av.h>
+#include <hardware/bt_bap_ba.h>
+
+#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 <time.h>
+#include <hardware/ble_advertiser.h>
+#include <hardware/bt_gatt.h>
+#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<btav_a2dp_codec_config_t> 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<uint8_t> 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<std::mutex> 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(&current_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<uint16_t> GetBISHandles() const { return bis_handle_list_;}
+ void SetBISHandles(std::vector<uint16_t> 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<uint16_t> 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<uint8_t/*adv_handle*/, BtifBapBroadcaster*>& Bms() const { return bms_; }
+
+ private:
+ void CleanupAllBms();
+
+ btbap_broadcast_callbacks_t* callbacks_;
+ bool enabled_;
+ bool offload_enabled_;
+ int max_broadcast_;
+ std::map<uint8_t, BtifBapBroadcaster*> 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<std::mutex> 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<const char*>(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<uint16_t> 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<uint16_t> 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<uint16_t> 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<const char*>(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 000000000..447c3f62e
--- /dev/null
+++ b/le_audio/system/bt/btif/src/btif_bap_codec_utils.cc
@@ -0,0 +1,395 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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"
+
+// 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 <base/logging.h>
+#include <hardware/bluetooth.h>
+#include <hardware/bt_pacs_client.h>
+
+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<uint16_t> (config->channel_mode) &
+ static_cast<uint16_t> (CodecChannelMode::CODEC_CHANNEL_MODE_STEREO)) {
+ max_chnl_count = 2;
+ } else if(static_cast<uint16_t> (config->channel_mode) &
+ static_cast<uint16_t> (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<uint64_t>(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<uint64_t>(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<uint64_t>(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<uint64_t>(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<uint64_t>(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<uint64_t>(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<uint64_t>(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<int>(src_config->channel_mode) &
+ static_cast<int>(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<uint8_t> (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 000000000..6daeb6ad6
--- /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 <base/strings/string_split.h>
+
+#include <base/logging.h>
+#include <ctype.h>
+#include <openssl/rand.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <string>
+
+#include <mutex>
+
+#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<std::recursive_mutex> 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(&current_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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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 &section,
+ uint16_t *index) {
+ char *temp = nullptr;
+ if (section.length() != 20) return false;
+
+ std::vector<std::string> 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<char *> *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<uint16_t> (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<uint16_t>(record->sample_rate));
+
+ // update chnl count
+ config_set_uint16(config, section, BAP_CHNL_ALLOCATION_KEY,
+ static_cast<uint16_t> (record->channel_mode));
+
+ // update supp frames
+ config_set_uint16(config, section, BAP_SUPP_FRM_DURATIONS_KEY ,
+ static_cast<uint16_t> (GetCapaSupFrameDurations(record)));
+
+ // update chnl supp min octs per frame
+ config_set_uint16(config, section, BAP_SUP_MIN_OCTS_PER_FRAME_KEY ,
+ static_cast<uint16_t> (GetCapaSupOctsPerFrame(record) &
+ 0xFFFF));
+
+ // update chnl supp max octs per frame
+ config_set_uint16(config, section, BAP_SUP_MAX_OCTS_PER_FRAME_KEY,
+ static_cast<uint16_t> ((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<uint16_t> (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<uint16_t> (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<uint16_t>(record->sample_rate));
+
+ // update chnl count
+ config_set_uint16(config, section, BAP_CHNL_ALLOCATION_KEY,
+ static_cast<uint16_t> (record->channel_mode));
+
+ // update configured frame duration
+ config_set_uint16(config, section, BAP_CONF_FRAME_DUR_KEY,
+ static_cast<uint16_t> (GetFrameDuration(record)));
+
+ // update configured octs per frame
+ config_set_uint16(config, section, BAP_CONF_OCTS_PER_FRAME_KEY,
+ static_cast<uint16_t> (GetOctsPerFrame(record)));
+
+ // update LC3 frames per SDU
+ config_set_uint16(config, section, BAP_LC3_FRAMES_PER_SDU_KEY,
+ static_cast<uint16_t> (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<std::recursive_mutex> lock(config_lock);
+ std::vector<char *> sections;
+
+ if(btif_bap_find_sections(bd_addr, rec_type, context_type,
+ direction, record, &sections)) {
+ 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<std::recursive_mutex> lock(config_lock);
+ bool record_removed = false;
+ std::vector<char *> sections;
+
+ if(btif_bap_find_sections(bd_addr, rec_type, context_type,
+ direction, record, &sections)) {
+ 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<std::recursive_mutex> lock(config_lock);
+ bool record_removed = false;
+ std::vector<char *> sections;
+
+ if(btif_bap_find_sections(bd_addr, rec_type, context_type,
+ direction, nullptr, &sections)) {
+ 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<std::recursive_mutex> 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<CodecConfig> *pac_records) {
+ std::unique_lock<std::recursive_mutex> lock(config_lock);
+ std::vector<char *> sections;
+
+ if(btif_bap_find_sections(bd_addr, rec_type, context_type,
+ direction, nullptr, &sections)) {
+ 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<CodecSampleRate>
+ (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 000000000..7a049a525
--- /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 <base/bind.h>
+#include <base/callback.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <hardware/bluetooth.h>
+#include <hardware/bt_pacs_client.h>
+#include <hardware/bt_bap_uclient.h>
+#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<UcastClientInterface> 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<StreamStateInfo> 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<StreamConfigInfo> 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<StreamReconfig> &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<StreamType> &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<RawAddress> &address, bool is_direct,
+ std::vector<StreamConnect> &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<StreamType> &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<StreamType> &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<StreamUpdate> &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 000000000..ffa374563
--- /dev/null
+++ b/le_audio/system/bt/btif/src/btif_bap_uclient_test.cc
@@ -0,0 +1,2971 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 <base/bind.h>
+#include <base/callback.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <hardware/bluetooth.h>
+#include <hardware/bt_pacs_client.h>
+#include <hardware/bt_bap_uclient.h>
+#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<StreamStateInfo> 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<int> (it->stream_state);
+ }
+ }
+ void OnStreamConfig(const RawAddress &address,
+ std::vector<StreamConfigInfo> 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<int> (it->audio_location);
+ btif_bap_add_record(address, REC_TYPE_CONFIGURATION,
+ it->stream_type.type,
+ static_cast<CodecDirection> (it->stream_type.direction),
+ &it->codec_config);
+
+ std::vector<CodecConfig> acm_pac_records;
+
+ btif_bap_get_records(address,
+ REC_TYPE_CAPABILITY,
+ CONTENT_TYPE_MEDIA |
+ CONTENT_TYPE_UNSPECIFIED,
+ static_cast<CodecDirection>
+ (it->stream_type.direction),
+ &acm_pac_records);
+
+ LOG(WARNING) << __func__ << " acm len to be 3" << (acm_pac_records.size());
+
+ std::vector<CodecConfig> config_pac_records;
+ btif_bap_get_records(address,
+ REC_TYPE_CONFIGURATION,
+ CONTENT_TYPE_MEDIA,
+ static_cast<CodecDirection>
+ (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<CodecDirection>
+ (it->stream_type.direction));
+
+ config_pac_records.clear();
+ btif_bap_get_records(address,
+ REC_TYPE_CONFIGURATION,
+ CONTENT_TYPE_MEDIA,
+ static_cast<CodecDirection>
+ (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<CodecDirection>
+ (it->stream_type.direction));
+
+ acm_pac_records.clear();
+ btif_bap_get_records(address,
+ REC_TYPE_CAPABILITY,
+ CONTENT_TYPE_MEDIA |
+ CONTENT_TYPE_UNSPECIFIED,
+ static_cast<CodecDirection>
+ (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<uint8_t>(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<StreamConnect> 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<StreamConnect> streams_2;
+ streams_2.push_back(conn_info_2);
+
+ // reconfig information
+ std::vector<StreamReconfig> reconf_streams;
+ codec_qos_config.qos_config.ascs_configs[0].cis_id = 1;
+ UpdateFrameDuration(&codec_qos_config.codec_config,
+ static_cast<uint8_t>(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<StreamReconfig> 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<StreamType> 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<uint8_t>(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<StreamConnect> media_rx_streams;
+ media_rx_streams.push_back(conn_info_media_recording);
+
+ std::vector<StreamType> 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<uint8_t>(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<uint8_t>(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<uint8_t>(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<StreamConnect> 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<StreamType> media_streams;
+ media_streams.push_back(type_media);
+ std::vector<StreamType> 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<StreamReconfig> 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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<StreamConnect> 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<StreamConnect> 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<StreamType> media_streams;
+ media_streams.push_back(type_media);
+
+ std::vector<StreamType> gaming_tx_streams;
+ gaming_tx_streams.push_back(type_media);
+
+ std::vector<StreamType> gaming_rx_streams;
+ gaming_rx_streams.push_back(type_voice_rx);
+
+ std::vector<StreamType> voice_streams;
+ voice_streams.push_back(type_voice_tx);
+ voice_streams.push_back(type_voice_rx);
+
+ std::vector<StreamType> 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<StreamReconfig> reconf_streams;
+ std::vector<StreamReconfig> 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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<StreamConnect> 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<StreamConnect> voice_conn_streams;
+ voice_conn_streams.push_back(conn_info_voice_tx);
+ voice_conn_streams.push_back(conn_info_voice_rx);
+
+ std::vector<StreamConnect> 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<StreamType> media_streams;
+ media_streams.push_back(type_media);
+
+ std::vector<StreamType> gaming_tx_streams;
+ gaming_tx_streams.push_back(type_gaming_tx);
+
+ std::vector<StreamType> media_rx_streams;
+ media_rx_streams.push_back(type_media_rx);
+
+ std::vector<StreamType> voice_streams;
+ voice_streams.push_back(type_voice_tx);
+ voice_streams.push_back(type_voice_rx);
+
+ std::vector<StreamType> 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<StreamReconfig> 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<bluetooth::bap::pacs::CodecConfig> sink_pac_records,
+ std::vector<bluetooth::bap::pacs::CodecConfig> 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 000000000..59c355da1
--- /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 <base/at_exit.h>
+#include <base/bind.h>
+#include <base/threading/thread.h>
+#include <base/callback.h>
+#include <hardware/bluetooth.h>
+#include <hardware/bluetooth_callcontrol_callbacks.h>
+#include <hardware/bluetooth_callcontrol_interface.h>
+
+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<CallControllerInterface> 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<uint8_t> 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__ << ": " <<Uri;
+ uint8_t* callUri = (uint8_t*)malloc(sizeof(uint8_t)*strlen((char*)Uri)+1);
+ if (callUri != NULL) {
+ memcpy(callUri, Uri, strlen((char*)Uri)+1);
+ do_in_bta_thread(FROM_HERE,Bind(&CallController::UpdateIncomingCall,
+ Unretained(CallController::Get()), index, callUri));
+ free(callUri);
+ }
+}
+
+void IncomingCallTargetUri(int index, uint8_t* target_uri) {
+ LOG(INFO) << __func__ << ": " <<target_uri;
+ uint8_t* callUri = (uint8_t*)malloc(sizeof(uint8_t)*strlen((char*)target_uri)+1);
+ if (callUri != NULL) {
+ memcpy(callUri, target_uri, strlen((char*)target_uri)+1);
+
+ do_in_bta_thread(FROM_HERE,Bind(&CallController::UpdateIncomingCallTargetUri,
+ Unretained(CallController::Get()), index, callUri));
+ free(callUri);
+ }
+}
+
+void Disconnect(const RawAddress& address) {
+
+ LOG(INFO) << __func__ << ": " <<address;
+ do_in_bta_thread(FROM_HERE,Bind(&CallController::Disconnect,
+ Unretained(CallController::Get()), address));
+ return;
+
+}
+
+void ContentControlId(uint32_t ccid) {
+ LOG(INFO) << __func__ << ": " << ccid;
+ do_in_bta_thread(FROM_HERE,
+ Bind(&CallController::ContentControlId, Unretained(CallController::Get()), ccid));
+}
+
+bt_status_t CallControlResponse(uint8_t op, uint8_t index, uint32_t status, const RawAddress& address) {
+
+ LOG(INFO) << __func__ << ": ";
+ do_in_bta_thread(FROM_HERE,Bind(&CallController::CallControlResponse,
+ Unretained(CallController::Get()), op, index, status, address));
+ return BT_STATUS_SUCCESS;
+}
+
+void SetActiveDevice(const RawAddress& address, int set_id) override {
+ LOG(INFO) << __func__ << ": set_id" << set_id<< ": device"<< address;
+ do_in_bta_thread(FROM_HERE,
+ Bind(&CallController::SetActiveDevice, Unretained(CallController::Get()), address, set_id));
+}
+
+void ConnectionStateCallback(uint8_t state, const RawAddress& address) override {
+
+ LOG(INFO) << __func__ << ": device=" << address << " state=" << state;
+ do_in_jni_thread(FROM_HERE, Bind(&CallControllerCallbacks::ConnectionStateCallback,
+ Unretained(callbacks), state, address));
+
+}
+
+void CallControlCallback(uint8_t op, std::vector<int32_t> indicies, int count, std::vector<uint8_t> uri_data, const RawAddress& address) override {
+
+ LOG(INFO) << __func__ << ": device=" << address << " operation=" << op;
+
+ do_in_jni_thread(FROM_HERE, Bind(&CallControllerCallbacks::CallControlCallback,
+ Unretained(callbacks), op, std::move(indicies), count, std::move(uri_data), address));
+}
+
+ void CallControlInitializedCallback(uint8_t state) override {
+ LOG(INFO) << __func__ << ": state=" << state;
+ do_in_jni_thread(FROM_HERE, Bind(&CallControllerCallbacks::CallControlInitializedCallback,
+ Unretained(callbacks), state));
+ }
+
+ private:
+ CallControllerCallbacks* callbacks;
+ };
+}//namespace
+
+const CallControllerInterface* btif_cc_server_get_interface(void) {
+ LOG(INFO) << __func__;
+ if (!CallControllerInstance)
+ CallControllerInstance.reset(new CallControllerInterfaceImpl());
+ return CallControllerInstance.get();
+}
diff --git a/le_audio/system/bt/btif/src/btif_csip.cc b/le_audio/system/bt/btif/src/btif_csip.cc
new file mode 100644
index 000000000..4700c8e54
--- /dev/null
+++ b/le_audio/system/bt/btif/src/btif_csip.cc
@@ -0,0 +1,252 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.
+ *
+ ******************************************************************************/
+
+/*******************************************************************************
+ *
+ * Filename: btif_csip.c
+ *
+ * Description: CSIP client implementation (Set Coordinator)
+ *
+ ******************************************************************************/
+
+#define LOG_TAG "bt_btif_csip"
+
+#include <base/at_exit.h>
+#include <base/bind.h>
+#include <base/threading/thread.h>
+#include <bluetooth/uuid.h>
+#include <errno.h>
+#include <hardware/bluetooth.h>
+#include <stdlib.h>
+#include <string.h>
+#include <vector>
+#include "device/include/controller.h"
+#include "bta_csip_api.h"
+
+#include "btif_api.h"
+#include "btif_common.h"
+#include "btif_util.h"
+
+#include <hardware/bt_csip.h>
+
+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<RawAddress> 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 000000000..a9c32f0ff
--- /dev/null
+++ b/le_audio/system/bt/btif/src/btif_dm_adv_audio.cc
@@ -0,0 +1,692 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 "bt_btif_dm"
+
+#include "btif_dm.h"
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+#include <iterator>
+#include <map>
+
+#include <mutex>
+
+#include <bluetooth/uuid.h>
+#include "hardware/vendor.h"
+
+#include <hardware/bluetooth.h>
+#include <hardware/bt_hearing_aid.h>
+
+#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<bluetooth::Uuid> uuid_srv_disc_search;
+std::unordered_map<RawAddress, uint32_t> 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<Uuid*> (p_data->disc_res.p_uuid_list + i);
+ for (j = 0; j < uuid_len; j++) {
+ Uuid* base_uuid = reinterpret_cast<Uuid*> (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 000000000..35b4ee952
--- /dev/null
+++ b/le_audio/system/bt/btif/src/btif_mcp.cc
@@ -0,0 +1,185 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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.
+ *
+ ******************************************************************************/
+
+
+/* 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 <base/bind.h>
+#include <base/callback.h>
+#include <hardware/bluetooth.h>
+#include <hardware/bt_mcp.h>
+
+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<McpServerInterface> 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 000000000..8c0867a65
--- /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 <base/bind.h>
+#include <base/callback.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <hardware/bluetooth.h>
+#include <hardware/bt_pacs_client.h>
+#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<PacsClientInterface> 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<CodecConfig> sink_pac_records,
+ std::vector<CodecConfig> 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 000000000..10ba2acab
--- /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 <base/bind.h>
+#include <base/callback.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <hardware/bluetooth.h>
+#include <hardware/bt_vcp_controller.h>
+
+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<VcpControllerInterface> 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 000000000..844ac9bd9
--- /dev/null
+++ b/le_audio/system/bt/btif/src/btif_vmcp.cc
@@ -0,0 +1,775 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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 <iostream>
+#include <string.h>
+#include <vector>
+#include <stack>
+#include <log/log.h>
+
+#include "bt_types.h"
+#include "bt_trace.h"
+
+#include <libxml/parser.h>
+#include "btif_bap_codec_utils.h"
+#include "btif_vmcp.h"
+#include "btif_api.h"
+#include <bluetooth/uuid.h>
+
+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::vector<codec_config>vmcp_voice_codec;
+std::vector<codec_config>vmcp_media_codec;
+std::vector<qos_config>vmcp_qos_low_lat_voice;
+std::vector<qos_config>vmcp_qos_low_lat_media;
+std::vector<qos_config>vmcp_qos_high_rel_media;
+
+std::vector<codec_config>bap_voice_codec;
+std::vector<codec_config>bap_media_codec;
+std::vector<qos_config>bap_qos_low_lat_voice;
+std::vector<qos_config>bap_qos_low_lat_media;
+std::vector<qos_config>bap_qos_high_rel_media;
+
+std::vector<codec_config>gcp_voice_codec;
+std::vector<codec_config>gcp_media_codec;
+std::vector<qos_config>gcp_qos_low_lat_voice;
+std::vector<qos_config>gcp_qos_low_lat_media;
+
+std::vector<codec_config>wmcp_media_codec;
+std::vector<qos_config>wmcp_qos_high_rel_media;
+
+vector<CodecConfig> get_all_codec_configs(uint8_t profile, uint8_t context)
+{
+ vector<CodecConfig> ret_config;
+ CodecConfig temp_config;
+ vector<codec_config> *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<uint8_t>(CodecFrameDuration::FRAME_DUR_7_5));
+ else if (vptr->at(i).frame_dur_msecs == FRM_DURATION_10_MS)
+ UpdateFrameDuration(&temp_config, static_cast<uint8_t>(CodecFrameDuration::FRAME_DUR_10));
+
+ UpdateOctsPerFrame(&temp_config, vptr->at(i).oct_per_codec_frm);
+
+ ret_config.push_back(temp_config);
+ }
+
+ return ret_config;
+}
+
+vector<CodecConfig> get_preferred_codec_configs(uint8_t profile, uint8_t context)
+{
+ vector<CodecConfig> ret_config;
+ CodecConfig temp_config;
+ vector<codec_config> *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<uint8_t>(CodecFrameDuration::FRAME_DUR_7_5));
+ else if (vptr->at(i).frame_dur_msecs == FRM_DURATION_10_MS)
+ UpdateFrameDuration(&temp_config, static_cast<uint8_t>(CodecFrameDuration::FRAME_DUR_10));
+
+ UpdateOctsPerFrame(&temp_config, vptr->at(i).oct_per_codec_frm);
+
+ ret_config.push_back(temp_config);
+ }
+ }
+
+ return ret_config;
+}
+
+vector<QoSConfig> get_all_qos_params(uint8_t profile, uint8_t context)
+{
+ vector<QoSConfig> ret_config;
+ QoSConfig temp_config;
+ vector<qos_config> *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<QoSConfig> get_qos_params_for_codec(uint8_t profile, uint8_t context, CodecSampleRate freq, uint8_t frame_dur, uint8_t octets)
+{
+ vector<QoSConfig> ret_config;
+ QoSConfig temp_config;
+ vector<qos_config> *vptr = NULL;
+ uint32_t frame_dur_micro_sec = 0;
+ uint32_t local_freq = 0;
+
+ if (frame_dur == static_cast<uint8_t>(CodecFrameDuration::FRAME_DUR_7_5))
+ frame_dur_micro_sec = FRM_DURATION_7_5_MS * 1000;
+ else if (frame_dur == static_cast<uint8_t>(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<xmlNode*> 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<xmlNode*> 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<xmlNode*> 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 000000000..62d92d263
--- /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 <map>
+#include <utility>
+
+#include <base/logging.h>
+
+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<int, State*> 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 000000000..713786789
--- /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<int32_t> indicies, int count, std::vector<uint8_t> 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 000000000..c2b784989
--- /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 <hardware/bluetooth.h>
+
+#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<uint8_t> 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 000000000..f32fb90d9
--- /dev/null
+++ b/le_audio/vhal/include/hardware/bt_acm.h
@@ -0,0 +1,126 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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_ACM_H
+#define ANDROID_INCLUDE_BT_ACM_H
+
+#include <vector>
+
+#include <hardware/bluetooth.h>
+#include <hardware/bt_av.h>
+#include <hardware/bt_pacs_client.h>
+
+__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<CodecConfig> codecs_local_acmabilities,
+ std::vector<CodecConfig> 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<CodecConfig>& 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<CodecConfig> 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 000000000..2eb95e32a
--- /dev/null
+++ b/le_audio/vhal/include/hardware/bt_apm.h
@@ -0,0 +1,75 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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_APM_H
+#define ANDROID_INCLUDE_BT_APM_H
+
+#define BT_APM_MODULE_ID "apm"
+
+#include <vector>
+
+#include <hardware/bluetooth.h>
+
+__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 000000000..cd9341b3d
--- /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 <hardware/bluetooth.h>
+
+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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<AseOpStatus> status;
+} __attribute__((packed));
+
+struct Ase {
+ uint16_t ase_handle;
+ uint16_t ase_ccc_handle;
+ AseParams ase_params;
+} __attribute__((packed));
+
+struct AscsDiscoveryDb {
+ std::vector<Ase> 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<AseOpStatus> 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<AseParams> sink_ase_list,
+ std::vector<AseParams> 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<AseCodecConfigOp> codec_configs);
+
+ virtual void QosConfig(uint16_t client_id, const RawAddress& address,
+ std::vector<AseQosConfigOp> qos_configs);
+
+ virtual void Enable(uint16_t client_id, const RawAddress& address,
+ std::vector<AseEnableOp> enable_ops);
+
+ virtual void Disable(uint16_t client_id, const RawAddress& address,
+ std::vector<AseDisableOp> disable_ops);
+
+ virtual void StartReady(uint16_t client_id, const RawAddress& address,
+ std::vector<AseStartReadyOp> start_ready_ops);
+
+ virtual void StopReady(uint16_t client_id, const RawAddress& address,
+ std::vector<AseStopReadyOp> stop_ready_ops);
+
+ virtual void Release(uint16_t client_id, const RawAddress& address,
+ std::vector<AseReleaseOp> release_ops);
+
+ virtual void UpdateStream(uint16_t client_id, const RawAddress& address,
+ std::vector<AseUpdateMetadataOp> 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 000000000..58c5fbd91
--- /dev/null
+++ b/le_audio/vhal/include/hardware/bt_bap_ba.h
@@ -0,0 +1,126 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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_BA_H
+#define ANDROID_INCLUDE_BT_BAP_BA_H
+
+#include <hardware/bluetooth.h>
+#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<btav_a2dp_codec_config_t> 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<uint16_t> bis_handles);
+
+typedef void (* bap_broadcast_bid_callback) (std::vector<uint8_t> 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 000000000..7f5a481fc
--- /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 <hardware/bluetooth.h>
+#include <hardware/bt_av.h>
+#include <hardware/bt_pacs_client.h>
+
+
+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<CISConfig> cis_configs; // individual CIS configs
+ std::vector<ASCSConfig> 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<CodecQosConfig> codec_qos_config_pair;
+};
+
+enum class StreamReconfigType {
+ CODEC_CONFIG,
+ QOS_CONFIG
+};
+
+struct StreamReconfig {
+ StreamType stream_type;
+ StreamReconfigType reconf_type;
+ std::vector<CodecQosConfig> 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<CodecConfig> codecs_selectable; // pacs codec capabilities
+};
+
+class UcastClientCallbacks {
+ public:
+ virtual ~UcastClientCallbacks() = default;
+
+ virtual void OnStreamState(const RawAddress &address,
+ std::vector<StreamStateInfo> streams_state_info) = 0;
+
+ virtual void OnStreamConfig(const RawAddress &address,
+ std::vector<StreamConfigInfo> 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<RawAddress> &address, bool is_direct,
+ std::vector<StreamConnect> &streams) = 0;
+
+ virtual void Disconnect(const RawAddress& address,
+ std::vector<StreamType> &streams) = 0;
+
+ virtual void Start(const RawAddress& address,
+ std::vector<StreamType> &streams) = 0;
+
+ virtual void Stop(const RawAddress& address,
+ std::vector<StreamType> &streams) = 0;
+
+ virtual void Reconfigure(const RawAddress& address,
+ std::vector<StreamReconfig> &streams) = 0;
+
+ virtual void UpdateStream(const RawAddress& address,
+ std::vector<StreamUpdate> &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 000000000..92eeef173
--- /dev/null
+++ b/le_audio/vhal/include/hardware/bt_csip.h
@@ -0,0 +1,119 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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_CSIP_H
+#define ANDROID_INCLUDE_BT_CSIP_H
+
+#include <stdint.h>
+#include <bluetooth/uuid.h>
+#include <vector>
+
+__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<RawAddress> 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<RawAddress> 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 000000000..8bde52dec
--- /dev/null
+++ b/le_audio/vhal/include/hardware/bt_mcp.h
@@ -0,0 +1,66 @@
+/******************************************************************************
+ *
+ * Copyright 2021 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_MCP_H
+#define ANDROID_INCLUDE_BT_MCP_H
+
+
+
+#include <hardware/bluetooth.h>
+#include <vector>
+
+#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 000000000..7579b728e
--- /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 <hardware/bluetooth.h>
+#include <hardware/bt_av.h>
+
+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<CodecConfig> sink_pac_records,
+ std::vector<CodecConfig> 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 000000000..ea2e35a85
--- /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 <hardware/bluetooth.h>
+
+#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 */
+
+
diff --git a/main/shim/acl.cc b/main/shim/acl.cc
index 533129dc0..54a8f58d0 100644
--- a/main/shim/acl.cc
+++ b/main/shim/acl.cc
@@ -59,8 +59,10 @@
#include "stack/include/bt_hdr.h"
#include "stack/include/btm_api.h"
#include "stack/include/btm_status.h"
+#include "stack/include/pan_api.h"
#include "stack/include/sec_hci_link_interface.h"
#include "stack/l2cap/l2c_int.h"
+#include "types/ble_address_with_type.h"
#include "types/raw_address.h"
extern tBTM_CB btm_cb;
@@ -128,6 +130,51 @@ class ShadowAcceptlist {
std::unordered_set<hci::AddressWithType> acceptlist_set_;
};
+class ShadowAddressResolutionList {
+ public:
+ ShadowAddressResolutionList(uint8_t max_address_resolution_size)
+ : max_address_resolution_size_(max_address_resolution_size) {}
+
+ bool Add(const hci::AddressWithType& address_with_type) {
+ if (address_resolution_set_.size() == max_address_resolution_size_) {
+ LOG_ERROR("Address Resolution is full size:%zu",
+ address_resolution_set_.size());
+ return false;
+ }
+ if (!address_resolution_set_.insert(address_with_type).second) {
+ LOG_WARN("Attempted to add duplicate le address to address_resolution:%s",
+ PRIVATE_ADDRESS(address_with_type));
+ }
+ return true;
+ }
+
+ bool Remove(const hci::AddressWithType& address_with_type) {
+ auto iter = address_resolution_set_.find(address_with_type);
+ if (iter == address_resolution_set_.end()) {
+ LOG_WARN("Unknown device being removed from address_resolution:%s",
+ PRIVATE_ADDRESS(address_with_type));
+ return false;
+ }
+ address_resolution_set_.erase(iter);
+ return true;
+ }
+
+ std::unordered_set<hci::AddressWithType> GetCopy() const {
+ return address_resolution_set_;
+ }
+
+ bool IsFull() const {
+ return address_resolution_set_.size() ==
+ static_cast<size_t>(max_address_resolution_size_);
+ }
+
+ void Clear() { address_resolution_set_.clear(); }
+
+ private:
+ uint8_t max_address_resolution_size_{0};
+ std::unordered_set<hci::AddressWithType> address_resolution_set_;
+};
+
struct ConnectionDescriptor {
CreationTime creation_time_;
TeardownTime teardown_time_;
@@ -676,8 +723,10 @@ class LeShimAclConnection
};
struct shim::legacy::Acl::impl {
- impl(uint8_t max_acceptlist_size)
- : shadow_acceptlist_(ShadowAcceptlist(max_acceptlist_size)) {}
+ impl(uint8_t max_acceptlist_size, uint8_t max_address_resolution_size)
+ : shadow_acceptlist_(ShadowAcceptlist(max_acceptlist_size)),
+ shadow_address_resolution_list_(
+ ShadowAddressResolutionList(max_address_resolution_size)) {}
std::map<HciHandle, std::unique_ptr<ClassicShimAclConnection>>
handle_to_classic_connection_map_;
@@ -688,6 +737,7 @@ struct shim::legacy::Acl::impl {
FixedQueue<std::unique_ptr<ConnectionDescriptor>>(kConnectionHistorySize);
ShadowAcceptlist shadow_acceptlist_;
+ ShadowAddressResolutionList shadow_address_resolution_list_;
bool IsClassicAcl(HciHandle handle) {
return handle_to_classic_connection_map_.find(handle) !=
@@ -858,6 +908,35 @@ struct shim::legacy::Acl::impl {
LOG_DEBUG("Cleared entire Le address acceptlist count:%zu", count);
}
+ void AddToAddressResolution(const hci::AddressWithType& address_with_type,
+ const std::array<uint8_t, 16>& peer_irk,
+ const std::array<uint8_t, 16>& local_irk) {
+ if (shadow_address_resolution_list_.IsFull()) {
+ LOG_WARN("Le Address Resolution list is full");
+ return;
+ }
+ // TODO This should really be added upon successful completion
+ shadow_address_resolution_list_.Add(address_with_type);
+ GetAclManager()->AddDeviceToResolvingList(address_with_type, peer_irk,
+ local_irk);
+ }
+
+ void RemoveFromAddressResolution(
+ const hci::AddressWithType& address_with_type) {
+ // TODO This should really be removed upon successful removal
+ if (!shadow_address_resolution_list_.Remove(address_with_type)) {
+ LOG_WARN("Unable to remove from Le Address Resolution list device:%s",
+ PRIVATE_ADDRESS(address_with_type));
+ }
+ GetAclManager()->RemoveDeviceFromResolvingList(address_with_type);
+ }
+
+ void ClearResolvingList() {
+ GetAclManager()->ClearResolvingList();
+ // TODO This should really be cleared after successful clear status
+ shadow_address_resolution_list_.Clear();
+ }
+
void DumpConnectionHistory() const {
std::vector<std::string> history =
connection_history_.ReadElementsAsString();
@@ -882,12 +961,23 @@ struct shim::legacy::Acl::impl {
}
auto acceptlist = shadow_acceptlist_.GetCopy();
LOG_DUMPSYS(fd,
- "Shadow le accept list size:%-3zu controller_max_size:%hhu",
+ "Shadow le accept list size:%-3zu "
+ "controller_max_size:%hhu",
acceptlist.size(),
controller_get_interface()->get_ble_acceptlist_size());
unsigned cnt = 0;
for (auto& entry : acceptlist) {
- LOG_DUMPSYS(fd, "%03u le acceptlist:%s", ++cnt, entry.ToString().c_str());
+ LOG_DUMPSYS(fd, " %03u %s", ++cnt, entry.ToString().c_str());
+ }
+ auto address_resolution_list = shadow_address_resolution_list_.GetCopy();
+ LOG_DUMPSYS(fd,
+ "Shadow le address resolution list size:%-3zu "
+ "controller_max_size:%hhu",
+ address_resolution_list.size(),
+ controller_get_interface()->get_ble_resolving_list_max_size());
+ cnt = 0;
+ for (auto& entry : address_resolution_list) {
+ LOG_DUMPSYS(fd, " %03u %s", ++cnt, entry.ToString().c_str());
}
}
#undef DUMPSYS_TAG
@@ -958,10 +1048,12 @@ void DumpsysAcl(int fd) {
common::ToString(link.peer_le_features_valid).c_str(),
bd_features_text(link.peer_le_features).c_str());
- LOG_DUMPSYS(fd, " [le] active_remote_addr:%s",
- link.active_remote_addr.ToString().c_str());
- LOG_DUMPSYS(fd, " [le] conn_addr:%s",
- link.conn_addr.ToString().c_str());
+ LOG_DUMPSYS(fd, " [le] active_remote_addr:%s[%s]",
+ link.active_remote_addr.ToString().c_str(),
+ AddressTypeText(link.active_remote_addr_type).c_str());
+ LOG_DUMPSYS(fd, " [le] conn_addr:%s[%s]",
+ link.conn_addr.ToString().c_str(),
+ AddressTypeText(link.conn_addr_type).c_str());
}
}
}
@@ -1037,6 +1129,7 @@ void DumpsysRecord(int fd) {
#undef DUMPSYS_TAG
void shim::legacy::Acl::Dump(int fd) const {
+ PAN_Dumpsys(fd);
DumpsysHid(fd);
DumpsysRecord(fd);
DumpsysAcl(fd);
@@ -1046,11 +1139,13 @@ void shim::legacy::Acl::Dump(int fd) const {
shim::legacy::Acl::Acl(os::Handler* handler,
const acl_interface_t& acl_interface,
- uint8_t max_acceptlist_size)
+ uint8_t max_acceptlist_size,
+ uint8_t max_address_resolution_size)
: handler_(handler), acl_interface_(acl_interface) {
ASSERT(handler_ != nullptr);
ValidateAclInterface(acl_interface_);
- pimpl_ = std::make_unique<Acl::impl>(max_acceptlist_size);
+ pimpl_ = std::make_unique<Acl::impl>(max_acceptlist_size,
+ max_address_resolution_size);
GetAclManager()->RegisterCallbacks(this, handler_);
GetAclManager()->RegisterLeCallbacks(this, handler_);
GetController()->RegisterCompletedMonitorAclPacketsCallback(
@@ -1451,3 +1546,21 @@ void shim::legacy::Acl::FinalShutdown() {
void shim::legacy::Acl::ClearAcceptList() {
handler_->CallOn(pimpl_.get(), &Acl::impl::clear_acceptlist);
}
+
+void shim::legacy::Acl::AddToAddressResolution(
+ const hci::AddressWithType& address_with_type,
+ const std::array<uint8_t, 16>& peer_irk,
+ const std::array<uint8_t, 16>& local_irk) {
+ handler_->CallOn(pimpl_.get(), &Acl::impl::AddToAddressResolution,
+ address_with_type, peer_irk, local_irk);
+}
+
+void shim::legacy::Acl::RemoveFromAddressResolution(
+ const hci::AddressWithType& address_with_type) {
+ handler_->CallOn(pimpl_.get(), &Acl::impl::RemoveFromAddressResolution,
+ address_with_type);
+}
+
+void shim::legacy::Acl::ClearAddressResolution() {
+ handler_->CallOn(pimpl_.get(), &Acl::impl::ClearResolvingList);
+}
diff --git a/main/shim/acl.h b/main/shim/acl.h
index da509422d..3fa9e41ff 100644
--- a/main/shim/acl.h
+++ b/main/shim/acl.h
@@ -42,7 +42,7 @@ class Acl : public hci::acl_manager::ConnectionCallbacks,
public LinkPolicyInterface {
public:
Acl(os::Handler* handler, const acl_interface_t& acl_interface,
- uint8_t max_acceptlist_size);
+ uint8_t max_acceptlist_size, uint8_t max_address_resolution_size);
~Acl();
// hci::acl_manager::ConnectionCallbacks
@@ -75,6 +75,14 @@ class Acl : public hci::acl_manager::ConnectionCallbacks,
void DisconnectClassic(uint16_t handle, tHCI_REASON reason) override;
void DisconnectLe(uint16_t handle, tHCI_REASON reason) override;
+ // Address Resolution List
+ void AddToAddressResolution(const hci::AddressWithType& address_with_type,
+ const std::array<uint8_t, 16>& peer_irk,
+ const std::array<uint8_t, 16>& local_irk);
+ void RemoveFromAddressResolution(
+ const hci::AddressWithType& address_with_type);
+ void ClearAddressResolution();
+
// LinkPolicyInterface
bool HoldMode(uint16_t hci_handle, uint16_t max_interval,
uint16_t min_interval) override;
diff --git a/main/shim/acl_api.cc b/main/shim/acl_api.cc
index 8c614885e..d7f47fbdc 100644
--- a/main/shim/acl_api.cc
+++ b/main/shim/acl_api.cc
@@ -107,3 +107,22 @@ void bluetooth::shim::ACL_ReadConnectionAddress(const RawAddress& pseudo_addr,
conn_addr = ToRawAddress(local_address.GetAddress());
*p_addr_type = static_cast<uint8_t>(local_address.GetAddressType());
}
+
+void bluetooth::shim::ACL_AddToAddressResolution(
+ const tBLE_BD_ADDR& legacy_address_with_type, const Octet16& peer_irk,
+ const Octet16& local_irk) {
+ Stack::GetInstance()->GetAcl()->AddToAddressResolution(
+ ToAddressWithType(legacy_address_with_type.bda,
+ legacy_address_with_type.type),
+ peer_irk, local_irk);
+}
+
+void bluetooth::shim::ACL_RemoveFromAddressResolution(
+ const tBLE_BD_ADDR& legacy_address_with_type) {
+ Stack::GetInstance()->GetAcl()->RemoveFromAddressResolution(ToAddressWithType(
+ legacy_address_with_type.bda, legacy_address_with_type.type));
+}
+
+void bluetooth::shim::ACL_ClearAddressResolution() {
+ Stack::GetInstance()->GetAcl()->ClearAddressResolution();
+}
diff --git a/main/shim/acl_api.h b/main/shim/acl_api.h
index 06afdd96d..ee9f3f4da 100644
--- a/main/shim/acl_api.h
+++ b/main/shim/acl_api.h
@@ -40,5 +40,12 @@ void ACL_IgnoreAllLeConnections();
void ACL_ReadConnectionAddress(const RawAddress& pseudo_addr,
RawAddress& conn_addr, uint8_t* p_addr_type);
+void ACL_AddToAddressResolution(const tBLE_BD_ADDR& legacy_address_with_type,
+ const Octet16& peer_irk,
+ const Octet16& local_irk);
+void ACL_RemoveFromAddressResolution(
+ const tBLE_BD_ADDR& legacy_address_with_type);
+void ACL_ClearAddressResolution();
+
} // namespace shim
} // namespace bluetooth
diff --git a/main/shim/btm_api.cc b/main/shim/btm_api.cc
index 6d790498a..d26dd6c18 100644
--- a/main/shim/btm_api.cc
+++ b/main/shim/btm_api.cc
@@ -924,30 +924,6 @@ void bluetooth::shim::BTM_RemoveEirService(uint32_t* p_eir_uuid,
CHECK(p_eir_uuid != nullptr);
}
-uint8_t bluetooth::shim::BTM_GetEirSupportedServices(uint32_t* p_eir_uuid,
- uint8_t** p,
- uint8_t max_num_uuid16,
- uint8_t* p_num_uuid16) {
- LOG_INFO("UNIMPLEMENTED %s", __func__);
- CHECK(p_eir_uuid != nullptr);
- CHECK(p != nullptr);
- CHECK(*p != nullptr);
- CHECK(p_num_uuid16 != nullptr);
- return BTM_NO_RESOURCES;
-}
-
-uint8_t bluetooth::shim::BTM_GetEirUuidList(uint8_t* p_eir, size_t eir_len,
- uint8_t uuid_size,
- uint8_t* p_num_uuid,
- uint8_t* p_uuid_list,
- uint8_t max_num_uuid) {
- LOG_INFO("UNIMPLEMENTED %s", __func__);
- CHECK(p_eir != nullptr);
- CHECK(p_num_uuid != nullptr);
- CHECK(p_uuid_list != nullptr);
- return 0;
-}
-
void bluetooth::shim::BTM_SecAddBleDevice(const RawAddress& bd_addr,
tBT_DEVICE_TYPE dev_type,
tBLE_ADDR_TYPE addr_type) {
diff --git a/main/shim/btm_api.h b/main/shim/btm_api.h
index cd3d08a3d..c276d012a 100644
--- a/main/shim/btm_api.h
+++ b/main/shim/btm_api.h
@@ -366,53 +366,6 @@ void BTM_RemoveEirService(uint32_t* p_eir_uuid, uint16_t uuid16);
/*******************************************************************************
*
- * Function BTM_GetEirSupportedServices
- *
- * Description This function is called to get UUID list from bit map UUID
- * list.
- *
- * Parameters p_eir_uuid - bit mask of UUID list for EIR
- * p - reference of current pointer of EIR
- * max_num_uuid16 - max number of UUID can be written in EIR
- * num_uuid16 - number of UUID have been written in EIR
- *
- * Returns HCI_EIR_MORE_16BITS_UUID_TYPE, if it has more than max
- * HCI_EIR_COMPLETE_16BITS_UUID_TYPE, otherwise
- *
- ******************************************************************************/
-uint8_t BTM_GetEirSupportedServices(uint32_t* p_eir_uuid, uint8_t** p,
- uint8_t max_num_uuid16,
- uint8_t* p_num_uuid16);
-
-/*******************************************************************************
- *
- * Function BTM_GetEirUuidList
- *
- * Description This function parses EIR and returns UUID list.
- *
- * Parameters p_eir - EIR
- * eirl_len - EIR len
- * uuid_size - Uuid::kNumBytes16, Uuid::kNumBytes32,
- * Uuid::kNumBytes128
- * p_num_uuid - return number of UUID in found list
- * p_uuid_list - return UUID 16-bit list
- * max_num_uuid - maximum number of UUID to be returned
- *
- * Returns 0 - if not found
- * HCI_EIR_COMPLETE_16BITS_UUID_TYPE
- * HCI_EIR_MORE_16BITS_UUID_TYPE
- * HCI_EIR_COMPLETE_32BITS_UUID_TYPE
- * HCI_EIR_MORE_32BITS_UUID_TYPE
- * HCI_EIR_COMPLETE_128BITS_UUID_TYPE
- * HCI_EIR_MORE_128BITS_UUID_TYPE
- *
- ******************************************************************************/
-uint8_t BTM_GetEirUuidList(uint8_t* p_eir, size_t eir_len, uint8_t uuid_size,
- uint8_t* p_num_uuid, uint8_t* p_uuid_list,
- uint8_t max_num_uuid);
-
-/*******************************************************************************
- *
* Function BTM_SecAddBleDevice
*
* Description Add/modify device. This function will be normally called
diff --git a/main/shim/config.cc b/main/shim/config.cc
index a2ee4adf0..732d022a6 100644
--- a/main/shim/config.cc
+++ b/main/shim/config.cc
@@ -156,6 +156,10 @@ std::vector<std::string> BtifConfigInterface::GetPersistentDevices() {
return GetStorage()->GetConfigCache()->GetPersistentSections();
}
+void BtifConfigInterface::ConvertEncryptOrDecryptKeyIfNeeded() {
+ GetStorage()->GetConfigCache()->ConvertEncryptOrDecryptKeyIfNeeded();
+}
+
void BtifConfigInterface::Save() { GetStorage()->SaveDelayed(); }
void BtifConfigInterface::Flush() { GetStorage()->SaveImmediately(); }
diff --git a/main/shim/config.h b/main/shim/config.h
index 6402bbf74..b6b223cbe 100644
--- a/main/shim/config.h
+++ b/main/shim/config.h
@@ -53,6 +53,7 @@ class BtifConfigInterface {
static bool RemoveProperty(const std::string& section,
const std::string& key);
static std::vector<std::string> GetPersistentDevices();
+ static void ConvertEncryptOrDecryptKeyIfNeeded();
static void Save();
static void Flush();
static void Clear();
diff --git a/main/shim/hci_layer.cc b/main/shim/hci_layer.cc
index 8d5c63820..8318aded3 100644
--- a/main/shim/hci_layer.cc
+++ b/main/shim/hci_layer.cc
@@ -361,11 +361,11 @@ void OnTransmitPacketStatus(command_status_cb status_callback, void* context,
status_callback(status, static_cast<BT_HDR*>(command->Release()), context);
}
-static void transmit_command(BT_HDR* command,
+static void transmit_command(const BT_HDR* command,
command_complete_cb complete_callback,
command_status_cb status_callback, void* context) {
CHECK(command != nullptr);
- uint8_t* data = command->data + command->offset;
+ const uint8_t* data = command->data + command->offset;
size_t len = command->len;
CHECK(len >= (kCommandOpcodeSize + kCommandLengthSize));
@@ -395,7 +395,7 @@ static void transmit_command(BT_HDR* command,
std::move(packet),
bluetooth::shim::GetGdShimHandler()->BindOnce(
OnTransmitPacketCommandComplete, complete_callback, context));
- osi_free(command);
+ osi_free(const_cast<void*>(static_cast<const void*>(command)));
}
}
@@ -406,7 +406,8 @@ static void transmit_fragment(const uint8_t* stream, size_t length) {
handle_with_flags >> 12 & 0b11);
auto bc_flag =
static_cast<bluetooth::hci::BroadcastFlag>(handle_with_flags >> 14);
- uint16_t handle = handle_with_flags & 0xEFF;
+ uint16_t handle = handle_with_flags & 0xFFF;
+ ASSERT_LOG(handle <= 0xEFF, "Require handle <= 0xEFF, but is 0x%X", handle);
length -= 2;
// skip data total length
stream += 2;
@@ -421,7 +422,8 @@ static void transmit_fragment(const uint8_t* stream, size_t length) {
static void transmit_sco_fragment(const uint8_t* stream, size_t length) {
uint16_t handle_with_flags;
STREAM_TO_UINT16(handle_with_flags, stream);
- uint16_t handle = handle_with_flags & 0xEFF;
+ uint16_t handle = handle_with_flags & 0xFFF;
+ ASSERT_LOG(handle <= 0xEFF, "Require handle <= 0xEFF, but is 0x%X", handle);
length -= 2;
// skip data total length
stream += 1;
@@ -442,7 +444,8 @@ static void transmit_iso_fragment(const uint8_t* stream, size_t length) {
handle_with_flags >> 12 & 0b11);
auto ts_flag =
static_cast<bluetooth::hci::TimeStampFlag>(handle_with_flags >> 14);
- uint16_t handle = handle_with_flags & 0xEFF;
+ uint16_t handle = handle_with_flags & 0xFFF;
+ ASSERT_LOG(handle <= 0xEFF, "Require handle <= 0xEFF, but is 0x%X", handle);
length -= 2;
// skip data total length
stream += 2;
@@ -674,7 +677,7 @@ void OnRustTransmitPacketStatus(command_status_cb status_callback,
status_callback(status, static_cast<BT_HDR*>(command->Release()), context);
}
-static void transmit_command(BT_HDR* command,
+static void transmit_command(const BT_HDR* command,
command_complete_cb complete_callback,
command_status_cb status_callback, void* context) {
CHECK(command != nullptr);
@@ -702,7 +705,7 @@ static void transmit_command(BT_HDR* command,
::rust::Slice(data, len),
std::make_unique<u8SliceOnceCallback>(BindOnce(
OnRustTransmitPacketCommandComplete, complete_callback, context)));
- osi_free(command);
+ osi_free(const_cast<void*>(static_cast<const void*>(command)));
}
}
@@ -772,7 +775,7 @@ static void set_data_cb(
send_data_upwards = std::move(send_data_cb);
}
-static void transmit_command(BT_HDR* command,
+static void transmit_command(const BT_HDR* command,
command_complete_cb complete_callback,
command_status_cb status_callback, void* context) {
if (bluetooth::common::init_flags::gd_rust_is_enabled()) {
@@ -794,7 +797,7 @@ static void command_status_callback(uint8_t status, BT_HDR* command,
"transmit_command_futured should only send command complete opcode");
}
-static future_t* transmit_command_futured(BT_HDR* command) {
+static future_t* transmit_command_futured(const BT_HDR* command) {
future_t* future = future_new();
transmit_command(command, command_complete_callback, command_status_callback,
future);
diff --git a/main/shim/stack.cc b/main/shim/stack.cc
index 4a3d200f1..88fb82655 100644
--- a/main/shim/stack.cc
+++ b/main/shim/stack.cc
@@ -179,7 +179,8 @@ void Stack::StartEverything() {
if (!common::init_flags::gd_core_is_enabled()) {
acl_ = new legacy::Acl(
stack_handler_, legacy::GetAclInterface(),
- controller_get_interface()->get_ble_acceptlist_size());
+ controller_get_interface()->get_ble_acceptlist_size(),
+ controller_get_interface()->get_ble_resolving_list_max_size());
}
}
if (!common::init_flags::gd_core_is_enabled()) {
diff --git a/main/test/main_shim_test.cc b/main/test/main_shim_test.cc
index 7f402413d..6d206e1ce 100644
--- a/main/test/main_shim_test.cc
+++ b/main/test/main_shim_test.cc
@@ -61,6 +61,8 @@ using namespace testing;
namespace test = bluetooth::hci::testing;
const uint8_t kMaxLeAcceptlistSize = 16;
+const uint8_t kMaxAddressResolutionSize = kMaxLeAcceptlistSize;
+
std::map<std::string, int> mock_function_count_map;
tL2C_CB l2cb;
tBTM_CB btm_cb;
@@ -304,7 +306,8 @@ class MainShimTest : public testing::Test {
UnregisterCompletedMonitorAclPacketsCallback)
.Times(1);
return std::make_unique<shim::legacy::Acl>(handler_, GetMockAclInterface(),
- kMaxLeAcceptlistSize);
+ kMaxLeAcceptlistSize,
+ kMaxAddressResolutionSize);
}
};
diff --git a/profile/avrcp/device.cc b/profile/avrcp/device.cc
index c5cdc55b2..708fc2401 100644
--- a/profile/avrcp/device.cc
+++ b/profile/avrcp/device.cc
@@ -440,7 +440,6 @@ void Device::TrackChangedNotificationResponse(uint8_t label, bool interim,
std::string curr_song_id,
std::vector<SongInfo> song_list) {
DEVICE_VLOG(1) << __func__;
- uint64_t uid = 0;
if (interim) {
track_changed_ = Notification(true, label);
@@ -449,9 +448,32 @@ void Device::TrackChangedNotificationResponse(uint8_t label, bool interim,
return;
}
+ if (!interim) {
+ if (curr_song_id.empty()) {
+ // CHANGED response is only defined when there is media selected
+ // for playing.
+ return;
+ }
+ active_labels_.erase(label);
+ track_changed_ = Notification(false, 0);
+ }
+
+ // Case for browsing not supported;
+ // PTS BV-04-C and BV-5-C assume browsing not supported
+ if (stack_config_get_interface()->get_pts_avrcp_test()) {
+ DEVICE_LOG(WARNING) << __func__ << ": pts test mode";
+ uint64_t uid = curr_song_id.empty() ? 0xffffffffffffffff : 0;
+ auto response =
+ RegisterNotificationResponseBuilder::MakeTrackChangedBuilder(interim,
+ uid);
+ send_message_cb_.Run(label, false, std::move(response));
+ return;
+ }
+
// Anytime we use the now playing list, update our map so that its always
// current
now_playing_ids_.clear();
+ uint64_t uid = 0;
for (const SongInfo& song : song_list) {
now_playing_ids_.insert(song.media_id);
if (curr_song_id == song.media_id) {
@@ -461,22 +483,14 @@ void Device::TrackChangedNotificationResponse(uint8_t label, bool interim,
}
}
- if (curr_song_id == "") {
- DEVICE_LOG(WARNING) << "Empty media ID";
- uid = 0;
- if (stack_config_get_interface()->get_pts_avrcp_test()) {
- DEVICE_LOG(WARNING) << __func__ << ": pts test mode";
- uid = 0xffffffffffffffff;
- }
+ if (uid == 0) {
+ // uid 0 is not valid here when browsing is supported
+ DEVICE_LOG(ERROR) << "No match for media ID found";
}
auto response = RegisterNotificationResponseBuilder::MakeTrackChangedBuilder(
interim, uid);
send_message_cb_.Run(label, false, std::move(response));
- if (!interim) {
- active_labels_.erase(label);
- track_changed_ = Notification(false, 0);
- }
}
void Device::PlaybackStatusNotificationResponse(uint8_t label, bool interim,
diff --git a/service/bluetooth_interface.cc b/service/bluetooth_interface.cc
new file mode 100644
index 000000000..ee58ef310
--- /dev/null
+++ b/service/bluetooth_interface.cc
@@ -0,0 +1,874 @@
+/******************************************************************************
+ *
+ * Copyright 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.c
+ *
+ * Description: Bluetooth HAL implementation
+ *
+ ******************************************************************************/
+
+#define LOG_TAG "bt_btif"
+
+#include <base/logging.h>
+#include <hardware/bluetooth.h>
+#include <hardware/bluetooth_headset_interface.h>
+#include <hardware/bt_av.h>
+#include <hardware/bt_csis.h>
+#include <hardware/bt_gatt.h>
+#include <hardware/bt_hd.h>
+#include <hardware/bt_hearing_aid.h>
+#include <hardware/bt_hf_client.h>
+#include <hardware/bt_hh.h>
+#include <hardware/bt_le_audio.h>
+#include <hardware/bt_pan.h>
+#include <hardware/bt_rc.h>
+#include <hardware/bt_sdp.h>
+#include <hardware/bt_sock.h>
+#include <hardware/bt_vc.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "bta/include/bta_csis_api.h"
+#include "bta/include/bta_hearing_aid_api.h"
+#include "bta/include/bta_hf_client_api.h"
+#include "bta/include/bta_le_audio_api.h"
+#include "btif/avrcp/avrcp_service.h"
+#include "btif/include/stack_manager.h"
+#include "btif_a2dp.h"
+#include "btif_activity_attribution.h"
+#include "btif_api.h"
+#include "btif_av.h"
+#include "btif_bqr.h"
+#include "btif_config.h"
+#include "btif_debug.h"
+#include "btif_debug_btsnoop.h"
+#include "btif_debug_conn.h"
+#include "btif_hf.h"
+#include "btif_keystore.h"
+#include "btif_metrics_logging.h"
+#include "btif_storage.h"
+#include "common/address_obfuscator.h"
+#include "common/metric_id_allocator.h"
+#include "common/metrics.h"
+#include "common/os_utils.h"
+#include "device/include/interop.h"
+#include "gd/common/init_flags.h"
+#include "main/shim/dumpsys.h"
+#include "main/shim/shim.h"
+#include "osi/include/alarm.h"
+#include "osi/include/allocation_tracker.h"
+#include "osi/include/allocator.h"
+#include "osi/include/log.h"
+#include "osi/include/osi.h"
+#include "osi/include/wakelock.h"
+#include "stack/gatt/connection_manager.h"
+#include "stack/include/avdt_api.h"
+#include "stack/include/btm_api.h"
+#include "stack/include/btu.h"
+#include "types/raw_address.h"
+
+using bluetooth::csis::CsisClientInterface;
+using bluetooth::hearing_aid::HearingAidInterface;
+using bluetooth::le_audio::LeAudioClientInterface;
+using bluetooth::vc::VolumeControlInterface;
+
+/*******************************************************************************
+ * Static variables
+ ******************************************************************************/
+
+static bt_callbacks_t* bt_hal_cbacks = NULL;
+bool restricted_mode = false;
+bool common_criteria_mode = false;
+const int CONFIG_COMPARE_ALL_PASS = 0b11;
+int common_criteria_config_compare_result = CONFIG_COMPARE_ALL_PASS;
+bool is_local_device_atv = false;
+
+/*******************************************************************************
+ * Externs
+ ******************************************************************************/
+
+/* list all extended interfaces here */
+
+/* handsfree profile - client */
+extern const bthf_client_interface_t* btif_hf_client_get_interface();
+/* advanced audio profile */
+extern const btav_source_interface_t* btif_av_get_src_interface();
+extern const btav_sink_interface_t* btif_av_get_sink_interface();
+/*rfc l2cap*/
+extern const btsock_interface_t* btif_sock_get_interface();
+/* hid host profile */
+extern const bthh_interface_t* btif_hh_get_interface();
+/* hid device profile */
+extern const bthd_interface_t* btif_hd_get_interface();
+/*pan*/
+extern const btpan_interface_t* btif_pan_get_interface();
+/* gatt */
+extern const btgatt_interface_t* btif_gatt_get_interface();
+/* avrc target */
+extern const btrc_interface_t* btif_rc_get_interface();
+/* avrc controller */
+extern const btrc_ctrl_interface_t* btif_rc_ctrl_get_interface();
+/*SDP search client*/
+extern const btsdp_interface_t* btif_sdp_get_interface();
+/*Hearing Aid client*/
+extern HearingAidInterface* btif_hearing_aid_get_interface();
+/* LeAudio testi client */
+extern LeAudioClientInterface* btif_le_audio_get_interface();
+/* Coordinated Set Service Client */
+extern CsisClientInterface* btif_csis_client_get_interface();
+/* Volume Control client */
+extern VolumeControlInterface* btif_volume_control_get_interface();
+
+/*******************************************************************************
+ * Functions
+ ******************************************************************************/
+
+static bool interface_ready(void) { return bt_hal_cbacks != NULL; }
+void set_hal_cbacks(bt_callbacks_t* callbacks) { bt_hal_cbacks = callbacks; }
+
+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 HAL INTERFACE FUNCTIONS
+ *
+ ****************************************************************************/
+
+static int init(bt_callbacks_t* callbacks, bool start_restricted,
+ bool is_common_criteria_mode, int config_compare_result,
+ const char** init_flags, bool is_atv) {
+ LOG_INFO(
+ "%s: start restricted = %d ; common criteria mode = %d, config compare "
+ "result = %d",
+ __func__, start_restricted, is_common_criteria_mode,
+ config_compare_result);
+
+ bluetooth::common::InitFlags::Load(init_flags);
+
+ if (interface_ready()) return BT_STATUS_DONE;
+
+#ifdef BLUEDROID_DEBUG
+ allocation_tracker_init();
+#endif
+
+ set_hal_cbacks(callbacks);
+
+ restricted_mode = start_restricted;
+ common_criteria_mode = is_common_criteria_mode;
+ common_criteria_config_compare_result = config_compare_result;
+ is_local_device_atv = is_atv;
+
+ stack_manager_get_interface()->init_stack();
+ btif_debug_init();
+ return BT_STATUS_SUCCESS;
+}
+
+static int enable() {
+ if (!interface_ready()) return BT_STATUS_NOT_READY;
+
+ stack_manager_get_interface()->start_up_stack_async();
+ return BT_STATUS_SUCCESS;
+}
+
+static int disable(void) {
+ if (!interface_ready()) return BT_STATUS_NOT_READY;
+
+ stack_manager_get_interface()->shut_down_stack_async();
+ return BT_STATUS_SUCCESS;
+}
+
+static void cleanup(void) { stack_manager_get_interface()->clean_up_stack(); }
+
+bool is_restricted_mode() { return restricted_mode; }
+bool is_common_criteria_mode() {
+ return is_bluetooth_uid() && common_criteria_mode;
+}
+// if common criteria mode disable, will always return
+// CONFIG_COMPARE_ALL_PASS(0b11) indicate don't check config checksum.
+int get_common_criteria_config_compare_result() {
+ return is_common_criteria_mode() ? common_criteria_config_compare_result
+ : CONFIG_COMPARE_ALL_PASS;
+}
+
+bool is_atv_device() { return is_local_device_atv; }
+
+static int get_adapter_properties(void) {
+ if (!btif_is_enabled()) return BT_STATUS_NOT_READY;
+
+ do_in_main_thread(FROM_HERE, base::BindOnce(btif_get_adapter_properties));
+ return BT_STATUS_SUCCESS;
+}
+
+static int get_adapter_property(bt_property_type_t type) {
+ /* Allow get_adapter_property only for BDADDR and BDNAME if BT is disabled */
+ if (!btif_is_enabled() && (type != BT_PROPERTY_BDADDR) &&
+ (type != BT_PROPERTY_BDNAME) && (type != BT_PROPERTY_CLASS_OF_DEVICE))
+ return BT_STATUS_NOT_READY;
+
+ do_in_main_thread(FROM_HERE, base::BindOnce(btif_get_adapter_property, type));
+ return BT_STATUS_SUCCESS;
+}
+
+static int set_adapter_property(const bt_property_t* property) {
+ if (!btif_is_enabled()) return BT_STATUS_NOT_READY;
+
+ switch (property->type) {
+ case BT_PROPERTY_BDNAME:
+ case BT_PROPERTY_ADAPTER_SCAN_MODE:
+ case BT_PROPERTY_ADAPTER_DISCOVERY_TIMEOUT:
+ case BT_PROPERTY_CLASS_OF_DEVICE:
+ case BT_PROPERTY_LOCAL_IO_CAPS:
+ case BT_PROPERTY_LOCAL_IO_CAPS_BLE:
+ break;
+ default:
+ return BT_STATUS_FAIL;
+ }
+
+ do_in_main_thread(FROM_HERE, base::BindOnce(
+ [](bt_property_t* property) {
+ btif_set_adapter_property(property);
+ osi_free(property);
+ },
+ property_deep_copy(property)));
+ return BT_STATUS_SUCCESS;
+}
+
+int get_remote_device_properties(RawAddress* remote_addr) {
+ if (!btif_is_enabled()) return BT_STATUS_NOT_READY;
+
+ do_in_main_thread(FROM_HERE, base::BindOnce(btif_get_remote_device_properties,
+ *remote_addr));
+ return BT_STATUS_SUCCESS;
+}
+
+int get_remote_device_property(RawAddress* remote_addr,
+ bt_property_type_t type) {
+ if (!btif_is_enabled()) return BT_STATUS_NOT_READY;
+
+ do_in_main_thread(FROM_HERE, base::BindOnce(btif_get_remote_device_property,
+ *remote_addr, type));
+ return BT_STATUS_SUCCESS;
+}
+
+int set_remote_device_property(RawAddress* remote_addr,
+ const bt_property_t* property) {
+ if (!btif_is_enabled()) return BT_STATUS_NOT_READY;
+
+ do_in_main_thread(
+ FROM_HERE, base::BindOnce(
+ [](RawAddress remote_addr, bt_property_t* property) {
+ btif_set_remote_device_property(&remote_addr, property);
+ osi_free(property);
+ },
+ *remote_addr, property_deep_copy(property)));
+ return BT_STATUS_SUCCESS;
+}
+
+int get_remote_services(RawAddress* remote_addr, int transport) {
+ if (!interface_ready()) return BT_STATUS_NOT_READY;
+
+ do_in_main_thread(FROM_HERE, base::BindOnce(btif_dm_get_remote_services,
+ *remote_addr, transport));
+ return BT_STATUS_SUCCESS;
+}
+
+static int start_discovery(void) {
+ if (!interface_ready()) return BT_STATUS_NOT_READY;
+
+ do_in_main_thread(FROM_HERE, base::BindOnce(btif_dm_start_discovery));
+ return BT_STATUS_SUCCESS;
+}
+
+static int cancel_discovery(void) {
+ if (!interface_ready()) return BT_STATUS_NOT_READY;
+
+ do_in_main_thread(FROM_HERE, base::BindOnce(btif_dm_cancel_discovery));
+ return BT_STATUS_SUCCESS;
+}
+
+static int create_bond(const RawAddress* bd_addr, int transport) {
+ if (!interface_ready()) return BT_STATUS_NOT_READY;
+ if (btif_dm_pairing_is_busy()) return BT_STATUS_BUSY;
+
+ do_in_main_thread(FROM_HERE,
+ base::BindOnce(btif_dm_create_bond, *bd_addr, transport));
+ return BT_STATUS_SUCCESS;
+}
+
+static int create_bond_out_of_band(const RawAddress* bd_addr, int transport,
+ const bt_oob_data_t* p192_data,
+ const bt_oob_data_t* p256_data) {
+ if (!interface_ready()) return BT_STATUS_NOT_READY;
+ if (btif_dm_pairing_is_busy()) return BT_STATUS_BUSY;
+
+ do_in_main_thread(FROM_HERE,
+ base::BindOnce(btif_dm_create_bond_out_of_band, *bd_addr,
+ transport, *p192_data, *p256_data));
+ return BT_STATUS_SUCCESS;
+}
+
+static int generate_local_oob_data(tBT_TRANSPORT transport) {
+ LOG_INFO("%s", __func__);
+ if (!interface_ready()) return BT_STATUS_NOT_READY;
+
+ return do_in_main_thread(
+ FROM_HERE, base::BindOnce(btif_dm_generate_local_oob_data, transport));
+}
+
+static int cancel_bond(const RawAddress* bd_addr) {
+ if (!interface_ready()) return BT_STATUS_NOT_READY;
+
+ do_in_main_thread(FROM_HERE, base::BindOnce(btif_dm_cancel_bond, *bd_addr));
+ return BT_STATUS_SUCCESS;
+}
+
+static int remove_bond(const RawAddress* bd_addr) {
+ if (is_restricted_mode() && !btif_storage_is_restricted_device(bd_addr))
+ return BT_STATUS_SUCCESS;
+
+ if (!interface_ready()) return BT_STATUS_NOT_READY;
+
+ do_in_main_thread(FROM_HERE, base::BindOnce(btif_dm_remove_bond, *bd_addr));
+ return BT_STATUS_SUCCESS;
+}
+
+static int get_connection_state(const RawAddress* bd_addr) {
+ if (!interface_ready()) return 0;
+
+ return btif_dm_get_connection_state(bd_addr);
+}
+
+static int pin_reply(const RawAddress* bd_addr, uint8_t accept, uint8_t pin_len,
+ bt_pin_code_t* pin_code) {
+ if (!interface_ready()) return BT_STATUS_NOT_READY;
+ if (pin_code == nullptr || pin_len > PIN_CODE_LEN) return BT_STATUS_FAIL;
+
+ do_in_main_thread(FROM_HERE, base::BindOnce(btif_dm_pin_reply, *bd_addr,
+ accept, pin_len, *pin_code));
+ return BT_STATUS_SUCCESS;
+}
+
+static int ssp_reply(const RawAddress* bd_addr, bt_ssp_variant_t variant,
+ uint8_t accept, uint32_t passkey) {
+ if (!interface_ready()) return BT_STATUS_NOT_READY;
+ if (variant == BT_SSP_VARIANT_PASSKEY_ENTRY) return BT_STATUS_FAIL;
+
+ do_in_main_thread(
+ FROM_HERE, base::BindOnce(btif_dm_ssp_reply, *bd_addr, variant, accept));
+ return BT_STATUS_SUCCESS;
+}
+
+static int read_energy_info() {
+ if (!interface_ready()) return BT_STATUS_NOT_READY;
+
+ do_in_main_thread(FROM_HERE, base::BindOnce(btif_dm_read_energy_info));
+ return BT_STATUS_SUCCESS;
+}
+
+static void dump(int fd, const char** arguments) {
+ btif_debug_conn_dump(fd);
+ btif_debug_bond_event_dump(fd);
+ btif_debug_a2dp_dump(fd);
+ btif_debug_av_dump(fd);
+ bta_debug_av_dump(fd);
+ stack_debug_avdtp_api_dump(fd);
+ bluetooth::avrcp::AvrcpService::DebugDump(fd);
+ btif_debug_config_dump(fd);
+ BTA_HfClientDumpStatistics(fd);
+ wakelock_debug_dump(fd);
+ osi_allocator_debug_dump(fd);
+ alarm_debug_dump(fd);
+ bluetooth::csis::CsisClient::DebugDump(fd);
+ HearingAid::DebugDump(fd);
+ LeAudioClient::DebugDump(fd);
+ connection_manager::dump(fd);
+ bluetooth::bqr::DebugDump(fd);
+ if (bluetooth::shim::is_any_gd_enabled()) {
+ bluetooth::shim::Dump(fd, arguments);
+ } else {
+#if (BTSNOOP_MEM == TRUE)
+ btif_debug_btsnoop_dump(fd);
+#endif
+ }
+}
+
+static void dumpMetrics(std::string* output) {
+ bluetooth::common::BluetoothMetricsLogger::GetInstance()->WriteString(output);
+}
+
+static const void* get_profile_interface(const char* profile_id) {
+ LOG_INFO("%s: id = %s", __func__, profile_id);
+
+ /* sanity check */
+ if (!interface_ready()) return NULL;
+
+ /* check for supported profile interfaces */
+ if (is_profile(profile_id, BT_PROFILE_HANDSFREE_ID))
+ return bluetooth::headset::GetInterface();
+
+ if (is_profile(profile_id, BT_PROFILE_HANDSFREE_CLIENT_ID))
+ return btif_hf_client_get_interface();
+
+ if (is_profile(profile_id, BT_PROFILE_SOCKETS_ID))
+ return btif_sock_get_interface();
+
+ if (is_profile(profile_id, BT_PROFILE_PAN_ID))
+ return btif_pan_get_interface();
+
+ if (is_profile(profile_id, BT_PROFILE_ADVANCED_AUDIO_ID))
+ return btif_av_get_src_interface();
+
+ if (is_profile(profile_id, BT_PROFILE_ADVANCED_AUDIO_SINK_ID))
+ return btif_av_get_sink_interface();
+
+ if (is_profile(profile_id, BT_PROFILE_HIDHOST_ID))
+ return btif_hh_get_interface();
+
+ if (is_profile(profile_id, BT_PROFILE_HIDDEV_ID))
+ return btif_hd_get_interface();
+
+ if (is_profile(profile_id, BT_PROFILE_SDP_CLIENT_ID))
+ return btif_sdp_get_interface();
+
+ if (is_profile(profile_id, BT_PROFILE_GATT_ID))
+ return btif_gatt_get_interface();
+
+ if (is_profile(profile_id, BT_PROFILE_AV_RC_ID))
+ return btif_rc_get_interface();
+
+ if (is_profile(profile_id, BT_PROFILE_AV_RC_CTRL_ID))
+ return btif_rc_ctrl_get_interface();
+
+ if (is_profile(profile_id, BT_PROFILE_HEARING_AID_ID))
+ return btif_hearing_aid_get_interface();
+
+ if (is_profile(profile_id, BT_KEYSTORE_ID))
+ return bluetooth::bluetooth_keystore::getBluetoothKeystoreInterface();
+
+ if (is_profile(profile_id, BT_ACTIVITY_ATTRIBUTION_ID)) {
+ return bluetooth::activity_attribution::get_activity_attribution_instance();
+ }
+
+ if (is_profile(profile_id, BT_PROFILE_LE_AUDIO_ID))
+ return btif_le_audio_get_interface();
+
+ if (is_profile(profile_id, BT_PROFILE_VC_ID))
+ return btif_volume_control_get_interface();
+
+ if (is_profile(profile_id, BT_PROFILE_CSIS_CLIENT_ID))
+ return btif_csis_client_get_interface();
+
+ return NULL;
+}
+
+int dut_mode_configure(uint8_t enable) {
+ if (!interface_ready()) return BT_STATUS_NOT_READY;
+ if (!stack_manager_get_interface()->get_stack_is_running())
+ return BT_STATUS_NOT_READY;
+
+ do_in_main_thread(FROM_HERE, base::BindOnce(btif_dut_mode_configure, enable));
+ return BT_STATUS_SUCCESS;
+}
+
+int dut_mode_send(uint16_t opcode, uint8_t* buf, uint8_t len) {
+ if (!interface_ready()) return BT_STATUS_NOT_READY;
+ if (!btif_is_dut_mode()) return BT_STATUS_FAIL;
+
+ uint8_t* copy = (uint8_t*)osi_calloc(len);
+ memcpy(copy, buf, len);
+
+ do_in_main_thread(FROM_HERE,
+ base::BindOnce(
+ [](uint16_t opcode, uint8_t* buf, uint8_t len) {
+ btif_dut_mode_send(opcode, buf, len);
+ osi_free(buf);
+ },
+ opcode, copy, len));
+ return BT_STATUS_SUCCESS;
+}
+
+int le_test_mode(uint16_t opcode, uint8_t* buf, uint8_t len) {
+ if (!interface_ready()) return BT_STATUS_NOT_READY;
+
+ switch (opcode) {
+ case HCI_BLE_TRANSMITTER_TEST:
+ if (len != 3) return BT_STATUS_PARM_INVALID;
+ do_in_main_thread(FROM_HERE, base::BindOnce(btif_ble_transmitter_test,
+ buf[0], buf[1], buf[2]));
+ break;
+ case HCI_BLE_RECEIVER_TEST:
+ if (len != 1) return BT_STATUS_PARM_INVALID;
+ do_in_main_thread(FROM_HERE,
+ base::BindOnce(btif_ble_receiver_test, buf[0]));
+ break;
+ case HCI_BLE_TEST_END:
+ do_in_main_thread(FROM_HERE, base::BindOnce(btif_ble_test_end));
+ break;
+ default:
+ return BT_STATUS_UNSUPPORTED;
+ }
+ return BT_STATUS_SUCCESS;
+}
+
+static bt_os_callouts_t* wakelock_os_callouts_saved = nullptr;
+
+static int acquire_wake_lock_cb(const char* lock_name) {
+ return do_in_jni_thread(
+ FROM_HERE, base::Bind(base::IgnoreResult(
+ wakelock_os_callouts_saved->acquire_wake_lock),
+ lock_name));
+}
+
+static int release_wake_lock_cb(const char* lock_name) {
+ return do_in_jni_thread(
+ FROM_HERE, base::Bind(base::IgnoreResult(
+ wakelock_os_callouts_saved->release_wake_lock),
+ lock_name));
+}
+
+static bt_os_callouts_t wakelock_os_callouts_jni = {
+ sizeof(wakelock_os_callouts_jni),
+ nullptr /* not used */,
+ acquire_wake_lock_cb,
+ release_wake_lock_cb,
+};
+
+static int set_os_callouts(bt_os_callouts_t* callouts) {
+ wakelock_os_callouts_saved = callouts;
+ wakelock_set_os_callouts(&wakelock_os_callouts_jni);
+ return BT_STATUS_SUCCESS;
+}
+
+static int config_clear(void) {
+ LOG_INFO("%s", __func__);
+ return btif_config_clear() ? BT_STATUS_SUCCESS : BT_STATUS_FAIL;
+}
+
+static bluetooth::avrcp::ServiceInterface* get_avrcp_service(void) {
+ return bluetooth::avrcp::AvrcpService::GetServiceInterface();
+}
+
+static std::string obfuscate_address(const RawAddress& address) {
+ return bluetooth::common::AddressObfuscator::GetInstance()->Obfuscate(
+ address);
+}
+
+static int get_metric_id(const RawAddress& address) {
+ return allocate_metric_id_from_metric_id_allocator(address);
+}
+
+static int set_dynamic_audio_buffer_size(int codec, int size) {
+ return btif_set_dynamic_audio_buffer_size(codec, size);
+}
+
+EXPORT_SYMBOL bt_interface_t bluetoothInterface = {
+ sizeof(bluetoothInterface),
+ init,
+ enable,
+ disable,
+ cleanup,
+ get_adapter_properties,
+ get_adapter_property,
+ set_adapter_property,
+ get_remote_device_properties,
+ get_remote_device_property,
+ set_remote_device_property,
+ nullptr,
+ get_remote_services,
+ start_discovery,
+ cancel_discovery,
+ create_bond,
+ create_bond_out_of_band,
+ remove_bond,
+ cancel_bond,
+ get_connection_state,
+ pin_reply,
+ ssp_reply,
+ get_profile_interface,
+ dut_mode_configure,
+ dut_mode_send,
+ le_test_mode,
+ set_os_callouts,
+ read_energy_info,
+ dump,
+ dumpMetrics,
+ config_clear,
+ interop_database_clear,
+ interop_database_add,
+ get_avrcp_service,
+ obfuscate_address,
+ get_metric_id,
+ set_dynamic_audio_buffer_size,
+ generate_local_oob_data};
+
+// callback reporting helpers
+
+bt_property_t* property_deep_copy_array(int num_properties,
+ bt_property_t* properties) {
+ bt_property_t* copy = nullptr;
+ if (num_properties > 0) {
+ size_t content_len = 0;
+ for (int i = 0; i < num_properties; i++) {
+ auto len = properties[i].len;
+ if (len > 0) {
+ content_len += len;
+ }
+ }
+
+ copy = (bt_property_t*)osi_calloc((sizeof(bt_property_t) * num_properties) +
+ content_len);
+ ASSERT(copy != nullptr);
+ uint8_t* content = (uint8_t*)(copy + num_properties);
+
+ for (int i = 0; i < num_properties; i++) {
+ auto len = properties[i].len;
+ copy[i].type = properties[i].type;
+ copy[i].len = len;
+ if (len <= 0) {
+ continue;
+ }
+ copy[i].val = content;
+ memcpy(content, properties[i].val, len);
+ content += len;
+ }
+ }
+ return copy;
+}
+
+void invoke_adapter_state_changed_cb(bt_state_t state) {
+ do_in_jni_thread(FROM_HERE, base::BindOnce(
+ [](bt_state_t state) {
+ HAL_CBACK(bt_hal_cbacks,
+ adapter_state_changed_cb, state);
+ },
+ state));
+}
+
+void invoke_adapter_properties_cb(bt_status_t status, int num_properties,
+ bt_property_t* properties) {
+ do_in_jni_thread(FROM_HERE,
+ base::BindOnce(
+ [](bt_status_t status, int num_properties,
+ bt_property_t* properties) {
+ HAL_CBACK(bt_hal_cbacks, adapter_properties_cb, status,
+ num_properties, properties);
+ if (properties) {
+ osi_free(properties);
+ }
+ },
+ status, num_properties,
+ property_deep_copy_array(num_properties, properties)));
+}
+
+void invoke_remote_device_properties_cb(bt_status_t status, RawAddress bd_addr,
+ int num_properties,
+ bt_property_t* properties) {
+ do_in_jni_thread(
+ FROM_HERE, base::BindOnce(
+ [](bt_status_t status, RawAddress bd_addr,
+ int num_properties, bt_property_t* properties) {
+ HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb,
+ status, &bd_addr, num_properties, properties);
+ if (properties) {
+ osi_free(properties);
+ }
+ },
+ status, bd_addr, num_properties,
+ property_deep_copy_array(num_properties, properties)));
+}
+
+void invoke_device_found_cb(int num_properties, bt_property_t* properties) {
+ do_in_jni_thread(FROM_HERE,
+ base::BindOnce(
+ [](int num_properties, bt_property_t* properties) {
+ HAL_CBACK(bt_hal_cbacks, device_found_cb,
+ num_properties, properties);
+ if (properties) {
+ osi_free(properties);
+ }
+ },
+ num_properties,
+ property_deep_copy_array(num_properties, properties)));
+}
+
+void invoke_discovery_state_changed_cb(bt_discovery_state_t state) {
+ do_in_jni_thread(FROM_HERE, base::BindOnce(
+ [](bt_discovery_state_t state) {
+ HAL_CBACK(bt_hal_cbacks,
+ discovery_state_changed_cb,
+ state);
+ },
+ state));
+}
+
+void invoke_pin_request_cb(RawAddress bd_addr, bt_bdname_t bd_name,
+ uint32_t cod, bool min_16_digit) {
+ do_in_jni_thread(FROM_HERE, base::BindOnce(
+ [](RawAddress bd_addr, bt_bdname_t bd_name,
+ uint32_t cod, bool min_16_digit) {
+ HAL_CBACK(bt_hal_cbacks, pin_request_cb,
+ &bd_addr, &bd_name, cod,
+ min_16_digit);
+ },
+ bd_addr, bd_name, cod, min_16_digit));
+}
+
+void invoke_ssp_request_cb(RawAddress bd_addr, bt_bdname_t bd_name,
+ uint32_t cod, bt_ssp_variant_t pairing_variant,
+ uint32_t pass_key) {
+ do_in_jni_thread(FROM_HERE,
+ base::BindOnce(
+ [](RawAddress bd_addr, bt_bdname_t bd_name, uint32_t cod,
+ bt_ssp_variant_t pairing_variant, uint32_t pass_key) {
+ HAL_CBACK(bt_hal_cbacks, ssp_request_cb, &bd_addr,
+ &bd_name, cod, pairing_variant, pass_key);
+ },
+ bd_addr, bd_name, cod, pairing_variant, pass_key));
+}
+
+void invoke_oob_data_request_cb(tBT_TRANSPORT t, bool valid, Octet16 c,
+ Octet16 r, RawAddress raw_address,
+ uint8_t address_type) {
+ LOG_INFO("%s", __func__);
+ bt_oob_data_t oob_data = {};
+ char* local_name;
+ BTM_ReadLocalDeviceName(&local_name);
+ for (int i = 0; i < BTM_MAX_LOC_BD_NAME_LEN; i++) {
+ oob_data.device_name[i] = local_name[i];
+ }
+
+ // Set the local address
+ int j = 5;
+ for (int i = 0; i < 6; i++) {
+ oob_data.address[i] = raw_address.address[j];
+ j--;
+ }
+ oob_data.address[6] = address_type;
+
+ // Each value (for C and R) is 16 octets in length
+ bool c_empty = true;
+ for (int i = 0; i < 16; i++) {
+ // C cannot be all 0s, if so then we want to fail
+ if (c[i] != 0) c_empty = false;
+ oob_data.c[i] = c[i];
+ // R is optional and may be empty
+ oob_data.r[i] = r[i];
+ }
+ oob_data.is_valid = valid && !c_empty;
+ // The oob_data_length is 2 octects in length. The value includes the length
+ // of itself. 16 + 16 + 2 = 34 Data 0x0022 Little Endian order 0x2200
+ oob_data.oob_data_length[0] = 0;
+ oob_data.oob_data_length[1] = 34;
+ bt_status_t status = do_in_jni_thread(
+ FROM_HERE, base::BindOnce(
+ [](tBT_TRANSPORT t, bt_oob_data_t oob_data) {
+ HAL_CBACK(bt_hal_cbacks, generate_local_oob_data_cb, t,
+ oob_data);
+ },
+ t, oob_data));
+ if (status != BT_STATUS_SUCCESS) {
+ LOG_ERROR("%s: Failed to call callback!", __func__);
+ }
+}
+
+void invoke_bond_state_changed_cb(bt_status_t status, RawAddress bd_addr,
+ bt_bond_state_t state, int fail_reason) {
+ do_in_jni_thread(FROM_HERE, base::BindOnce(
+ [](bt_status_t status, RawAddress bd_addr,
+ bt_bond_state_t state, int fail_reason) {
+ HAL_CBACK(bt_hal_cbacks,
+ bond_state_changed_cb, status,
+ &bd_addr, state, fail_reason);
+ },
+ status, bd_addr, state, fail_reason));
+}
+
+void invoke_acl_state_changed_cb(bt_status_t status, RawAddress bd_addr,
+ bt_acl_state_t state, int transport_link_type,
+ bt_hci_error_code_t hci_reason) {
+ do_in_jni_thread(
+ FROM_HERE,
+ base::BindOnce(
+ [](bt_status_t status, RawAddress bd_addr, bt_acl_state_t state,
+ int transport_link_type, bt_hci_error_code_t hci_reason) {
+ HAL_CBACK(bt_hal_cbacks, acl_state_changed_cb, status, &bd_addr,
+ state, transport_link_type, hci_reason);
+ },
+ status, bd_addr, state, transport_link_type, hci_reason));
+}
+
+void invoke_thread_evt_cb(bt_cb_thread_evt event) {
+ do_in_jni_thread(FROM_HERE, base::BindOnce(
+ [](bt_cb_thread_evt event) {
+ HAL_CBACK(bt_hal_cbacks, thread_evt_cb,
+ event);
+ if (event == DISASSOCIATE_JVM) {
+ bt_hal_cbacks = NULL;
+ }
+ },
+ event));
+}
+
+void invoke_le_test_mode_cb(bt_status_t status, uint16_t count) {
+ do_in_jni_thread(FROM_HERE, base::BindOnce(
+ [](bt_status_t status, uint16_t count) {
+ HAL_CBACK(bt_hal_cbacks, le_test_mode_cb,
+ status, count);
+ },
+ status, count));
+}
+
+// takes ownership of |uid_data|
+void invoke_energy_info_cb(bt_activity_energy_info energy_info,
+ bt_uid_traffic_t* uid_data) {
+ do_in_jni_thread(
+ FROM_HERE,
+ base::BindOnce(
+ [](bt_activity_energy_info energy_info, bt_uid_traffic_t* uid_data) {
+ HAL_CBACK(bt_hal_cbacks, energy_info_cb, &energy_info, uid_data);
+ osi_free(uid_data);
+ },
+ energy_info, uid_data));
+}
+
+void invoke_link_quality_report_cb(uint64_t timestamp, int report_id, int rssi,
+ int snr, int retransmission_count,
+ int packets_not_receive_count,
+ int negative_acknowledgement_count) {
+ do_in_jni_thread(
+ FROM_HERE,
+ base::BindOnce(
+ [](uint64_t timestamp, int report_id, int rssi, int snr,
+ int retransmission_count, int packets_not_receive_count,
+ int negative_acknowledgement_count) {
+ HAL_CBACK(bt_hal_cbacks, link_quality_report_cb, timestamp,
+ report_id, rssi, snr, retransmission_count,
+ packets_not_receive_count,
+ negative_acknowledgement_count);
+ },
+ timestamp, report_id, rssi, snr, retransmission_count,
+ packets_not_receive_count, negative_acknowledgement_count));
+}
diff --git a/service/hal/bluetooth_interface.cc b/service/hal/bluetooth_interface.cc
index 391f6a456..0382b5e5a 100644
--- a/service/hal/bluetooth_interface.cc
+++ b/service/hal/bluetooth_interface.cc
@@ -18,6 +18,7 @@
#include <base/logging.h>
#include <base/observer_list.h>
+#include <dlfcn.h>
#include <mutex>
#include <shared_mutex>
@@ -235,6 +236,43 @@ bt_os_callouts_t bt_os_callouts = {sizeof(bt_os_callouts_t),
SetWakeAlarmCallout, AcquireWakeLockCallout,
ReleaseWakeLockCallout};
+constexpr char kLibbluetooth[] = "libbluetooth.so";
+constexpr char kBluetoothInterfaceSym[] = "bluetoothInterface";
+
+int hal_util_load_bt_library_from_dlib(const bt_interface_t** interface) {
+ bt_interface_t* itf{nullptr};
+
+ // Always try to load the default Bluetooth stack on GN builds.
+ void* handle = dlopen(kLibbluetooth, RTLD_NOW);
+ if (!handle) {
+ const char* err_str = dlerror();
+ LOG(ERROR) << __func__ << ": failed to load bluetooth library, error="
+ << (err_str ? err_str : "error unknown");
+ goto error;
+ }
+
+ // Get the address of the bt_interface_t.
+ itf = (bt_interface_t*)dlsym(handle, kBluetoothInterfaceSym);
+ if (!itf) {
+ LOG(ERROR) << __func__ << ": failed to load symbol from Bluetooth library "
+ << kBluetoothInterfaceSym;
+ goto error;
+ }
+
+ // Success.
+ LOG(INFO) << __func__ << " loaded HAL path=" << kLibbluetooth
+ << " btinterface=" << itf << " handle=" << handle;
+
+ *interface = itf;
+ return 0;
+
+error:
+ *interface = NULL;
+ if (handle) dlclose(handle);
+
+ return -EINVAL;
+}
+
} // namespace
// BluetoothInterface implementation for production.
@@ -266,7 +304,7 @@ class BluetoothInterfaceImpl : public BluetoothInterface {
bool Initialize() {
// Load the Bluetooth shared library module.
const bt_interface_t* interface;
- int status = hal_util_load_bt_library(&interface);
+ int status = hal_util_load_bt_library_from_dlib(&interface);
if (status) {
LOG(ERROR) << "Failed to open the Bluetooth module";
return false;
diff --git a/stack/acl/acl.h b/stack/acl/acl.h
index 9c4c9e65e..d114cdaba 100644
--- a/stack/acl/acl.h
+++ b/stack/acl/acl.h
@@ -176,7 +176,10 @@ struct tACL_CONN {
bool peer_lmp_feature_valid[HCI_EXT_FEATURES_PAGE_MAX + 1];
RawAddress active_remote_addr;
+ tBLE_ADDR_TYPE active_remote_addr_type;
RawAddress conn_addr;
+ tBLE_ADDR_TYPE conn_addr_type;
+
RawAddress remote_addr;
bool in_use{false};
@@ -200,8 +203,6 @@ struct tACL_CONN {
uint16_t Handle() const { return hci_handle; }
uint16_t link_super_tout;
uint16_t pkt_types_mask;
- tBLE_ADDR_TYPE active_remote_addr_type;
- tBLE_ADDR_TYPE conn_addr_type;
uint8_t disconnect_reason;
private:
diff --git a/stack/avdt/avdt_int.h b/stack/avdt/avdt_int.h
index d872573d5..1950ac58c 100644
--- a/stack/avdt/avdt_int.h
+++ b/stack/avdt/avdt_int.h
@@ -418,6 +418,7 @@ class AvdtpScb {
media_seq(0),
allocated(false),
in_use(false),
+ need_open(false),
role(0),
remove(false),
state(0),
@@ -467,6 +468,7 @@ class AvdtpScb {
media_seq = 0;
allocated = false;
in_use = false;
+ need_open = false;
role = 0;
remove = false;
state = 0;
@@ -491,6 +493,7 @@ class AvdtpScb {
uint16_t media_seq; // Media packet sequence number
bool allocated; // True if the SCB is allocated
bool in_use; // True if used by peer
+ bool need_open; // True if need open after receiving delay report (AVDT 1_3)
uint8_t role; // Initiator/acceptor role in current procedure
bool remove; // True if the SCB is marked for removal
uint8_t state; // State machine state
diff --git a/stack/avdt/avdt_scb_act.cc b/stack/avdt/avdt_scb_act.cc
index 611abb564..bea470844 100644
--- a/stack/avdt/avdt_scb_act.cc
+++ b/stack/avdt/avdt_scb_act.cc
@@ -686,7 +686,6 @@ void avdt_scb_snd_snk_delay_rpt_req(AvdtpScb* p_scb,
*
******************************************************************************/
void avdt_scb_hdl_setconfig_rsp(AvdtpScb* p_scb, tAVDT_SCB_EVT* p_data) {
- tAVDT_EVT_HDR single;
if (p_scb->p_ccb != NULL) {
/* save configuration */
@@ -696,11 +695,19 @@ void avdt_scb_hdl_setconfig_rsp(AvdtpScb* p_scb, tAVDT_SCB_EVT* p_data) {
// Delay reporting is sent before open request (i.e., in configured state).
avdt_scb_snd_snk_delay_rpt_req(p_scb, p_data);
- /* initiate open */
- single.seid = p_scb->peer_seid;
- tAVDT_SCB_EVT avdt_scb_evt;
- avdt_scb_evt.msg.single = single;
- avdt_scb_event(p_scb, AVDT_SCB_API_OPEN_REQ_EVT, &avdt_scb_evt);
+ p_scb->need_open = false;
+ /* Check if both local and SEP support AVDT version 1.3*/
+ if (A2DP_GetAvdtpVersion() >= AVDT_VERSION_1_3 &&
+ (p_scb->stream_config.cfg.psc_mask & AVDT_PSC_DELAY_RPT)) {
+ p_scb->need_open = true;
+ } else {
+ /* Initiate open */
+ tAVDT_EVT_HDR single;
+ single.seid = p_scb->peer_seid;
+ tAVDT_SCB_EVT avdt_scb_evt;
+ avdt_scb_evt.msg.single = single;
+ avdt_scb_event(p_scb, AVDT_SCB_API_OPEN_REQ_EVT, &avdt_scb_evt);
+ }
}
}
@@ -863,9 +870,20 @@ void avdt_scb_hdl_delay_rpt_cmd(AvdtpScb* p_scb, tAVDT_SCB_EVT* p_data) {
p_scb->p_ccb ? p_scb->p_ccb->peer_addr : RawAddress::kEmpty,
AVDT_DELAY_REPORT_EVT, (tAVDT_CTRL*)&p_data->msg.hdr,
p_scb->stream_config.scb_index);
-
- if (p_scb->p_ccb)
+ if (p_scb->p_ccb) {
avdt_msg_send_rsp(p_scb->p_ccb, AVDT_SIG_DELAY_RPT, &p_data->msg);
+ /* Check if we need open stream after set_config */
+ if (p_scb->need_open) {
+ /* Initiate open */
+ tAVDT_EVT_HDR single;
+ single.seid = p_scb->peer_seid;
+ tAVDT_SCB_EVT avdt_scb_evt;
+ avdt_scb_evt.msg.single = single;
+ avdt_scb_event(p_scb, AVDT_SCB_API_OPEN_REQ_EVT, &avdt_scb_evt);
+ /* Clear flag */
+ p_scb->need_open = false;
+ }
+ }
else
avdt_scb_rej_not_in_use(p_scb, p_data);
}
diff --git a/stack/btm/btm_ble.cc b/stack/btm/btm_ble.cc
index e249631b8..3d0228de5 100644
--- a/stack/btm/btm_ble.cc
+++ b/stack/btm/btm_ble.cc
@@ -485,6 +485,7 @@ void BTM_ReadDevInfo(const RawAddress& remote_bda, tBT_DEVICE_TYPE* p_dev_type,
p_dev_rec->device_type = p_inq_info->results.device_type;
p_dev_rec->ble.ble_addr_type = p_inq_info->results.ble_addr_type;
}
+
if (p_dev_rec->bd_addr == remote_bda &&
p_dev_rec->ble.pseudo_addr == remote_bda) {
*p_dev_type = p_dev_rec->device_type;
@@ -492,9 +493,13 @@ void BTM_ReadDevInfo(const RawAddress& remote_bda, tBT_DEVICE_TYPE* p_dev_type,
} else if (p_dev_rec->ble.pseudo_addr == remote_bda) {
*p_dev_type = BT_DEVICE_TYPE_BLE;
*p_addr_type = p_dev_rec->ble.ble_addr_type;
- } else /* matching static adddress only */
- {
- *p_dev_type = BT_DEVICE_TYPE_BREDR;
+ } else /* matching static adddress only */ {
+ if (p_dev_rec->device_type != BT_DEVICE_TYPE_UNKNOWN) {
+ *p_dev_type = p_dev_rec->device_type;
+ } else {
+ LOG_WARN("device_type not set; assuming BR/EDR");
+ *p_dev_type = BT_DEVICE_TYPE_BREDR;
+ }
*p_addr_type = BLE_ADDR_PUBLIC;
}
}
diff --git a/stack/btm/btm_ble_bgconn.cc b/stack/btm/btm_ble_bgconn.cc
index 80ba1cd04..8a70c2065 100644
--- a/stack/btm/btm_ble_bgconn.cc
+++ b/stack/btm/btm_ble_bgconn.cc
@@ -364,7 +364,9 @@ static bool btm_ble_start_auto_conn() {
btm_ble_enable_resolving_list_for_platform(BTM_BLE_RL_INIT);
if (btm_cb.ble_ctr_cb.rl_state != BTM_BLE_RL_IDLE &&
controller_get_interface()->supports_ble_privacy()) {
+#if (BLE_LOCAL_PRIVACY_ENABLED == TRUE)
own_addr_type |= BLE_ADDR_TYPE_ID_BIT;
+#endif
peer_addr_type |= BLE_ADDR_TYPE_ID_BIT;
}
diff --git a/stack/btm/btm_ble_privacy.cc b/stack/btm/btm_ble_privacy.cc
index d3a2b9605..4b8ffd031 100644
--- a/stack/btm/btm_ble_privacy.cc
+++ b/stack/btm/btm_ble_privacy.cc
@@ -280,6 +280,7 @@ void btm_ble_add_resolving_list_entry_complete(uint8_t* p, uint16_t evt_len) {
}
if (status == HCI_SUCCESS) {
+ btm_ble_update_resolving_list(pseudo_bda, true);
/* privacy 1.2 command complete does not have these extra byte */
if (evt_len > 2) {
/* VSC complete has one extra byte for op code, skip it here */
@@ -740,7 +741,6 @@ bool btm_ble_resolving_list_load_dev(tBTM_SEC_DEV_REC* p_dev_rec) {
return false;
}
- btm_ble_update_resolving_list(p_dev_rec->bd_addr, true);
if (controller_get_interface()->supports_ble_privacy()) {
const Octet16& peer_irk = p_dev_rec->ble.keys.irk;
const Octet16& local_irk = btm_cb.devcb.id_keys.irk;
diff --git a/stack/btm/btm_inq.cc b/stack/btm/btm_inq.cc
index 67ca22cbc..c54973c8e 100644
--- a/stack/btm/btm_inq.cc
+++ b/stack/btm/btm_inq.cc
@@ -145,8 +145,8 @@ static tBTM_STATUS btm_initiate_rem_name(const RawAddress& remote_bda,
static uint8_t btm_convert_uuid_to_eir_service(uint16_t uuid16);
void btm_set_eir_uuid(uint8_t* p_eir, tBTM_INQ_RESULTS* p_results);
-static const uint8_t* btm_eir_get_uuid_list(uint8_t* p_eir, size_t eir_len,
- uint8_t uuid_size,
+static const uint8_t* btm_eir_get_uuid_list(const uint8_t* p_eir,
+ size_t eir_len, uint8_t uuid_size,
uint8_t* p_num_uuid,
uint8_t* p_uuid_list_type);
@@ -1683,9 +1683,9 @@ uint8_t BTM_GetEirSupportedServices(uint32_t* p_eir_uuid, uint8_t** p,
* HCI_EIR_MORE_128BITS_UUID_TYPE
*
******************************************************************************/
-uint8_t BTM_GetEirUuidList(uint8_t* p_eir, size_t eir_len, uint8_t uuid_size,
- uint8_t* p_num_uuid, uint8_t* p_uuid_list,
- uint8_t max_num_uuid) {
+uint8_t BTM_GetEirUuidList(const uint8_t* p_eir, size_t eir_len,
+ uint8_t uuid_size, uint8_t* p_num_uuid,
+ uint8_t* p_uuid_list, uint8_t max_num_uuid) {
const uint8_t* p_uuid_data;
uint8_t type;
uint8_t yy, xx;
@@ -1747,8 +1747,8 @@ uint8_t BTM_GetEirUuidList(uint8_t* p_eir, size_t eir_len, uint8_t uuid_size,
* beginning of UUID list in EIR - otherwise
*
******************************************************************************/
-static const uint8_t* btm_eir_get_uuid_list(uint8_t* p_eir, size_t eir_len,
- uint8_t uuid_size,
+static const uint8_t* btm_eir_get_uuid_list(const uint8_t* p_eir,
+ size_t eir_len, uint8_t uuid_size,
uint8_t* p_num_uuid,
uint8_t* p_uuid_list_type) {
const uint8_t* p_uuid_data;
diff --git a/stack/btm/btm_int_types.h b/stack/btm/btm_int_types.h
index 8b96a426a..ce0d46bf0 100644
--- a/stack/btm/btm_int_types.h
+++ b/stack/btm/btm_int_types.h
@@ -118,9 +118,6 @@ typedef struct {
tBTM_BLE_SEC_ACT sec_act;
} tBTM_SEC_QUEUE_ENTRY;
-// Bluetooth Quality Report - Report receiver
-typedef void(tBTM_BT_QUALITY_REPORT_RECEIVER)(uint8_t len, uint8_t* p_stream);
-
/* Define a structure to hold all the BTM data
*/
@@ -275,6 +272,7 @@ typedef struct tBTM_CB {
uint8_t pairing_flags{0}; /* The current pairing flags */
RawAddress pairing_bda; /* The device currently pairing */
alarm_t* pairing_timer{nullptr}; /* Timer for pairing process */
+ alarm_t* execution_wait_timer{nullptr}; /* To avoid concurrent auth request */
uint16_t disc_handle{0}; /* for legacy devices */
uint8_t disc_reason{0}; /* for legacy devices */
tBTM_SEC_SERV_REC sec_serv_rec[BTM_SEC_MAX_SERVICE_RECORDS];
@@ -332,6 +330,7 @@ typedef struct tBTM_CB {
sec_pending_q = fixed_queue_new(SIZE_MAX);
sec_collision_timer = alarm_new("btm.sec_collision_timer");
pairing_timer = alarm_new("btm.pairing_timer");
+ execution_wait_timer = alarm_new("btm.execution_wait_timer");
#if defined(BTM_INITIAL_TRACE_LEVEL)
trace_level = BTM_INITIAL_TRACE_LEVEL;
@@ -373,6 +372,9 @@ typedef struct tBTM_CB {
alarm_free(pairing_timer);
pairing_timer = nullptr;
+
+ alarm_free(execution_wait_timer);
+ execution_wait_timer = nullptr;
}
private:
diff --git a/stack/btm/btm_sco.cc b/stack/btm/btm_sco.cc
index b80bd40b2..2d1e1eae7 100644
--- a/stack/btm/btm_sco.cc
+++ b/stack/btm/btm_sco.cc
@@ -198,7 +198,8 @@ void btm_route_sco_data(BT_HDR* p_msg) {
return;
}
LOG_INFO("Received SCO packet from HCI. Dropping it since no handler so far");
- uint16_t handle = handle_with_flags & 0xeff;
+ uint16_t handle = handle_with_flags & 0xFFF;
+ ASSERT_LOG(handle <= 0xEFF, "Require handle <= 0xEFF, but is 0x%X", handle);
auto* active_sco = btm_get_active_sco();
if (active_sco != nullptr && active_sco->hci_handle == handle) {
// TODO: For MSBC, we need to decode here
diff --git a/stack/btm/btm_sec.cc b/stack/btm/btm_sec.cc
index 480b4f353..e0fb60e49 100644
--- a/stack/btm/btm_sec.cc
+++ b/stack/btm/btm_sec.cc
@@ -89,7 +89,8 @@ extern void HACK_acl_check_sm4(tBTM_SEC_DEV_REC& p_dev_rec);
tBTM_SEC_SERV_REC* btm_sec_find_first_serv(bool is_originator, uint16_t psm);
static bool btm_sec_start_get_name(tBTM_SEC_DEV_REC* p_dev_rec);
-static void btm_sec_start_authentication(tBTM_SEC_DEV_REC* p_dev_rec);
+static void btm_sec_wait_and_start_authentication(tBTM_SEC_DEV_REC* p_dev_rec);
+static void btm_sec_auth_timer_timeout(void* data);
static void btm_sec_collision_timeout(void* data);
static void btm_restore_mode(void);
static void btm_sec_pairing_timeout(void* data);
@@ -808,7 +809,7 @@ tBTM_STATUS btm_sec_bond_by_transport(const RawAddress& bd_addr,
/* If connection already exists... */
if (BTM_IsAclConnectionUpAndHandleValid(bd_addr, transport)) {
- btm_sec_start_authentication(p_dev_rec);
+ btm_sec_wait_and_start_authentication(p_dev_rec);
btm_sec_change_pairing_state(BTM_PAIR_STATE_WAIT_PIN_REQ);
@@ -3320,6 +3321,11 @@ void btm_sec_encrypt_change(uint16_t handle, tHCI_STATUS status,
__func__, p_dev_rec, p_dev_rec->p_callback);
p_dev_rec->p_callback = NULL;
l2cu_resubmit_pending_sec_req(&p_dev_rec->bd_addr);
+#ifdef BTM_DISABLE_CONCURRENT_PEER_AUTH
+ } else if (BTM_DISABLE_CONCURRENT_PEER_AUTH &&
+ p_dev_rec->sec_state == BTM_SEC_STATE_AUTHENTICATING) {
+ p_dev_rec->sec_state = BTM_SEC_STATE_IDLE;
+#endif
}
return;
}
@@ -3923,6 +3929,10 @@ void btm_sec_link_key_request(uint8_t* p_event) {
tBTM_SEC_DEV_REC* p_dev_rec = btm_find_or_alloc_dev(bda);
VLOG(2) << __func__ << " bda: " << bda;
+#if (defined(BTM_DISABLE_CONCURRENT_PEER_AUTH) && \
+ (BTM_DISABLE_CONCURRENT_PEER_AUTH == TRUE))
+ p_dev_rec->sec_state = BTM_SEC_STATE_AUTHENTICATING;
+#endif
if ((btm_cb.pairing_state == BTM_PAIR_STATE_WAIT_PIN_REQ) &&
(btm_cb.collision_start_time != 0) &&
@@ -4315,7 +4325,7 @@ tBTM_STATUS btm_sec_execute_procedure(tBTM_SEC_DEV_REC* p_dev_rec) {
BTM_SEC_AUTHENTICATED);
}
- btm_sec_start_authentication(p_dev_rec);
+ btm_sec_wait_and_start_authentication(p_dev_rec);
return (BTM_CMD_STARTED);
} else {
LOG_DEBUG("Authentication not required");
@@ -4378,12 +4388,30 @@ static bool btm_sec_start_get_name(tBTM_SEC_DEV_REC* p_dev_rec) {
/*******************************************************************************
*
- * Function btm_sec_start_authentication
+ * Function btm_sec_wait_and_start_authentication
+ *
+ * Description This function is called to add an alarm to wait and start
+ * authentication
+ *
+ ******************************************************************************/
+static void btm_sec_wait_and_start_authentication(tBTM_SEC_DEV_REC* p_dev_rec) {
+ if (alarm_is_scheduled(btm_cb.execution_wait_timer)) {
+ BTM_TRACE_EVENT("%s: alarm already scheduled", __func__);
+ return;
+ }
+ alarm_set(btm_cb.execution_wait_timer, BTM_DELAY_AUTH_MS,
+ btm_sec_auth_timer_timeout, p_dev_rec);
+}
+
+/*******************************************************************************
+ *
+ * Function btm_sec_auth_timer_timeout
*
- * Description This function is called to start authentication
+ * Description called after wait timeout to request authentication
*
******************************************************************************/
-static void btm_sec_start_authentication(tBTM_SEC_DEV_REC* p_dev_rec) {
+static void btm_sec_auth_timer_timeout(void* data) {
+ tBTM_SEC_DEV_REC* p_dev_rec = (tBTM_SEC_DEV_REC*)data;
p_dev_rec->sec_state = BTM_SEC_STATE_AUTHENTICATING;
btsnd_hcic_auth_request(p_dev_rec->hci_handle);
}
diff --git a/stack/btm/neighbor_inquiry.h b/stack/btm/neighbor_inquiry.h
index 24a0175db..eb9a45951 100644
--- a/stack/btm/neighbor_inquiry.h
+++ b/stack/btm/neighbor_inquiry.h
@@ -127,7 +127,7 @@ typedef struct {
* First param is inquiry results database, second is pointer of EIR.
*/
typedef void(tBTM_INQ_RESULTS_CB)(tBTM_INQ_RESULTS* p_inq_results,
- uint8_t* p_eir, uint16_t eir_len);
+ const uint8_t* p_eir, uint16_t eir_len);
typedef struct {
uint32_t inq_count; /* Used for determining if a response has already been */
diff --git a/stack/gatt/gatt_api.cc b/stack/gatt/gatt_api.cc
index d2ad51f19..6129b30de 100644
--- a/stack/gatt/gatt_api.cc
+++ b/stack/gatt/gatt_api.cc
@@ -67,22 +67,6 @@ tGATT_HDL_LIST_ELEM& gatt_add_an_item_to_list(uint16_t s_handle) {
*****************************************************************************/
/*******************************************************************************
*
- * Function GATTS_AddHandleRange
- *
- * Description This function add the allocated handles range for the
- * specified application UUID, service UUID and service
- * instance
- *
- * Parameter p_hndl_range: pointer to allocated handles information
- *
- **/
-
-void GATTS_AddHandleRange(tGATTS_HNDL_RANGE* p_hndl_range) {
- gatt_add_an_item_to_list(p_hndl_range->s_handle);
-}
-
-/*******************************************************************************
- *
* Function GATTS_NVRegister
*
* Description Application manager calls this function to register for
diff --git a/stack/gatt/gatt_cl.cc b/stack/gatt/gatt_cl.cc
index 831d031b9..d8896b3a9 100644
--- a/stack/gatt/gatt_cl.cc
+++ b/stack/gatt/gatt_cl.cc
@@ -615,6 +615,7 @@ void gatt_process_prep_write_rsp(tGATT_TCB& tcb, tGATT_CLCB* p_clcb,
gatt_end_operation(p_clcb, p_clcb->status, &value);
}
}
+
/*******************************************************************************
*
* Function gatt_process_notification
@@ -626,10 +627,10 @@ void gatt_process_prep_write_rsp(tGATT_TCB& tcb, tGATT_CLCB* p_clcb,
******************************************************************************/
void gatt_process_notification(tGATT_TCB& tcb, uint16_t cid, uint8_t op_code,
uint16_t len, uint8_t* p_data) {
- tGATT_VALUE value;
+ tGATT_VALUE value = {};
tGATT_REG* p_reg;
uint16_t conn_id;
- tGATT_STATUS encrypt_status;
+ tGATT_STATUS encrypt_status = {};
uint8_t* p = p_data;
uint8_t i;
tGATTC_OPTYPE event = (op_code == GATT_HANDLE_VALUE_IND)
@@ -638,34 +639,52 @@ void gatt_process_notification(tGATT_TCB& tcb, uint16_t cid, uint8_t op_code,
VLOG(1) << __func__;
+ // Ensure our packet has enough data (2 bytes)
if (len < GATT_NOTIFICATION_MIN_LEN) {
LOG(ERROR) << "illegal notification PDU length, discard";
return;
}
- memset(&value, 0, sizeof(value));
+ // Get 2 byte handle
STREAM_TO_UINT16(value.handle, p);
+ // Fail early if the GATT handle is not valid
+ if (!GATT_HANDLE_IS_VALID(value.handle)) {
+ /* illegal handle, send ack now */
+ if (op_code == GATT_HANDLE_VALUE_IND)
+ attp_send_cl_confirmation_msg(tcb, cid);
+ return;
+ }
+
+ // Calculate value length based on opcode
if (op_code == GATT_HANDLE_MULTI_VALUE_NOTIF) {
+ // Ensure our packet has enough data; MIN + 2 more bytes for len value
+ if (len < GATT_NOTIFICATION_MIN_LEN + 2) {
+ LOG(ERROR) << "illegal notification PDU length, discard";
+ return;
+ }
+
+ // Allow multi value opcode to set value len from the packet
STREAM_TO_UINT16(value.len, p);
+
+ if (value.len > len - 4) {
+ LOG(ERROR) << "value.len (" << value.len << ") greater than length ("
+ << (len - 4);
+ return;
+ }
+
} else {
+ // For single value, just use the passed in len minus opcode length (2)
value.len = len - 2;
}
+ // Verify the new calculated length
if (value.len > GATT_MAX_ATTR_LEN) {
LOG(ERROR) << "value.len larger than GATT_MAX_ATTR_LEN, discard";
return;
}
- STREAM_TO_ARRAY(value.value, p, value.len);
-
- if (!GATT_HANDLE_IS_VALID(value.handle)) {
- /* illegal handle, send ack now */
- if (op_code == GATT_HANDLE_VALUE_IND)
- attp_send_cl_confirmation_msg(tcb, cid);
- return;
- }
-
+ // Handle indications differently
if (event == GATTC_OPTYPE_INDICATION) {
if (tcb.ind_count) {
/* this is an error case that receiving an indication but we
@@ -676,34 +695,67 @@ void gatt_process_notification(tGATT_TCB& tcb, uint16_t cid, uint8_t op_code,
LOG(ERROR) << __func__ << " rcv Ind. but ind_count=" << tcb.ind_count
<< " (will reset ind_count)";
}
- tcb.ind_count = 0;
- }
- /* should notify all registered client with the handle value
- notificaion/indication
- Note: need to do the indication count and start timer first then do
- callback
- */
+ // Zero out the ind_count
+ tcb.ind_count = 0;
- for (i = 0, p_reg = gatt_cb.cl_rcb; i < GATT_MAX_APPS; i++, p_reg++) {
- if (p_reg->in_use && p_reg->app_cb.p_cmpl_cb &&
- (event == GATTC_OPTYPE_INDICATION))
- tcb.ind_count++;
- }
+ // Notify all registered clients with the handle value
+ // notification/indication
+ // Note: need to do the indication count and start timer first then do
+ // callback
+ for (i = 0, p_reg = gatt_cb.cl_rcb; i < GATT_MAX_APPS; i++, p_reg++) {
+ if (p_reg->in_use && p_reg->app_cb.p_cmpl_cb) tcb.ind_count++;
+ }
- if (event == GATTC_OPTYPE_INDICATION) {
/* start a timer for app confirmation */
- if (tcb.ind_count > 0)
+ if (tcb.ind_count > 0) {
gatt_start_ind_ack_timer(tcb, cid);
- else /* no app to indicate, or invalid handle */
+ } else { /* no app to indicate, or invalid handle */
attp_send_cl_confirmation_msg(tcb, cid);
+ }
}
encrypt_status = gatt_get_link_encrypt_status(tcb);
- uint16_t rem_len = len;
- while (rem_len) {
- tGATT_CL_COMPLETE gatt_cl_complete;
+ STREAM_TO_ARRAY(value.value, p, value.len);
+
+ tGATT_CL_COMPLETE gatt_cl_complete;
+ gatt_cl_complete.att_value = value;
+ gatt_cl_complete.cid = cid;
+
+ for (i = 0, p_reg = gatt_cb.cl_rcb; i < GATT_MAX_APPS; i++, p_reg++) {
+ if (p_reg->in_use && p_reg->app_cb.p_cmpl_cb) {
+ conn_id = GATT_CREATE_CONN_ID(tcb.tcb_idx, p_reg->gatt_if);
+ (*p_reg->app_cb.p_cmpl_cb)(conn_id, event, encrypt_status,
+ &gatt_cl_complete);
+ }
+ }
+
+ // If this is single value, then nothing is left to do
+ if (op_code != GATT_HANDLE_MULTI_VALUE_NOTIF) return;
+
+ // Need a signed type to check if the value is below 0
+ // as uint16_t doesn't have negatives so the negatives register as a number
+ // thus anything less than zero won't trigger the conditional and it is not
+ // always 0
+ // when done looping as value.len is arbitrary.
+ int16_t rem_len = (int16_t)len - (4 /* octets */ + value.len);
+
+ // Already streamed the first value and sent it, lets send the rest
+ while (rem_len > 4 /* octets */) {
+ // 2
+ STREAM_TO_UINT16(value.handle, p);
+ // + 2 = 4
+ STREAM_TO_UINT16(value.len, p);
+ // Accounting
+ rem_len -= 4;
+ // Make sure we don't read past the remaining data even if the length says
+ // we can Also need to watch comparing the int16_t with the uint16_t
+ value.len = std::min(rem_len, (int16_t)value.len);
+ STREAM_TO_ARRAY(value.value, p, value.len);
+ // Accounting
+ rem_len -= value.len;
+
gatt_cl_complete.att_value = value;
gatt_cl_complete.cid = cid;
@@ -714,16 +766,6 @@ void gatt_process_notification(tGATT_TCB& tcb, uint16_t cid, uint8_t op_code,
&gatt_cl_complete);
}
}
-
- if (op_code != GATT_HANDLE_MULTI_VALUE_NOTIF) return;
-
- /* 4 stands for 2 octects for handle and 2 octecs for len */
- rem_len -= (4 + value.len);
- if (rem_len) {
- STREAM_TO_UINT16(value.handle, p);
- STREAM_TO_UINT16(value.len, p);
- STREAM_TO_ARRAY(value.value, p, value.len);
- }
}
}
diff --git a/stack/include/btm_api.h b/stack/include/btm_api.h
index 7005fbeb3..522e05c6b 100644
--- a/stack/include/btm_api.h
+++ b/stack/include/btm_api.h
@@ -922,9 +922,9 @@ uint8_t BTM_GetEirSupportedServices(uint32_t* p_eir_uuid, uint8_t** p,
* HCI_EIR_MORE_128BITS_UUID_TYPE
*
******************************************************************************/
-uint8_t BTM_GetEirUuidList(uint8_t* p_eir, size_t eir_len, uint8_t uuid_size,
- uint8_t* p_num_uuid, uint8_t* p_uuid_list,
- uint8_t max_num_uuid);
+uint8_t BTM_GetEirUuidList(const uint8_t* p_eir, size_t eir_len,
+ uint8_t uuid_size, uint8_t* p_num_uuid,
+ uint8_t* p_uuid_list, uint8_t max_num_uuid);
/*******************************************************************************
*
diff --git a/stack/include/btm_api_types.h b/stack/include/btm_api_types.h
index 5f5299427..6e9bc99d7 100644
--- a/stack/include/btm_api_types.h
+++ b/stack/include/btm_api_types.h
@@ -924,6 +924,7 @@ typedef struct {
} tBTM_DELETE_STORED_LINK_KEY_COMPLETE;
+#define BTM_CONTRL_UNKNOWN 0
/* ACL link on, SCO link ongoing, sniff mode */
#define BTM_CONTRL_ACTIVE 1
/* Scan state - paging/inquiry/trying to connect*/
diff --git a/stack/include/btm_client_interface.h b/stack/include/btm_client_interface.h
index d4a3f28bf..2d9b96b55 100644
--- a/stack/include/btm_client_interface.h
+++ b/stack/include/btm_client_interface.h
@@ -216,7 +216,7 @@ struct btm_client_interface_s {
uint8_t (*BTM_GetEirSupportedServices)(uint32_t* p_eir_uuid, uint8_t** p,
uint8_t max_num_uuid16,
uint8_t* p_num_uuid16);
- uint8_t (*BTM_GetEirUuidList)(uint8_t* p_eir, size_t eir_len,
+ uint8_t (*BTM_GetEirUuidList)(const uint8_t* p_eir, size_t eir_len,
uint8_t uuid_size, uint8_t* p_num_uuid,
uint8_t* p_uuid_list, uint8_t max_num_uuid);
void (*BTM_AddEirService)(uint32_t* p_eir_uuid, uint16_t uuid16);
diff --git a/stack/include/gatt_api.h b/stack/include/gatt_api.h
index e7e6b4276..33a6eaee6 100644
--- a/stack/include/gatt_api.h
+++ b/stack/include/gatt_api.h
@@ -758,18 +758,6 @@ typedef struct {
/******************************************************************************/
/* GATT Profile Server Functions */
/******************************************************************************/
-/*******************************************************************************
- *
- * Function GATTS_AddHandleRange
- *
- * Description This function add the allocated handles range for the
- * specified application UUID, service UUID and service
- * instance
- *
- * Parameter p_hndl_range: pointer to allocated handles information
- ******************************************************************************/
-
-extern void GATTS_AddHandleRange(tGATTS_HNDL_RANGE* p_hndl_range);
/*******************************************************************************
*
diff --git a/stack/include/pan_api.h b/stack/include/pan_api.h
index a9f3b16ad..7a732758f 100644
--- a/stack/include/pan_api.h
+++ b/stack/include/pan_api.h
@@ -322,8 +322,8 @@ extern tPAN_RESULT PAN_SetRole(uint8_t role, const char* p_user_name,
* allowed at that point of time
*
******************************************************************************/
-extern tPAN_RESULT PAN_Connect(const RawAddress& rem_bda, uint8_t src_role,
- uint8_t dst_role, uint16_t* handle);
+extern tPAN_RESULT PAN_Connect(const RawAddress& rem_bda, tPAN_ROLE src_role,
+ tPAN_ROLE dst_role, uint16_t* handle);
/*******************************************************************************
*
@@ -461,4 +461,6 @@ extern uint8_t PAN_SetTraceLevel(uint8_t new_level);
******************************************************************************/
extern void PAN_Init(void);
+extern void PAN_Dumpsys(int fd);
+
#endif /* PAN_API_H */
diff --git a/stack/include/security_client_callbacks.h b/stack/include/security_client_callbacks.h
index 4bc68f75d..de59ff174 100644
--- a/stack/include/security_client_callbacks.h
+++ b/stack/include/security_client_callbacks.h
@@ -40,7 +40,8 @@ typedef uint8_t(tBTM_AUTHORIZE_CALLBACK)(uint8_t service_id);
* Flag indicating the minimum pin code length to be 16 digits
*/
typedef uint8_t(tBTM_PIN_CALLBACK)(const RawAddress& bd_addr,
- DEV_CLASS dev_class, tBTM_BD_NAME bd_name,
+ DEV_CLASS dev_class,
+ const tBTM_BD_NAME bd_name,
bool min_16_digit);
/* New Link Key for the connection. Parameters are
diff --git a/stack/l2cap/l2c_csm.cc b/stack/l2cap/l2c_csm.cc
index ded9eabac..29229d1d3 100644
--- a/stack/l2cap/l2c_csm.cc
+++ b/stack/l2cap/l2c_csm.cc
@@ -505,6 +505,12 @@ static void l2c_csm_term_w4_sec_comp(tL2C_CCB* p_ccb, tL2CEVT event,
** stack version : 05.04.11.20060119
*/
+ /* Cancel ccb timer as security complete. waiting for w4_info_rsp
+ ** once info rsp received, connection rsp timer will be started
+ ** while sending connection ind to profiles
+ */
+ alarm_cancel(p_ccb->l2c_ccb_timer);
+
/* Waiting for the info resp, tell the peer to set a longer timer */
LOG_DEBUG("Waiting for info response, sending connect pending");
l2cu_send_peer_connect_rsp(p_ccb, L2CAP_CONN_PENDING, 0);
diff --git a/stack/pan/pan_api.cc b/stack/pan/pan_api.cc
index 62baf2e23..9fc53ff82 100644
--- a/stack/pan/pan_api.cc
+++ b/stack/pan/pan_api.cc
@@ -26,13 +26,16 @@
#include "stack/include/pan_api.h"
#include <base/logging.h>
+#include <base/strings/stringprintf.h>
#include <cstdint>
#include "bta/sys/bta_sys.h"
+#include "main/shim/dumpsys.h"
#include "osi/include/allocator.h"
#include "stack/include/bnep_api.h"
#include "stack/include/bt_hdr.h"
+#include "stack/include/btm_log_history.h"
#include "stack/include/sdp_api.h"
#include "stack/include/sdpdefs.h"
#include "stack/pan/pan_int.h"
@@ -41,6 +44,10 @@
using bluetooth::Uuid;
+namespace {
+constexpr char kBtmLogTag[] = "PAN";
+}
+
/*******************************************************************************
*
* Function PAN_Register
@@ -56,10 +63,10 @@ using bluetooth::Uuid;
*
******************************************************************************/
void PAN_Register(tPAN_REGISTER* p_register) {
- pan_register_with_bnep();
-
if (!p_register) return;
+ pan_register_with_bnep();
+
pan_cb.pan_conn_state_cb = p_register->pan_conn_state_cb;
pan_cb.pan_bridge_req_cb = p_register->pan_bridge_req_cb;
pan_cb.pan_data_buf_ind_cb = p_register->pan_data_buf_ind_cb;
@@ -68,7 +75,7 @@ void PAN_Register(tPAN_REGISTER* p_register) {
pan_cb.pan_mfilt_ind_cb = p_register->pan_mfilt_ind_cb;
pan_cb.pan_tx_data_flow_cb = p_register->pan_tx_data_flow_cb;
- return;
+ BTM_LogHistory(kBtmLogTag, RawAddress::kEmpty, "Registered");
}
/*******************************************************************************
@@ -97,7 +104,7 @@ void PAN_Deregister(void) {
PAN_SetRole(PAN_ROLE_INACTIVE, NULL, NULL);
BNEP_Deregister();
- return;
+ BTM_LogHistory(kBtmLogTag, RawAddress::kEmpty, "Unregistered");
}
/*******************************************************************************
@@ -196,6 +203,9 @@ tPAN_RESULT PAN_SetRole(uint8_t role, const char* p_user_name,
pan_cb.role = role;
PAN_TRACE_EVENT("PAN role set to: %d", role);
+
+ BTM_LogHistory(kBtmLogTag, RawAddress::kEmpty, "Role change",
+ base::StringPrintf("role:0x%x", role));
return PAN_SUCCESS;
}
@@ -222,8 +232,8 @@ tPAN_RESULT PAN_SetRole(uint8_t role, const char* p_user_name,
* allowed at that point of time
*
******************************************************************************/
-tPAN_RESULT PAN_Connect(const RawAddress& rem_bda, uint8_t src_role,
- uint8_t dst_role, uint16_t* handle) {
+tPAN_RESULT PAN_Connect(const RawAddress& rem_bda, tPAN_ROLE src_role,
+ tPAN_ROLE dst_role, uint16_t* handle) {
uint32_t mx_chan_id;
/*
@@ -360,6 +370,8 @@ tPAN_RESULT PAN_Disconnect(uint16_t handle) {
if (pan_cb.pan_bridge_req_cb && pcb->src_uuid == UUID_SERVCLASS_NAP)
(*pan_cb.pan_bridge_req_cb)(pcb->rem_bda, false);
+ BTM_LogHistory(kBtmLogTag, pcb->rem_bda, "Disconnect");
+
pan_release_pcb(pcb);
if (result != BNEP_SUCCESS) {
@@ -497,6 +509,9 @@ tPAN_RESULT PAN_WriteBuf(uint16_t handle, const RawAddress& dst,
return (tPAN_RESULT)result;
}
+ pan_cb.pcb[i].write.octets += p_buf->len;
+ pan_cb.pcb[i].write.packets++;
+
PAN_TRACE_DEBUG("PAN successfully wrote data for the PANU connection");
return PAN_SUCCESS;
}
@@ -511,6 +526,7 @@ tPAN_RESULT PAN_WriteBuf(uint16_t handle, const RawAddress& dst,
if (pcb->con_state != PAN_STATE_CONNECTED) {
PAN_TRACE_ERROR("PAN Buf write when conn is not active");
+ pcb->write.drops++;
osi_free(p_buf);
return PAN_FAILURE;
}
@@ -518,13 +534,19 @@ tPAN_RESULT PAN_WriteBuf(uint16_t handle, const RawAddress& dst,
result = BNEP_WriteBuf(pcb->handle, dst, p_buf, protocol, &src, ext);
if (result == BNEP_IGNORE_CMD) {
PAN_TRACE_DEBUG("PAN ignored data buf write to PANU");
- return (tPAN_RESULT)result;
+ pcb->write.errors++;
+ return PAN_IGNORE_CMD;
} else if (result != BNEP_SUCCESS) {
PAN_TRACE_ERROR("PAN failed to send data buf to the PANU");
+ pcb->write.errors++;
return (tPAN_RESULT)result;
}
+ pcb->write.octets += p_buf->len;
+ pcb->write.packets++;
+
PAN_TRACE_DEBUG("PAN successfully sent data buf to the PANU");
+
return PAN_SUCCESS;
}
@@ -647,3 +669,34 @@ void PAN_Init(void) {
pan_cb.trace_level = BT_TRACE_LEVEL_NONE; /* No traces */
#endif
}
+
+#define DUMPSYS_TAG "shim::legacy::pan"
+void PAN_Dumpsys(int fd) {
+ LOG_DUMPSYS_TITLE(fd, DUMPSYS_TAG);
+
+ LOG_DUMPSYS(fd, "Connections:%hhu roles configured:%s current:%s previous:%s",
+ pan_cb.num_conns, pan_role_to_text(pan_cb.role).c_str(),
+ pan_role_to_text(pan_cb.active_role).c_str(),
+ pan_role_to_text(pan_cb.prv_active_role).c_str());
+ const tPAN_CONN* pcb = &pan_cb.pcb[0];
+ for (int i = 0; i < MAX_PAN_CONNS; i++, pcb++) {
+ if (pcb->con_state == PAN_STATE_IDLE) continue;
+ LOG_DUMPSYS(fd, " Id:%d peer:%s", i, PRIVATE_ADDRESS(pcb->rem_bda));
+ LOG_DUMPSYS(
+ fd,
+ " rx_packets:%-5lu rx_octets:%-8lu rx_errors:%-5lu rx_drops:%-5lu",
+ (unsigned long)pcb->read.packets, (unsigned long)pcb->read.octets,
+ (unsigned long)pcb->read.errors, (unsigned long)pcb->read.drops);
+ LOG_DUMPSYS(
+ fd,
+ " tx_packets:%-5lu tx_octets:%-8lu tx_errors:%-5lu tx_drops:%-5lu",
+ (unsigned long)pcb->write.packets, (unsigned long)pcb->write.octets,
+ (unsigned long)pcb->write.errors, (unsigned long)pcb->write.drops);
+ LOG_DUMPSYS(fd,
+ " src_uuid:0x%04x[prev:0x%04x] dst_uuid:0x%04x[prev:0x%04x] "
+ "bad_pkts:%hu",
+ pcb->src_uuid, pcb->dst_uuid, pcb->prv_src_uuid,
+ pcb->prv_dst_uuid, pcb->bad_pkts_rcvd);
+ }
+}
+#undef DUMPSYS_TAG
diff --git a/stack/pan/pan_int.h b/stack/pan/pan_int.h
index fec07868a..709d5ec8d 100644
--- a/stack/pan/pan_int.h
+++ b/stack/pan/pan_int.h
@@ -65,6 +65,13 @@ typedef struct {
uint16_t ip_addr_known;
uint32_t ip_addr;
+ struct {
+ size_t octets{0};
+ size_t packets{0};
+ size_t errors{0};
+ size_t drops{0};
+ } write, read;
+
} tPAN_CONN;
/* The main PAN control block
diff --git a/stack/pan/pan_main.cc b/stack/pan/pan_main.cc
index cad53abd1..e4c1151a1 100644
--- a/stack/pan/pan_main.cc
+++ b/stack/pan/pan_main.cc
@@ -393,6 +393,7 @@ void pan_data_buf_ind_cb(uint16_t handle, const RawAddress& src,
if (pcb->con_state != PAN_STATE_CONNECTED) {
PAN_TRACE_ERROR("PAN Data indication in wrong state %d for handle %d",
pcb->con_state, handle);
+ pcb->read.drops++;
osi_free(p_buf);
return;
}
@@ -400,6 +401,9 @@ void pan_data_buf_ind_cb(uint16_t handle, const RawAddress& src,
p_data = (uint8_t*)(p_buf + 1) + p_buf->offset;
len = p_buf->len;
+ pcb->read.octets += len;
+ pcb->read.packets++;
+
PAN_TRACE_EVENT(
"pan_data_buf_ind_cb - for handle %d, protocol 0x%x, length %d, ext %d",
handle, protocol, len, ext);
@@ -447,6 +451,7 @@ void pan_data_buf_ind_cb(uint16_t handle, const RawAddress& src,
if (result != BNEP_SUCCESS && result != BNEP_IGNORE_CMD)
PAN_TRACE_ERROR("Failed to write data for PAN connection handle %d",
dst_pcb->handle);
+ pcb->read.errors++;
osi_free(p_buf);
return;
}
diff --git a/test/Android.bp b/test/Android.bp
index ee93ae2a9..0a95be027 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -399,3 +399,10 @@ filegroup {
"common/init_flags.cc",
],
}
+
+filegroup {
+ name: "TestMockBluetoothInterface",
+ srcs: [
+ "mock/mock_bluetooth_interface.cc",
+ ],
+}
diff --git a/test/mock/mock_bluetooth_interface.cc b/test/mock/mock_bluetooth_interface.cc
new file mode 100644
index 000000000..bb09b0574
--- /dev/null
+++ b/test/mock/mock_bluetooth_interface.cc
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2021 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 <cstdint>
+
+#include "btif/include/stack_manager.h"
+#include "device/include/interop.h"
+#include "hardware/bluetooth.h"
+#include "stack/include/bt_octets.h"
+#include "types/raw_address.h"
+
+void set_hal_cbacks(bt_callbacks_t* callbacks) {}
+
+static int init(bt_callbacks_t* callbacks, bool start_restricted,
+ bool is_common_criteria_mode, int config_compare_result,
+ const char** init_flags, bool is_atv) {
+ return BT_STATUS_SUCCESS;
+}
+
+static int enable() { return BT_STATUS_SUCCESS; }
+
+static int disable(void) { return BT_STATUS_SUCCESS; }
+
+static void cleanup(void) {}
+
+bool is_restricted_mode() { return false; }
+bool is_common_criteria_mode() { return false; }
+
+int get_common_criteria_config_compare_result() { return BT_STATUS_SUCCESS; }
+
+bool is_atv_device() { return false; }
+
+static int get_adapter_properties(void) { return BT_STATUS_SUCCESS; }
+
+static int get_adapter_property(bt_property_type_t type) {
+ return BT_STATUS_SUCCESS;
+}
+
+static int set_adapter_property(const bt_property_t* property) {
+ return BT_STATUS_SUCCESS;
+}
+
+int get_remote_device_properties(RawAddress* remote_addr) {
+ return BT_STATUS_SUCCESS;
+}
+
+int get_remote_device_property(RawAddress* remote_addr,
+ bt_property_type_t type) {
+ return BT_STATUS_SUCCESS;
+}
+
+int set_remote_device_property(RawAddress* remote_addr,
+ const bt_property_t* property) {
+ return BT_STATUS_SUCCESS;
+}
+
+int get_remote_services(RawAddress* remote_addr, int transport) {
+ return BT_STATUS_SUCCESS;
+}
+
+static int start_discovery(void) { return BT_STATUS_SUCCESS; }
+
+static int cancel_discovery(void) { return BT_STATUS_SUCCESS; }
+
+static int create_bond(const RawAddress* bd_addr, int transport) {
+ return BT_STATUS_SUCCESS;
+}
+
+static int create_bond_out_of_band(const RawAddress* bd_addr, int transport,
+ const bt_oob_data_t* p192_data,
+ const bt_oob_data_t* p256_data) {
+ return BT_STATUS_SUCCESS;
+}
+
+static int generate_local_oob_data(tBT_TRANSPORT transport) {
+ return BT_STATUS_SUCCESS;
+}
+
+static int cancel_bond(const RawAddress* bd_addr) { return BT_STATUS_SUCCESS; }
+
+static int remove_bond(const RawAddress* bd_addr) { return BT_STATUS_SUCCESS; }
+
+static int get_connection_state(const RawAddress* bd_addr) {
+ return BT_STATUS_SUCCESS;
+}
+
+static int pin_reply(const RawAddress* bd_addr, uint8_t accept, uint8_t pin_len,
+ bt_pin_code_t* pin_code) {
+ return BT_STATUS_SUCCESS;
+}
+
+static int ssp_reply(const RawAddress* bd_addr, bt_ssp_variant_t variant,
+ uint8_t accept, uint32_t passkey) {
+ return BT_STATUS_SUCCESS;
+}
+
+static int read_energy_info() { return BT_STATUS_SUCCESS; }
+
+static void dump(int fd, const char** arguments) {}
+
+static void dumpMetrics(std::string* output) {}
+
+static const void* get_profile_interface(const char* profile_id) {
+ return nullptr;
+}
+
+int dut_mode_configure(uint8_t enable) { return BT_STATUS_SUCCESS; }
+
+int dut_mode_send(uint16_t opcode, uint8_t* buf, uint8_t len) {
+ return BT_STATUS_SUCCESS;
+}
+
+int le_test_mode(uint16_t opcode, uint8_t* buf, uint8_t len) {
+ return BT_STATUS_SUCCESS;
+}
+
+static int set_os_callouts(bt_os_callouts_t* callouts) {
+ return BT_STATUS_SUCCESS;
+}
+
+static int config_clear(void) { return 0; }
+
+static bluetooth::avrcp::ServiceInterface* get_avrcp_service(void) {
+ return nullptr;
+}
+
+static std::string obfuscate_address(const RawAddress& address) {
+ return std::string("Test");
+}
+
+static int get_metric_id(const RawAddress& address) { return 0; }
+
+static int set_dynamic_audio_buffer_size(int codec, int size) { return 0; }
+
+EXPORT_SYMBOL bt_interface_t bluetoothInterface = {
+ sizeof(bluetoothInterface),
+ init,
+ enable,
+ disable,
+ cleanup,
+ get_adapter_properties,
+ get_adapter_property,
+ set_adapter_property,
+ get_remote_device_properties,
+ get_remote_device_property,
+ set_remote_device_property,
+ nullptr,
+ get_remote_services,
+ start_discovery,
+ cancel_discovery,
+ create_bond,
+ create_bond_out_of_band,
+ remove_bond,
+ cancel_bond,
+ get_connection_state,
+ pin_reply,
+ ssp_reply,
+ get_profile_interface,
+ dut_mode_configure,
+ dut_mode_send,
+ le_test_mode,
+ set_os_callouts,
+ read_energy_info,
+ dump,
+ dumpMetrics,
+ config_clear,
+ interop_database_clear,
+ interop_database_add,
+ get_avrcp_service,
+ obfuscate_address,
+ get_metric_id,
+ set_dynamic_audio_buffer_size,
+ generate_local_oob_data};
+
+// callback reporting helpers
+
+bt_property_t* property_deep_copy_array(int num_properties,
+ bt_property_t* properties) {
+ return nullptr;
+}
+
+void invoke_adapter_state_changed_cb(bt_state_t state) {}
+
+void invoke_adapter_properties_cb(bt_status_t status, int num_properties,
+ bt_property_t* properties) {}
+
+void invoke_remote_device_properties_cb(bt_status_t status, RawAddress bd_addr,
+ int num_properties,
+ bt_property_t* properties) {}
+
+void invoke_device_found_cb(int num_properties, bt_property_t* properties) {}
+
+void invoke_discovery_state_changed_cb(bt_discovery_state_t state) {}
+
+void invoke_pin_request_cb(RawAddress bd_addr, bt_bdname_t bd_name,
+ uint32_t cod, bool min_16_digit) {}
+
+void invoke_ssp_request_cb(RawAddress bd_addr, bt_bdname_t bd_name,
+ uint32_t cod, bt_ssp_variant_t pairing_variant,
+ uint32_t pass_key) {}
+
+void invoke_oob_data_request_cb(tBT_TRANSPORT t, bool valid, Octet16 c,
+ Octet16 r, RawAddress raw_address,
+ uint8_t address_type) {}
+
+void invoke_bond_state_changed_cb(bt_status_t status, RawAddress bd_addr,
+ bt_bond_state_t state, int fail_reason) {}
+
+void invoke_acl_state_changed_cb(bt_status_t status, RawAddress bd_addr,
+ bt_acl_state_t state, int transport_link_type,
+ bt_hci_error_code_t hci_reason) {}
+
+void invoke_thread_evt_cb(bt_cb_thread_evt event) {}
+
+void invoke_le_test_mode_cb(bt_status_t status, uint16_t count) {}
+
+// takes ownership of |uid_data|
+void invoke_energy_info_cb(bt_activity_energy_info energy_info,
+ bt_uid_traffic_t* uid_data) {}
+
+void invoke_link_quality_report_cb(uint64_t timestamp, int report_id, int rssi,
+ int snr, int retransmission_count,
+ int packets_not_receive_count,
+ int negative_acknowledgement_count) {}
diff --git a/test/mock/mock_bta_dm_api.cc b/test/mock/mock_bta_dm_api.cc
index a7fb58730..4b1720843 100644
--- a/test/mock/mock_bta_dm_api.cc
+++ b/test/mock/mock_bta_dm_api.cc
@@ -223,7 +223,7 @@ void BTA_EnableTestMode(void) {
mock_function_count_map[__func__]++;
test::mock::bta_dm_api::BTA_EnableTestMode();
}
-void BTA_GetEirService(uint8_t* p_eir, size_t eir_len,
+void BTA_GetEirService(const uint8_t* p_eir, size_t eir_len,
tBTA_SERVICE_MASK* p_services) {
mock_function_count_map[__func__]++;
test::mock::bta_dm_api::BTA_GetEirService(p_eir, eir_len, p_services);
diff --git a/test/mock/mock_bta_dm_api.h b/test/mock/mock_bta_dm_api.h
index a71509074..b4413d5cd 100644
--- a/test/mock/mock_bta_dm_api.h
+++ b/test/mock/mock_bta_dm_api.h
@@ -433,11 +433,11 @@ extern struct BTA_EnableTestMode BTA_EnableTestMode;
// Params: uint8_t* p_eir, size_t eir_len, tBTA_SERVICE_MASK* p_services
// Return: void
struct BTA_GetEirService {
- std::function<void(uint8_t* p_eir, size_t eir_len,
+ std::function<void(const uint8_t* p_eir, size_t eir_len,
tBTA_SERVICE_MASK* p_services)>
- body{
- [](uint8_t* p_eir, size_t eir_len, tBTA_SERVICE_MASK* p_services) {}};
- void operator()(uint8_t* p_eir, size_t eir_len,
+ body{[](const uint8_t* p_eir, size_t eir_len,
+ tBTA_SERVICE_MASK* p_services) {}};
+ void operator()(const uint8_t* p_eir, size_t eir_len,
tBTA_SERVICE_MASK* p_services) {
body(p_eir, eir_len, p_services);
};
diff --git a/test/mock/mock_btif_bta_pan_co_rx.cc b/test/mock/mock_btif_bta_pan_co_rx.cc
new file mode 100644
index 000000000..085653464
--- /dev/null
+++ b/test/mock/mock_btif_bta_pan_co_rx.cc
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2021 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.
+ */
+
+/*
+ * Generated mock file from original source file
+ * Functions generated:7
+ *
+ * mockcify.pl ver 0.2.1
+ */
+
+#include <cstdint>
+#include <functional>
+#include <map>
+#include <string>
+
+extern std::map<std::string, int> mock_function_count_map;
+
+// Mock include file to share data between tests and mock
+#include "test/mock/mock_btif_bta_pan_co_rx.h"
+
+// Mocked compile conditionals, if any
+#ifndef UNUSED_ATTR
+#define UNUSED_ATTR
+#endif
+
+// Mocked internal structures, if any
+
+namespace test {
+namespace mock {
+namespace btif_bta_pan_co_rx {
+
+// Function state capture and return values, if needed
+struct bta_pan_co_init bta_pan_co_init;
+struct bta_pan_co_close bta_pan_co_close;
+struct bta_pan_co_mfilt_ind bta_pan_co_mfilt_ind;
+struct bta_pan_co_pfilt_ind bta_pan_co_pfilt_ind;
+struct bta_pan_co_rx_flow bta_pan_co_rx_flow;
+struct bta_pan_co_rx_path bta_pan_co_rx_path;
+struct bta_pan_co_tx_path bta_pan_co_tx_path;
+
+} // namespace btif_bta_pan_co_rx
+} // namespace mock
+} // namespace test
+
+// Mocked functions, if any
+uint8_t bta_pan_co_init(uint8_t* q_level) {
+ mock_function_count_map[__func__]++;
+ return test::mock::btif_bta_pan_co_rx::bta_pan_co_init(q_level);
+}
+void bta_pan_co_close(uint16_t handle, uint8_t app_id) {
+ mock_function_count_map[__func__]++;
+ test::mock::btif_bta_pan_co_rx::bta_pan_co_close(handle, app_id);
+}
+void bta_pan_co_mfilt_ind(UNUSED_ATTR uint16_t handle,
+ UNUSED_ATTR bool indication,
+ UNUSED_ATTR tBTA_PAN_STATUS result,
+ UNUSED_ATTR uint16_t len,
+ UNUSED_ATTR uint8_t* p_filters) {
+ mock_function_count_map[__func__]++;
+ test::mock::btif_bta_pan_co_rx::bta_pan_co_mfilt_ind(handle, indication,
+ result, len, p_filters);
+}
+void bta_pan_co_pfilt_ind(UNUSED_ATTR uint16_t handle,
+ UNUSED_ATTR bool indication,
+ UNUSED_ATTR tBTA_PAN_STATUS result,
+ UNUSED_ATTR uint16_t len,
+ UNUSED_ATTR uint8_t* p_filters) {
+ mock_function_count_map[__func__]++;
+ test::mock::btif_bta_pan_co_rx::bta_pan_co_pfilt_ind(handle, indication,
+ result, len, p_filters);
+}
+void bta_pan_co_rx_flow(UNUSED_ATTR uint16_t handle, UNUSED_ATTR uint8_t app_id,
+ UNUSED_ATTR bool enable) {
+ mock_function_count_map[__func__]++;
+ test::mock::btif_bta_pan_co_rx::bta_pan_co_rx_flow(handle, app_id, enable);
+}
+void bta_pan_co_rx_path(UNUSED_ATTR uint16_t handle,
+ UNUSED_ATTR uint8_t app_id) {
+ mock_function_count_map[__func__]++;
+ test::mock::btif_bta_pan_co_rx::bta_pan_co_rx_path(handle, app_id);
+}
+void bta_pan_co_tx_path(uint16_t handle, uint8_t app_id) {
+ mock_function_count_map[__func__]++;
+ test::mock::btif_bta_pan_co_rx::bta_pan_co_tx_path(handle, app_id);
+}
+
+// END mockcify generation
diff --git a/test/mock/mock_btif_bta_pan_co_rx.h b/test/mock/mock_btif_bta_pan_co_rx.h
new file mode 100644
index 000000000..b3055b7be
--- /dev/null
+++ b/test/mock/mock_btif_bta_pan_co_rx.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2021 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.
+ */
+
+/*
+ * Generated mock file from original source file
+ * Functions generated:7
+ *
+ * mockcify.pl ver 0.2.1
+ */
+
+#include <cstdint>
+#include <functional>
+#include <map>
+#include <string>
+
+extern std::map<std::string, int> mock_function_count_map;
+
+// Original included files, if any
+// NOTE: Since this is a mock file with mock definitions some number of
+// include files may not be required. The include-what-you-use
+// still applies, but crafting proper inclusion is out of scope
+// for this effort. This compilation unit may compile as-is, or
+// may need attention to prune the inclusion set.
+#include <base/logging.h>
+#include <hardware/bluetooth.h>
+#include <hardware/bt_pan.h>
+#include <string.h>
+
+#include "bta/include/bta_pan_api.h"
+
+// Mocked compile conditionals, if any
+#ifndef UNUSED_ATTR
+#define UNUSED_ATTR
+#endif
+
+namespace test {
+namespace mock {
+namespace btif_bta_pan_co_rx {
+
+// Shared state between mocked functions and tests
+// Name: bta_pan_co_init
+// Params: uint8_t* q_level
+// Returns: uint8_t
+struct bta_pan_co_init {
+ std::function<uint8_t(uint8_t* q_level)> body{
+ [](uint8_t* q_level) { return 0; }};
+ uint8_t operator()(uint8_t* q_level) { return body(q_level); };
+};
+extern struct bta_pan_co_init bta_pan_co_init;
+// Name: bta_pan_co_close
+// Params: uint16_t handle, uint8_t app_id
+// Returns: void
+struct bta_pan_co_close {
+ std::function<void(uint16_t handle, uint8_t app_id)> body{
+ [](uint16_t handle, uint8_t app_id) { ; }};
+ void operator()(uint16_t handle, uint8_t app_id) { body(handle, app_id); };
+};
+extern struct bta_pan_co_close bta_pan_co_close;
+// Name: bta_pan_co_mfilt_ind
+// Params: UNUSED_ATTR uint16_t handle, UNUSED_ATTR bool indication, UNUSED_ATTR
+// tBTA_PAN_STATUS result, UNUSED_ATTR uint16_t len, UNUSED_ATTR uint8_t*
+// p_filters Returns: void
+struct bta_pan_co_mfilt_ind {
+ std::function<void(UNUSED_ATTR uint16_t handle, UNUSED_ATTR bool indication,
+ UNUSED_ATTR tBTA_PAN_STATUS result,
+ UNUSED_ATTR uint16_t len, UNUSED_ATTR uint8_t* p_filters)>
+ body{[](UNUSED_ATTR uint16_t handle, UNUSED_ATTR bool indication,
+ UNUSED_ATTR tBTA_PAN_STATUS result, UNUSED_ATTR uint16_t len,
+ UNUSED_ATTR uint8_t* p_filters) { ; }};
+ void operator()(UNUSED_ATTR uint16_t handle, UNUSED_ATTR bool indication,
+ UNUSED_ATTR tBTA_PAN_STATUS result, UNUSED_ATTR uint16_t len,
+ UNUSED_ATTR uint8_t* p_filters) {
+ body(handle, indication, result, len, p_filters);
+ };
+};
+extern struct bta_pan_co_mfilt_ind bta_pan_co_mfilt_ind;
+// Name: bta_pan_co_pfilt_ind
+// Params: UNUSED_ATTR uint16_t handle, UNUSED_ATTR bool indication, UNUSED_ATTR
+// tBTA_PAN_STATUS result, UNUSED_ATTR uint16_t len, UNUSED_ATTR uint8_t*
+// p_filters Returns: void
+struct bta_pan_co_pfilt_ind {
+ std::function<void(UNUSED_ATTR uint16_t handle, UNUSED_ATTR bool indication,
+ UNUSED_ATTR tBTA_PAN_STATUS result,
+ UNUSED_ATTR uint16_t len, UNUSED_ATTR uint8_t* p_filters)>
+ body{[](UNUSED_ATTR uint16_t handle, UNUSED_ATTR bool indication,
+ UNUSED_ATTR tBTA_PAN_STATUS result, UNUSED_ATTR uint16_t len,
+ UNUSED_ATTR uint8_t* p_filters) { ; }};
+ void operator()(UNUSED_ATTR uint16_t handle, UNUSED_ATTR bool indication,
+ UNUSED_ATTR tBTA_PAN_STATUS result, UNUSED_ATTR uint16_t len,
+ UNUSED_ATTR uint8_t* p_filters) {
+ body(handle, indication, result, len, p_filters);
+ };
+};
+extern struct bta_pan_co_pfilt_ind bta_pan_co_pfilt_ind;
+// Name: bta_pan_co_rx_flow
+// Params: UNUSED_ATTR uint16_t handle, UNUSED_ATTR uint8_t app_id, UNUSED_ATTR
+// bool enable Returns: void
+struct bta_pan_co_rx_flow {
+ std::function<void(UNUSED_ATTR uint16_t handle, UNUSED_ATTR uint8_t app_id,
+ UNUSED_ATTR bool enable)>
+ body{[](UNUSED_ATTR uint16_t handle, UNUSED_ATTR uint8_t app_id,
+ UNUSED_ATTR bool enable) { ; }};
+ void operator()(UNUSED_ATTR uint16_t handle, UNUSED_ATTR uint8_t app_id,
+ UNUSED_ATTR bool enable) {
+ body(handle, app_id, enable);
+ };
+};
+extern struct bta_pan_co_rx_flow bta_pan_co_rx_flow;
+// Name: bta_pan_co_rx_path
+// Params: UNUSED_ATTR uint16_t handle, UNUSED_ATTR uint8_t app_id
+// Returns: void
+struct bta_pan_co_rx_path {
+ std::function<void(UNUSED_ATTR uint16_t handle, UNUSED_ATTR uint8_t app_id)>
+ body{[](UNUSED_ATTR uint16_t handle, UNUSED_ATTR uint8_t app_id) { ; }};
+ void operator()(UNUSED_ATTR uint16_t handle, UNUSED_ATTR uint8_t app_id) {
+ body(handle, app_id);
+ };
+};
+extern struct bta_pan_co_rx_path bta_pan_co_rx_path;
+// Name: bta_pan_co_tx_path
+// Params: uint16_t handle, uint8_t app_id
+// Returns: void
+struct bta_pan_co_tx_path {
+ std::function<void(uint16_t handle, uint8_t app_id)> body{
+ [](uint16_t handle, uint8_t app_id) { ; }};
+ void operator()(uint16_t handle, uint8_t app_id) { body(handle, app_id); };
+};
+extern struct bta_pan_co_tx_path bta_pan_co_tx_path;
+
+} // namespace btif_bta_pan_co_rx
+} // namespace mock
+} // namespace test
+
+// END mockcify generation
diff --git a/test/mock/mock_main_shim_BtifConfigInterface.cc b/test/mock/mock_main_shim_BtifConfigInterface.cc
index 1981aef8b..7198d178c 100644
--- a/test/mock/mock_main_shim_BtifConfigInterface.cc
+++ b/test/mock/mock_main_shim_BtifConfigInterface.cc
@@ -85,6 +85,8 @@ std::vector<std::string>
bluetooth::shim::BtifConfigInterface::GetPersistentDevices() {
return std::vector<std::string>();
}
+void bluetooth::shim::BtifConfigInterface::
+ ConvertEncryptOrDecryptKeyIfNeeded(){};
void bluetooth::shim::BtifConfigInterface::Save(){};
void bluetooth::shim::BtifConfigInterface::Flush(){};
void bluetooth::shim::BtifConfigInterface::Clear(){};
diff --git a/test/mock/mock_main_shim_acl_api.cc b/test/mock/mock_main_shim_acl_api.cc
index 8c82df426..87da74f1b 100644
--- a/test/mock/mock_main_shim_acl_api.cc
+++ b/test/mock/mock_main_shim_acl_api.cc
@@ -29,6 +29,7 @@ extern std::map<std::string, int> mock_function_count_map;
#include "main/shim/acl_api.h"
#include "stack/include/bt_hdr.h"
+#include "stack/include/bt_octets.h"
#include "types/ble_address_with_type.h"
#include "types/raw_address.h"
@@ -71,3 +72,17 @@ void bluetooth::shim::ACL_ReadConnectionAddress(const RawAddress& pseudo_addr,
uint8_t* p_addr_type) {
mock_function_count_map[__func__]++;
}
+
+void bluetooth::shim::ACL_AddToAddressResolution(
+ const tBLE_BD_ADDR& legacy_address_with_type, const Octet16& peer_irk,
+ const Octet16& local_irk) {
+ mock_function_count_map[__func__]++;
+}
+
+void bluetooth::shim::ACL_RemoveFromAddressResolution(
+ const tBLE_BD_ADDR& legacy_address_with_type) {
+ mock_function_count_map[__func__]++;
+}
+void bluetooth::shim::ACL_ClearAddressResolution() {
+ mock_function_count_map[__func__]++;
+}
diff --git a/test/mock/mock_main_shim_btm_api.cc b/test/mock/mock_main_shim_btm_api.cc
index c02ce8c44..33f967271 100644
--- a/test/mock/mock_main_shim_btm_api.cc
+++ b/test/mock/mock_main_shim_btm_api.cc
@@ -244,21 +244,6 @@ uint8_t bluetooth::shim::BTM_BleMaxMultiAdvInstanceCount() {
mock_function_count_map[__func__]++;
return 0;
}
-uint8_t bluetooth::shim::BTM_GetEirSupportedServices(uint32_t* p_eir_uuid,
- uint8_t** p,
- uint8_t max_num_uuid16,
- uint8_t* p_num_uuid16) {
- mock_function_count_map[__func__]++;
- return 0;
-}
-uint8_t bluetooth::shim::BTM_GetEirUuidList(uint8_t* p_eir, size_t eir_len,
- uint8_t uuid_size,
- uint8_t* p_num_uuid,
- uint8_t* p_uuid_list,
- uint8_t max_num_uuid) {
- mock_function_count_map[__func__]++;
- return 0;
-}
void bluetooth::shim::BTM_AddEirService(uint32_t* p_eir_uuid, uint16_t uuid16) {
mock_function_count_map[__func__]++;
}
diff --git a/test/mock/mock_stack_acl_btm_pm.cc b/test/mock/mock_stack_acl_btm_pm.cc
index 7efca8380..c7cb0bf18 100644
--- a/test/mock/mock_stack_acl_btm_pm.cc
+++ b/test/mock/mock_stack_acl_btm_pm.cc
@@ -53,7 +53,7 @@ bool BTM_SetLinkPolicyActiveMode(const RawAddress& remote_bda) {
}
tBTM_CONTRL_STATE BTM_PM_ReadControllerState(void) {
mock_function_count_map[__func__]++;
- return 0;
+ return BTM_CONTRL_UNKNOWN;
}
tBTM_STATUS BTM_PmRegister(uint8_t mask, uint8_t* p_pm_id,
tBTM_PM_STATUS_CBACK* p_cb) {
diff --git a/test/mock/mock_stack_btm.cc b/test/mock/mock_stack_btm.cc
index d14af15f7..0dd5c65b4 100644
--- a/test/mock/mock_stack_btm.cc
+++ b/test/mock/mock_stack_btm.cc
@@ -18,6 +18,7 @@
* Generated mock file from original source file
*/
+#include "stack/include/btm_api.h"
#include "stack/include/btm_ble_api_types.h"
#include "stack/include/btm_client_interface.h"
#include "types/raw_address.h"
@@ -29,7 +30,12 @@ void BTM_BleBackgroundObserve(bool enable, tBTM_INQ_RESULTS_CB* p_results_cb) {}
void BTM_BleReadControllerFeatures(tBTM_BLE_CTRL_FEATURES_CBACK* p_vsc_cback) {}
uint8_t BTM_GetAcceptlistSize() { return 0; }
-struct btm_client_interface_s btm_client_interface = {};
+struct btm_client_interface_s btm_client_interface = {
+ .eir =
+ {
+ .BTM_GetEirSupportedServices = BTM_GetEirSupportedServices,
+ },
+};
struct btm_client_interface_s& get_btm_client_interface() {
return btm_client_interface;
diff --git a/test/mock/mock_stack_btm_inq.cc b/test/mock/mock_stack_btm_inq.cc
index 011754d4c..ef8d09258 100644
--- a/test/mock/mock_stack_btm_inq.cc
+++ b/test/mock/mock_stack_btm_inq.cc
@@ -137,9 +137,9 @@ uint8_t BTM_GetEirSupportedServices(uint32_t* p_eir_uuid, uint8_t** p,
mock_function_count_map[__func__]++;
return 0;
}
-uint8_t BTM_GetEirUuidList(uint8_t* p_eir, size_t eir_len, uint8_t uuid_size,
- uint8_t* p_num_uuid, uint8_t* p_uuid_list,
- uint8_t max_num_uuid) {
+uint8_t BTM_GetEirUuidList(const uint8_t* p_eir, size_t eir_len,
+ uint8_t uuid_size, uint8_t* p_num_uuid,
+ uint8_t* p_uuid_list, uint8_t max_num_uuid) {
mock_function_count_map[__func__]++;
return 0;
}
diff --git a/test/mock/mock_stack_gatt.cc b/test/mock/mock_stack_gatt.cc
index 60f4f3d06..5339ab058 100644
--- a/test/mock/mock_stack_gatt.cc
+++ b/test/mock/mock_stack_gatt.cc
@@ -145,9 +145,6 @@ tGATT_STATUS GATT_Disconnect(uint16_t conn_id) {
mock_function_count_map[__func__]++;
return GATT_SUCCESS;
}
-void GATTS_AddHandleRange(tGATTS_HNDL_RANGE* p_hndl_range) {
- mock_function_count_map[__func__]++;
-}
void GATTS_StopService(uint16_t service_handle) {
mock_function_count_map[__func__]++;
}
diff --git a/test/mock/mock_stack_pan_api.cc b/test/mock/mock_stack_pan_api.cc
index 53a8d088e..b33e1165a 100644
--- a/test/mock/mock_stack_pan_api.cc
+++ b/test/mock/mock_stack_pan_api.cc
@@ -89,3 +89,4 @@ void PAN_Init(void) { mock_function_count_map[__func__]++; }
void PAN_Register(tPAN_REGISTER* p_register) {
mock_function_count_map[__func__]++;
}
+void PAN_Dumpsys(int fd) { mock_function_count_map[__func__]++; }
diff --git a/test/suite/Android.bp b/test/suite/Android.bp
index 18050bc1d..74d281735 100644
--- a/test/suite/Android.bp
+++ b/test/suite/Android.bp
@@ -12,8 +12,16 @@ cc_test {
name: "net_test_bluetooth",
test_suites: ["device-tests"],
defaults: ["fluoride_defaults"],
- include_dirs: ["system/bt"],
+ include_dirs: [
+ "system/bt",
+ "system/bt/gd",
+ "system/bt/include",
+ "system/bt/stack/include",
+ ],
srcs: [
+ ":TestCommonMockFunctions",
+ ":TestMockBluetoothInterface",
+ ":TestMockDevice",
"adapter/adapter_unittest.cc",
"adapter/bluetooth_test.cc",
"gatt/gatt_test.cc",
@@ -39,8 +47,16 @@ cc_test {
cc_test {
name: "net_test_rfcomm_suite",
defaults: ["fluoride_defaults"],
- include_dirs: ["system/bt"],
+ include_dirs: [
+ "system/bt",
+ "system/bt/gd",
+ "system/bt/include",
+ "system/bt/stack/include",
+ ],
srcs: [
+ ":TestMockBluetoothInterface",
+ ":TestMockDevice",
+ ":TestCommonMockFunctions",
"adapter/bluetooth_test.cc",
"rfcomm/rfcomm_test.cc",
"rfcomm/rfcomm_unittest.cc",
diff --git a/types/raw_address.h b/types/raw_address.h
index 8f5b8dca6..b3e753014 100644
--- a/types/raw_address.h
+++ b/types/raw_address.h
@@ -88,6 +88,12 @@ inline void BDADDR_TO_STREAM(uint8_t*& p, const RawAddress& a) {
*(p)++ = (uint8_t)(a.address)[BD_ADDR_LEN - 1 - ijk];
}
+inline void STREAM_TO_BDADDR(RawAddress& a, const uint8_t*& p) {
+ uint8_t* pbda = (uint8_t*)(a.address) + BD_ADDR_LEN - 1;
+ for (int ijk = 0; ijk < BD_ADDR_LEN; ijk++) *pbda-- = *(p)++;
+}
+
+// DEPRECATED
inline void STREAM_TO_BDADDR(RawAddress& a, uint8_t*& p) {
uint8_t* pbda = (uint8_t*)(a.address) + BD_ADDR_LEN - 1;
for (int ijk = 0; ijk < BD_ADDR_LEN; ijk++) *pbda-- = *(p)++;
diff --git a/vendor_libs/test_vendor_lib/Android.bp b/vendor_libs/test_vendor_lib/Android.bp
index 62d03928c..7a122c4c1 100644
--- a/vendor_libs/test_vendor_lib/Android.bp
+++ b/vendor_libs/test_vendor_lib/Android.bp
@@ -13,6 +13,7 @@ cc_library_static {
defaults: [
"gd_defaults",
"gd_clang_tidy",
+ "gd_clang_tidy_ignore_android",
],
host_supported: true,
proprietary: true,
diff --git a/vendor_libs/test_vendor_lib/model/controller/dual_mode_controller.cc b/vendor_libs/test_vendor_lib/model/controller/dual_mode_controller.cc
index 654aeac71..6cd3fc5df 100644
--- a/vendor_libs/test_vendor_lib/model/controller/dual_mode_controller.cc
+++ b/vendor_libs/test_vendor_lib/model/controller/dual_mode_controller.cc
@@ -1611,12 +1611,16 @@ void DualModeController::LeReadBufferSizeV2(CommandView command) {
}
void DualModeController::LeSetAddressResolutionEnable(CommandView command) {
- // NOP
- auto payload =
- std::make_unique<bluetooth::packet::RawBuilder>(std::vector<uint8_t>(
- {static_cast<uint8_t>(bluetooth::hci::ErrorCode::SUCCESS)}));
- send_event_(bluetooth::hci::CommandCompleteBuilder::Create(
- kNumCommandPackets, command.GetOpCode(), std::move(payload)));
+ auto command_view = gd_hci::LeSetAddressResolutionEnableView::Create(
+ gd_hci::LeSecurityCommandView::Create(
+ gd_hci::SecurityCommandView::Create(command)));
+ ASSERT(command_view.IsValid());
+ auto status = link_layer_controller_.LeSetAddressResolutionEnable(
+ command_view.GetAddressResolutionEnable() ==
+ bluetooth::hci::Enable::ENABLED);
+ send_event_(
+ bluetooth::hci::LeSetAddressResolutionEnableCompleteBuilder::Create(
+ kNumCommandPackets, status));
}
void DualModeController::LeSetResovalablePrivateAddressTimeout(CommandView command) {
diff --git a/vendor_libs/test_vendor_lib/model/controller/link_layer_controller.cc b/vendor_libs/test_vendor_lib/model/controller/link_layer_controller.cc
index dc1ffbb56..091b700ed 100644
--- a/vendor_libs/test_vendor_lib/model/controller/link_layer_controller.cc
+++ b/vendor_libs/test_vendor_lib/model/controller/link_layer_controller.cc
@@ -1566,8 +1566,6 @@ void LinkLayerController::IncomingLeScanResponsePacket(
auto adv_type = scan_response.GetAdvertisementType();
auto address_type =
static_cast<LeAdvertisement::AddressType>(scan_response.GetAddressType());
- LOG_INFO("Scan response with %s",
- bluetooth::hci::OpCodeText(le_scan_enable_).c_str());
if (le_scan_enable_ == bluetooth::hci::OpCode::LE_SET_SCAN_ENABLE) {
if (adv_type != model::packets::AdvertisementType::SCAN_RESPONSE) {
return;
@@ -2878,6 +2876,15 @@ ErrorCode LinkLayerController::LeConnectListClear() {
return ErrorCode::SUCCESS;
}
+ErrorCode LinkLayerController::LeSetAddressResolutionEnable(bool enable) {
+ if (ResolvingListBusy()) {
+ return ErrorCode::COMMAND_DISALLOWED;
+ }
+
+ le_resolving_list_enabled_ = enable;
+ return ErrorCode::SUCCESS;
+}
+
ErrorCode LinkLayerController::LeResolvingListClear() {
if (ResolvingListBusy()) {
return ErrorCode::COMMAND_DISALLOWED;
diff --git a/vendor_libs/test_vendor_lib/model/controller/link_layer_controller.h b/vendor_libs/test_vendor_lib/model/controller/link_layer_controller.h
index 8693add30..f3ab29892 100644
--- a/vendor_libs/test_vendor_lib/model/controller/link_layer_controller.h
+++ b/vendor_libs/test_vendor_lib/model/controller/link_layer_controller.h
@@ -181,6 +181,7 @@ class LinkLayerController {
bool LeConnectListContainsDevice(Address addr, uint8_t addr_type);
bool LeConnectListFull();
bool ResolvingListBusy();
+ ErrorCode LeSetAddressResolutionEnable(bool enable);
ErrorCode LeResolvingListClear();
ErrorCode LeResolvingListAddDevice(Address addr, uint8_t addr_type,
std::array<uint8_t, kIrkSize> peerIrk,
@@ -463,6 +464,7 @@ class LinkLayerController {
std::array<uint8_t, kIrkSize> local_irk;
};
std::vector<ResolvingListEntry> le_resolving_list_;
+ bool le_resolving_list_enabled_{false};
std::array<LeAdvertiser, 7> advertisers_;
diff --git a/vendor_libs/test_vendor_lib/scripts/hci_socket.py b/vendor_libs/test_vendor_lib/scripts/hci_socket.py
index 427b8d406..aed257769 100644
--- a/vendor_libs/test_vendor_lib/scripts/hci_socket.py
+++ b/vendor_libs/test_vendor_lib/scripts/hci_socket.py
@@ -58,7 +58,7 @@ Usage:
"""
-#!/usr/bin/env python
+#!/usr/bin/env python3
import binascii
import cmd
diff --git a/vendor_libs/test_vendor_lib/scripts/link_layer_socket.py b/vendor_libs/test_vendor_lib/scripts/link_layer_socket.py
index d1268e928..e73ef0816 100644
--- a/vendor_libs/test_vendor_lib/scripts/link_layer_socket.py
+++ b/vendor_libs/test_vendor_lib/scripts/link_layer_socket.py
@@ -58,7 +58,7 @@ Usage:
"""
-#!/usr/bin/env python
+#!/usr/bin/env python3
import binascii
import cmd
diff --git a/vendor_libs/test_vendor_lib/scripts/send_simple_commands.py b/vendor_libs/test_vendor_lib/scripts/send_simple_commands.py
index 72f3e18c8..72d0a0095 100644
--- a/vendor_libs/test_vendor_lib/scripts/send_simple_commands.py
+++ b/vendor_libs/test_vendor_lib/scripts/send_simple_commands.py
@@ -58,7 +58,7 @@ Usage:
"""
-#!/usr/bin/env python
+#!/usr/bin/env python3
import binascii
import cmd
diff --git a/vendor_libs/test_vendor_lib/scripts/simple_link_layer_socket.py b/vendor_libs/test_vendor_lib/scripts/simple_link_layer_socket.py
index 5ce2a3594..3baabb726 100644
--- a/vendor_libs/test_vendor_lib/scripts/simple_link_layer_socket.py
+++ b/vendor_libs/test_vendor_lib/scripts/simple_link_layer_socket.py
@@ -45,7 +45,7 @@ send 18000000 09 010203040506 4de24c67454b 00 04 050954465446
"""
-#!/usr/bin/env python
+#!/usr/bin/env python3
import binascii
import cmd
diff --git a/vendor_libs/test_vendor_lib/scripts/simple_stack.py b/vendor_libs/test_vendor_lib/scripts/simple_stack.py
index 5139c50bc..36089eb63 100644
--- a/vendor_libs/test_vendor_lib/scripts/simple_stack.py
+++ b/vendor_libs/test_vendor_lib/scripts/simple_stack.py
@@ -58,7 +58,7 @@ Usage:
"""
-#!/usr/bin/env python
+#!/usr/bin/env python3
import binascii
import cmd
diff --git a/vendor_libs/test_vendor_lib/scripts/test_channel.py b/vendor_libs/test_vendor_lib/scripts/test_channel.py
index bca358c72..dc9d8f894 100644
--- a/vendor_libs/test_vendor_lib/scripts/test_channel.py
+++ b/vendor_libs/test_vendor_lib/scripts/test_channel.py
@@ -34,7 +34,7 @@ Usage:
as arguments.
"""
-#!/usr/bin/env python
+#!/usr/bin/env python3
import cmd
import random