diff options
33 files changed, 81 insertions, 7617 deletions
diff --git a/audio_hal_interface/aidl/a2dp_encoding.cc b/audio_hal_interface/aidl/a2dp_encoding.cc index 722d09334..3ed701dcd 100644 --- a/audio_hal_interface/aidl/a2dp_encoding.cc +++ b/audio_hal_interface/aidl/a2dp_encoding.cc @@ -14,8 +14,7 @@ * limitations under the License. */ /* - * Changes from Qualcomm Innovation Center are provided under the following license: - * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the @@ -169,52 +168,20 @@ bool A2dpTransport::GetPresentationPosition(uint64_t* remote_delay_report_ns, } void A2dpTransport::SourceMetadataChanged( - const source_metadata_t& source_metadata) { - + const source_metadata_t& source_metadata) { auto track_count = source_metadata.track_count; auto tracks = source_metadata.tracks; - LOG(INFO) << __func__ << "AIDL: " << track_count << " track(s) received"; - if (track_count == 0) { - LOG(WARNING) << __func__ << ": Invalid number of metadata changed tracks"; - return; + while (track_count) { + LOG(INFO) << __func__ << "AIDL: usage=" << tracks->usage + << ", content_type=" << tracks->content_type + << ", gain=" << tracks->gain; + --track_count; + ++tracks; } - - auto usage = source_metadata.tracks->usage; - - LOG(INFO) << __func__ << ", content_type=" << tracks->content_type - << ", track_count: " << track_count - << ", usage: " << usage; - - btif_ahim_update_src_metadata(source_metadata); - } -void A2dpTransport::SinkMetadataChanged( - const sink_metadata_t& sink_metadata) { - - auto track_count = sink_metadata.track_count; - auto tracks = sink_metadata.tracks; - - LOG(INFO) << __func__ << "AIDL: " << track_count << " track(s) received"; - if (track_count == 0) { - LOG(WARNING) << __func__ << ": Invalid number of metadata changed tracks"; - return; - } - - auto source = sink_metadata.tracks->source; - - LOG(INFO) << __func__ << ", track_count: " << track_count - << ", source: " << source; - - btif_ahim_update_sink_metadata(sink_metadata); - -} - -void A2dpTransport::SetLatencyMode(bool is_low_latency) { - LOG(INFO) << __func__ << " is_low_latency: " << is_low_latency; - btif_ahim_set_latency_mode(is_low_latency); -} +void A2dpTransport::SinkMetadataChanged(const sink_metadata_t&) {} tA2DP_CTRL_CMD A2dpTransport::GetPendingCmd() const { LOG(ERROR) << ": AIDL Is this function called"; @@ -638,18 +605,6 @@ bool setup_codec() { pcm_config_global = pcm_config; audio_config.set<AudioConfiguration::pcmConfig>(pcm_config); } - - A2dpCodecConfig* a2dp_config = bta_av_get_a2dp_current_codec(); - if (a2dp_config && active_hal_interface) { - btav_a2dp_codec_config_t current_codec = a2dp_config->getCodecConfig(); - if (current_codec.codec_type == BTAV_A2DP_CODEC_INDEX_SOURCE_APTX_ADAPTIVE) { - active_hal_interface->set_low_latency_allowed(true); - } - else { - active_hal_interface->set_low_latency_allowed(false); - } - } - return active_hal_interface->UpdateAudioConfig(audio_config); } diff --git a/audio_hal_interface/aidl/a2dp_transport.h b/audio_hal_interface/aidl/a2dp_transport.h index 8371dcb54..6c2c1e423 100644 --- a/audio_hal_interface/aidl/a2dp_transport.h +++ b/audio_hal_interface/aidl/a2dp_transport.h @@ -14,8 +14,7 @@ * limitations under the License. */ /* - * Changes from Qualcomm Innovation Center are provided under the following license: - * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the @@ -84,8 +83,6 @@ class A2dpTransport void SinkMetadataChanged(const sink_metadata_t&) override; - void SetLatencyMode(bool is_low_latency); - tA2DP_CTRL_CMD GetPendingCmd() const; void ResetPendingCmd(); diff --git a/audio_hal_interface/aidl/bluetooth_audio_port_impl.cc b/audio_hal_interface/aidl/bluetooth_audio_port_impl.cc index 25f321354..cb616a809 100644 --- a/audio_hal_interface/aidl/bluetooth_audio_port_impl.cc +++ b/audio_hal_interface/aidl/bluetooth_audio_port_impl.cc @@ -14,8 +14,7 @@ * limitations under the License. */ /* - * Changes from Qualcomm Innovation Center are provided under the following license: - * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the @@ -173,7 +172,6 @@ ndk::ScopedAStatus BluetoothAudioPortImpl::updateSinkMetadata( ndk::ScopedAStatus BluetoothAudioPortImpl::setLatencyMode( LatencyMode latency_mode) { bool is_low_latency = latency_mode == LatencyMode::LOW_LATENCY ? true : false; - transport_instance_->SetLatencyMode(is_low_latency); return ndk::ScopedAStatus::ok(); } diff --git a/audio_hal_interface/aidl/client_interface.cc b/audio_hal_interface/aidl/client_interface.cc index 1edd236df..b6e9b8dd4 100644 --- a/audio_hal_interface/aidl/client_interface.cc +++ b/audio_hal_interface/aidl/client_interface.cc @@ -14,7 +14,7 @@ * limitations under the License. */ /* Changes from Qualcomm Innovation Center are provided under the following license: - * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the @@ -110,12 +110,6 @@ bool BluetoothAudioClientInterface::is_aidl_available() { return aidl_available; } -void BluetoothAudioClientInterface::set_low_latency_allowed( - bool is_low_latency_allowed) { - LOG(INFO) << __func__ << ": is_low_latency_allowed: " << is_low_latency_allowed; - is_low_latency_allowed_ = is_low_latency_allowed; -} - std::vector<AudioCapabilities> BluetoothAudioClientInterface::GetAudioCapabilities() const { LOG(INFO) << __func__ << ": AIDL"; @@ -384,10 +378,6 @@ int BluetoothAudioClientInterface::StartSession() { DataMQDesc mq_desc; std::vector<LatencyMode> latency_modes = {LatencyMode::FREE}; - if (is_low_latency_allowed_) { - LOG(INFO) << __func__ << ": Adding Low latency to latency modes: "; - latency_modes.push_back(LatencyMode::LOW_LATENCY); - } binder_status_t binder_status = AIBinder_linkToDeath( provider_factory_->asBinder().get(), death_recipient_.get(), this); diff --git a/audio_hal_interface/aidl/client_interface.h b/audio_hal_interface/aidl/client_interface.h index 918fc45c7..ac8132456 100644 --- a/audio_hal_interface/aidl/client_interface.h +++ b/audio_hal_interface/aidl/client_interface.h @@ -14,8 +14,7 @@ * limitations under the License. */ /* - * Changes from Qualcomm Innovation Center are provided under the following license: - * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the @@ -123,8 +122,6 @@ class BluetoothAudioClientInterface { void FlushAudioData(); - void set_low_latency_allowed(bool is_low_latency_allowed); - static constexpr PcmConfiguration kInvalidPcmConfiguration = {}; static bool is_aidl_available(); @@ -160,7 +157,6 @@ class BluetoothAudioClientInterface { private: static inline bool aidl_available = true; - bool is_low_latency_allowed_ = false; IBluetoothTransportInstance* transport_; std::vector<AudioCapabilities> capabilities_; static std::unordered_set<BluetoothAudioClientInterface *> objs_address_; diff --git a/audio_hal_interface/aidl/hearing_aid_software_encoding.cc b/audio_hal_interface/aidl/hearing_aid_software_encoding.cc index 55c142c6d..b41fd9bb3 100644 --- a/audio_hal_interface/aidl/hearing_aid_software_encoding.cc +++ b/audio_hal_interface/aidl/hearing_aid_software_encoding.cc @@ -14,8 +14,7 @@ * limitations under the License. */ /* - * Changes from Qualcomm Innovation Center are provided under the following license: - * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the @@ -144,8 +143,6 @@ class HearingAidTransport void SinkMetadataChanged(const sink_metadata_t&) override {} - void SetLatencyMode(bool is_low_latency) override {} - void ResetPresentationPosition() override { VLOG(2) << __func__ << ": called."; remote_delay_report_ms_ = 0; diff --git a/audio_hal_interface/aidl/le_audio_software.cc b/audio_hal_interface/aidl/le_audio_software.cc index 407ca2ce5..1cd5329d4 100644 --- a/audio_hal_interface/aidl/le_audio_software.cc +++ b/audio_hal_interface/aidl/le_audio_software.cc @@ -16,7 +16,7 @@ */ /* * Changes from Qualcomm Innovation Center are provided under the following license: - * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the @@ -207,8 +207,6 @@ void LeAudioTransport::SinkMetadataChanged( btif_ahim_update_sink_metadata(sink_metadata); } -void LeAudioTransport::SetLatencyMode(bool is_low_latency) { -} tA2DP_CTRL_CMD LeAudioTransport:: GetPendingCmd() const { return lea_pending_cmd_; @@ -337,9 +335,6 @@ void LeAudioSinkTransport::SinkMetadataChanged( transport_->SinkMetadataChanged(sink_metadata); } -void LeAudioSinkTransport::SetLatencyMode(bool is_low_latency) { -} - void LeAudioSinkTransport::ResetPresentationPosition() { transport_->ResetPresentationPosition(); } @@ -437,9 +432,6 @@ void LeAudioSourceTransport::SinkMetadataChanged( transport_->SinkMetadataChanged(sink_metadata); } -void LeAudioSourceTransport::SetLatencyMode(bool is_low_latency) { -} - void LeAudioSourceTransport::ResetPresentationPosition() { transport_->ResetPresentationPosition(); } diff --git a/audio_hal_interface/aidl/le_audio_software.h b/audio_hal_interface/aidl/le_audio_software.h index 2b4af9f27..faa3a61fb 100644 --- a/audio_hal_interface/aidl/le_audio_software.h +++ b/audio_hal_interface/aidl/le_audio_software.h @@ -14,8 +14,7 @@ * limitations under the License. */ /* - * Changes from Qualcomm Innovation Center are provided under the following license: - * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the @@ -118,8 +117,6 @@ class LeAudioTransport { void SinkMetadataChanged(const sink_metadata_t& sink_metadata); - void SetLatencyMode(bool is_low_latency); - tA2DP_CTRL_CMD GetPendingCmd() const; void ResetPendingCmd(); @@ -176,8 +173,6 @@ class LeAudioSinkTransport void SinkMetadataChanged(const sink_metadata_t& sink_metadata) override; - void SetLatencyMode(bool is_low_latency) override; - tA2DP_CTRL_CMD GetPendingCmd() const; void ResetPendingCmd(); @@ -229,8 +224,6 @@ class LeAudioSourceTransport void SinkMetadataChanged(const sink_metadata_t& sink_metadata) override; - void SetLatencyMode(bool is_low_latency) override; - tA2DP_CTRL_CMD GetPendingCmd() const; void ResetPendingCmd(); diff --git a/audio_hal_interface/aidl/transport_instance.h b/audio_hal_interface/aidl/transport_instance.h index 56e2a0eae..f1d0311d5 100644 --- a/audio_hal_interface/aidl/transport_instance.h +++ b/audio_hal_interface/aidl/transport_instance.h @@ -14,8 +14,7 @@ * limitations under the License. */ /* - * Changes from Qualcomm Innovation Center are provided under the following license: - * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the @@ -141,8 +140,6 @@ class IBluetoothTransportInstance { const source_metadata_t& source_metadata) = 0; virtual void SinkMetadataChanged(const sink_metadata_t& sink_metadata) = 0; - virtual void SetLatencyMode(bool is_low_latency) = 0; - //virtual tA2DP_CTRL_CMD GetPendingCmd() const; //virtual void ResetPendingCmd(); diff --git a/bta/Android.bp b/bta/Android.bp index 4610d3a9b..ec9646e73 100644 --- a/bta/Android.bp +++ b/bta/Android.bp @@ -123,11 +123,6 @@ cc_library_static { "gatt/database_builder.cc", "hearing_aid/hearing_aid.cc", "hearing_aid/hearing_aid_audio_source.cc", - "has/has_client.cc", - "has/has_ctp.cc", - "has/has_journal.cc", - "has/has_preset.cc", - "has/has_types.cc", "hf_client/bta_hf_client_act.cc", "hf_client/bta_hf_client_api.cc", "hf_client/bta_hf_client_at.cc", diff --git a/bta/has/has_client.cc b/bta/has/has_client.cc deleted file mode 100644 index 02f65822b..000000000 --- a/bta/has/has_client.cc +++ /dev/null @@ -1,2187 +0,0 @@ -/* - * Copyright 2021 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * Changes from Qualcomm Innovation Center are provided under the following license: - * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. - * SPDX-License-Identifier: BSD-3-Clause-Clear - */ - -#include <base/bind.h> -#include <base/callback.h> -#include <base/logging.h> -#include <base/strings/string_number_conversions.h> -#include <hardware/bt_gatt_types.h> -#include <hardware/bt_has.h> - -#include <list> -#include <map> -#include <string> -#include <vector> - -#include "bta_csip_api.h" -#include "bta_gatt_api.h" -#include "bta_gatt_queue.h" -//#include "bta_groups.h" -#include "bta_has_api.h" -//#include "bta_le_audio_uuids.h" -#include "btm_int.h" -#include "btm_api.h" -#include "bt_types.h" -//#include "btm_sec.h" -#include "device/include/controller.h" -#include "gap_api.h" -#include "gatt_api.h" -#include "has_types.h" -#include "osi/include/log.h" -#include "osi/include/osi.h" -#include "osi/include/properties.h" - -using base::Closure; -using bluetooth::Uuid; -//using bluetooth::csis::CsisClient; -using bluetooth::has::ConnectionState; -using bluetooth::has::ErrorCode; -using bluetooth::has::kFeatureBitPresetSynchronizationSupported; -using bluetooth::has::kHasPresetIndexInvalid; -using bluetooth::has::PresetInfo; -using bluetooth::has::PresetInfoReason; -using le_audio::has::HasClient; -using le_audio::has::HasCtpGroupOpCoordinator; -using le_audio::has::HasCtpNtf; -using le_audio::has::HasCtpOp; -using le_audio::has::HasDevice; -using le_audio::has::HasGattOpContext; -using le_audio::has::HasJournalRecord; -using le_audio::has::HasPreset; -using le_audio::has::kControlPointMandatoryOpcodesBitmask; -using le_audio::has::kControlPointSynchronizedOpcodesBitmask; -using le_audio::has::kUuidActivePresetIndex; -using le_audio::has::kUuidHearingAccessService; -using le_audio::has::kUuidHearingAidFeatures; -using le_audio::has::kUuidHearingAidPresetControlPoint; -using le_audio::has::PresetCtpChangeId; -using le_audio::has::PresetCtpOpcode; - -void btif_storage_add_leaudio_has_device(const RawAddress& address, - std::vector<uint8_t> presets_bin, - uint8_t features, - uint8_t active_preset); -bool btif_storage_get_leaudio_has_presets(const RawAddress& address, - std::vector<uint8_t>& presets_bin, - uint8_t& active_preset); -void btif_storage_set_leaudio_has_presets(const RawAddress& address, - std::vector<uint8_t> presets_bin); -bool btif_storage_get_leaudio_has_features(const RawAddress& address, - uint8_t& features); -void btif_storage_set_leaudio_has_features(const RawAddress& address, - uint8_t features); -void btif_storage_set_leaudio_has_active_preset(const RawAddress& address, - uint8_t active_preset); -void btif_storage_remove_leaudio_has(const RawAddress& address); - -extern bool gatt_profile_get_eatt_support(const RawAddress& remote_bda); - -namespace { -class HasClientImpl; -HasClientImpl* instance; - -/** - * ----------------------------------------------------------------------------- - * Hearing Access Service - Client role - * ----------------------------------------------------------------------------- - * Overview: - * - * This is Hearing Access Service client class. - * - * Each connected peer device supporting Hearing Access Service (HAS) is being - * connected and has its characteristics discovered. All the characteristics - * and descriptors (incl. the optional ones) are being read or written during - * this initial connection stage. Encryption is also verified. If all of this - * succeeds the appropriate callbacks are being called to notify upper layer - * about the successful HAS device connection and its features and the list - * of available audio configuration presets. - * - * Each HA device is expected to have the HAS service instantiated. It must - * contain Hearing Aid Features characteristic and optionally Presets Control - * Point and Active Preset Index characteristics, allowing the user to read - * preset details, switch currently active preset and possibly rename some of - * them. - * - * Hearing Aid Features characteristic informs the client about the type of - * Hearign Aids device (Monaural, Binaural or Banded), which operations are - * supported via the Preset Control Point characteristic, about dynamically - * changing list of available presets, writable presets and the support for - * synchronised preset change operations on the Binaural Hearing Aid devices. - */ -class HasClientImpl : public HasClient { - public: - HasClientImpl(bluetooth::has::HasClientCallbacks* callbacks, - base::Closure initCb) - : gatt_if_(0), callbacks_(callbacks) { - BTA_GATTC_AppRegister( - [](tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { - if (instance && p_data) instance->GattcCallback(event, p_data); - }, - base::Bind( - [](base::Closure initCb, uint8_t client_id, uint8_t status) { - if (status != GATT_SUCCESS) { - LOG(ERROR) << ": Can't start Hearing Aid Service client " - "profile - no gatt clients left!"; - return; - } - if (instance) { - instance->gatt_if_ = client_id; - } - initCb.Run(); - }, - initCb), - true); - } - - ~HasClientImpl() override = default; - - void Connect(const RawAddress& address) override { - LOG(INFO) << __func__ << ": " << address; - - std::vector<RawAddress> addresses = {address}; - tBTA_CSIP_CSET cset_info; - memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); - cset_info = BTA_CsipGetCoordinatedSet(address.address[5]); - LOG(INFO) << __func__ << ":set size: " << loghex(cset_info.size); - addresses = cset_info.set_members; - //RawAddress addr = {}; - /*auto csis_api = CsisClient::Get(); - if (csis_api != nullptr) { - // Connect entire CAS set of devices - auto group_id = csis_api->GetGroupId( - address, bluetooth::Uuid::From16Bit(UUID_COMMON_AUDIO_SERVICE)); - addresses = csis_api->GetDeviceList(group_id); - }*/ - - if (addresses.empty()) { - LOG(WARNING) << __func__ << ": " << address << " is not part of any set"; - addresses = {address}; - } - - for (auto const& addr : addresses) { - auto device = std::find_if(devices_.begin(), devices_.end(), - HasDevice::MatchAddress(addr)); - if (device == devices_.end()) { - devices_.emplace_back(addr, true); - LOG(WARNING) << __func__ << ": " << address << " Gattc Open is called"; - BTA_GATTC_Open(gatt_if_, addr, true, GATT_TRANSPORT_LE, false); - - } else { - device->is_connecting_actively = true; - if (!device->IsConnected()) - BTA_GATTC_Open(gatt_if_, addr, true, GATT_TRANSPORT_LE, false); - } - } - } - - void AddFromStorage(const RawAddress& address, uint8_t features, - uint16_t is_acceptlisted) { - LOG(INFO) << __func__ << ": " <<address - << ", features=" << loghex(features) - << ", isAcceptlisted=" << is_acceptlisted; - - /* Notify upper layer about the device */ - callbacks_->OnDeviceAvailable(address, features); - if (is_acceptlisted) { - auto device = std::find_if(devices_.begin(), devices_.end(), - HasDevice::MatchAddress(address)); - if (device == devices_.end()) - devices_.push_back(HasDevice(address, features)); - - /* Connect in background */ - BTA_GATTC_Open(gatt_if_, address, true, BT_TRANSPORT_LE, false); - } - } - - void Disconnect(const RawAddress& address) override { - LOG(INFO) << __func__ << ": " << address; - - std::vector<RawAddress> addresses = {address}; - tBTA_CSIP_CSET cset_info; - memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); - cset_info = BTA_CsipGetCoordinatedSet(address.address[5]); - LOG(INFO) << __func__ << ": set size: " << loghex(cset_info.size); - addresses = cset_info.set_members; - /*auto csis_api = CsisClient::Get(); - if (csis_api != nullptr) { - // Disconnect entire CAS set of devices - auto group_id = csis_api->GetGroupId( - address, bluetooth::Uuid::From16Bit(UUID_COMMON_AUDIO_SERVICE)); - addresses = csis_api->GetDeviceList(group_id); - }*/ - - if (addresses.empty()) { - LOG(WARNING) << __func__ << ": " << address << " is not part of any set"; - addresses = {address}; - } - - for (auto const& addr : addresses) { - auto device = std::find_if(devices_.begin(), devices_.end(), - HasDevice::MatchAddress(addr)); - if (device == devices_.end()) { - LOG(WARNING) << ": Device not connected to profile" << addr; - return; - } - - auto conn_id = device->conn_id; - auto is_connecting_actively = device->is_connecting_actively; - devices_.erase(device); - - if (conn_id != GATT_INVALID_CONN_ID) { - BTA_GATTC_Close(conn_id); - callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, addr); - } else { - /* Removes active connection. */ - if (is_connecting_actively) BTA_GATTC_CancelOpen(gatt_if_, addr, true); - } - - /* Removes all registrations for connection. */ - BTA_GATTC_CancelOpen(0, addr, false); - } - } - - void UpdateJournalOpEntryStatus(HasDevice& device, HasGattOpContext context, - tGATT_STATUS status) { - /* Find journal entry by the context and update */ - auto journal_entry = std::find_if( - device.has_journal_.begin(), device.has_journal_.end(), - [&context](auto const& record) { - if (record.is_operation) { - return HasGattOpContext(record.op_context_handle) == context; - } - return false; - }); - - if (journal_entry == device.has_journal_.end()) { - LOG(WARNING) << ": Journaling error or journal length limit was set to " - "low. Unable to log the operation outcome."; - return; - } - - if (journal_entry == device.has_journal_.end()) { - LOG(ERROR) << __func__ - << ": Unable to find operation context in the journal!"; - return; - } - - journal_entry->op_status = status; - } - - std::optional<HasCtpOp> ExtractPendingCtpOp(uint16_t op_id) { - auto op_it = - std::find_if(pending_operations_.begin(), pending_operations_.end(), - [op_id](auto const& el) { return op_id == el.op_id; }); - - if (op_it != pending_operations_.end()) { - auto op = *op_it; - pending_operations_.erase(op_it); - - return op; - } - return std::nullopt; - } - - void EnqueueCtpOp(HasCtpOp op) { pending_operations_.push_back(op); } - - void OnHasActivePresetCycleStatus(uint16_t conn_id, tGATT_STATUS status, - void* user_data) { - LOG(INFO) << __func__ << ": status: " << +status; - - auto device = GetDevice(conn_id); - if (!device) { - LOG(WARNING) << ": Device not connected to profile, conn_id=" << +conn_id; - return; - } - - /* Journal update */ - LOG_ASSERT(user_data != nullptr) << "Has operation context is missing!"; - auto context = HasGattOpContext(user_data); - UpdateJournalOpEntryStatus(*device, context, status); - - auto op_opt = ExtractPendingCtpOp(context.ctp_op_id); - if (status == GATT_SUCCESS) return; - - /* This could be one of the coordinated group preset change request */ - pending_group_operation_timeouts_.erase(context.ctp_op_id); - - /* Error handling */ - if (!op_opt.has_value()) { - LOG(ERROR) << __func__ << ": Unknown operation error"; - return; - } - auto op = op_opt.value(); - callbacks_->OnActivePresetSelectError(op.addr_or_group, - GattStatus2SvcErrorCode(status)); - - if (status == GATT_DATABASE_OUT_OF_SYNC) { -// LOG_INFO("Database out of sync for %s", device->addr); - ClearDeviceInformationAndStartSearch(device); - } - } - - void OnHasPresetNameSetStatus(uint16_t conn_id, tGATT_STATUS status, - void* user_data) { - auto device = GetDevice(conn_id); - if (!device) { - LOG(WARNING) << ": Device not connected to profile, conn_id=" << +conn_id; - return; - } - - LOG_ASSERT(user_data != nullptr) << ": Has operation context is missing!"; - HasGattOpContext context(user_data); - - /* Journal update */ - UpdateJournalOpEntryStatus(*device, context, status); - - auto op_opt = ExtractPendingCtpOp(context.ctp_op_id); - if (status == GATT_SUCCESS) return; - - /* This could be one of the coordinated group preset change request */ - pending_group_operation_timeouts_.erase(context.ctp_op_id); - - /* Error handling */ - if (!op_opt.has_value()) { - LOG(ERROR) << __func__ << ": Unknown operation error"; - return; - } - auto op = op_opt.value(); - callbacks_->OnSetPresetNameError(device->addr, op.index, - GattStatus2SvcErrorCode(status)); - if (status == GATT_DATABASE_OUT_OF_SYNC) { - //LOG_INFO("Database out of sync for %s", device->addr); - ClearDeviceInformationAndStartSearch(device); - } - } - - void OnHasPresetNameGetStatus(uint16_t conn_id, tGATT_STATUS status, - void* user_data) { - auto device = GetDevice(conn_id); - if (!device) { - LOG(WARNING) << ": Device not connected to profile, conn_id=" << +conn_id; - return; - } - - LOG_ASSERT(user_data != nullptr) << ": Has operation context is missing!"; - HasGattOpContext context(user_data); - - /* Journal update */ - UpdateJournalOpEntryStatus(*device, context, status); - - auto op_opt = ExtractPendingCtpOp(context.ctp_op_id); - if (status == GATT_SUCCESS) return; - - /* Error handling */ - if (!op_opt.has_value()) { - LOG(ERROR) << __func__ << ": Unknown operation error"; - return; - } - auto op = op_opt.value(); - callbacks_->OnPresetInfoError(device->addr, op.index, - GattStatus2SvcErrorCode(status)); - - if (status == GATT_DATABASE_OUT_OF_SYNC) { - //LOG_INFO("Database out of sync for %s", device->addr); - ClearDeviceInformationAndStartSearch(device); - } else { - //LOG_ERROR("Devices %s: Control point not usable. Disconnecting!", device->addr); - BTA_GATTC_Close(device->conn_id); - } - } - - void OnHasPresetIndexOperation(uint16_t conn_id, tGATT_STATUS status, - void* user_data) { - LOG(INFO) << __func__; - - auto device = GetDevice(conn_id); - if (!device) { - LOG(WARNING) << ": Device not connected to profile, conn_id=" << +conn_id; - return; - } - - LOG_ASSERT(user_data != nullptr) << ": Has operation context is missing!"; - HasGattOpContext context(user_data); - - /* Journal update */ - UpdateJournalOpEntryStatus(*device, context, status); - - auto op_opt = ExtractPendingCtpOp(context.ctp_op_id); - if (status == GATT_SUCCESS) return; - - /* This could be one of the coordinated group preset change request */ - pending_group_operation_timeouts_.erase(context.ctp_op_id); - - /* Error handling */ - if (!op_opt.has_value()) { - LOG(ERROR) << __func__ << ": Unknown operation error"; - return; - } - - auto op = op_opt.value(); - if (op.opcode == PresetCtpOpcode::READ_PRESETS) { - callbacks_->OnPresetInfoError(device->addr, op.index, - GattStatus2SvcErrorCode(status)); - - } else { - callbacks_->OnActivePresetSelectError(op.addr_or_group, - GattStatus2SvcErrorCode(status)); - } - - if (status == GATT_DATABASE_OUT_OF_SYNC) { - //LOG_INFO("Database out of sync for %s", device->addr); - ClearDeviceInformationAndStartSearch(device); - } else { - //LOG_ERROR("Devices %s: Control point not usable. Disconnecting!", device->addr); - BTA_GATTC_Close(device->conn_id); - } - } - - void CpReadAllPresetsOperation(HasCtpOp operation) { - LOG(INFO) << __func__ << ": Operation: " << operation; - - if (std::holds_alternative<int>(operation.addr_or_group)) { - LOG(ERROR) << __func__ - << ": Read all presets on the entire group not supported."; - callbacks_->OnPresetInfoError(operation.addr_or_group, operation.index, - ErrorCode::OPERATION_NOT_POSSIBLE); - return; - } - - auto device = std::find_if( - devices_.begin(), devices_.end(), - HasDevice::MatchAddress(std::get<RawAddress>(operation.addr_or_group))); - if (device == devices_.end()) { - LOG(WARNING) << __func__ << ": Device not connected to profile addr: " - << std::get<RawAddress>(operation.addr_or_group); - callbacks_->OnPresetInfoError(device->addr, operation.index, - ErrorCode::OPERATION_NOT_POSSIBLE); - return; - } - - if (!device->SupportsPresets()) { - callbacks_->OnPresetInfoError(device->addr, operation.index, - ErrorCode::OPERATION_NOT_SUPPORTED); - } - - auto context = HasGattOpContext(operation); - - /* Journal update */ - device->has_journal_.Append(HasJournalRecord(operation, context)); - - /* Write to control point */ - EnqueueCtpOp(operation); - BtaGattQueue::WriteCharacteristic( - device->conn_id, device->cp_handle, operation.ToCharacteristicValue(), - GATT_WRITE, - [](uint16_t conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len, - const uint8_t* value, void* user_data) { - if (instance) - instance->OnHasPresetNameGetStatus(conn_id, status, user_data); - }, - context); - } - - ErrorCode CpPresetIndexOperationWriteReq(HasDevice& device, - HasCtpOp& operation) { - LOG(INFO) << __func__ << ": Operation: " << operation; - - if (!device.IsConnected()) return ErrorCode::OPERATION_NOT_POSSIBLE; - - if (!device.SupportsPresets()) return ErrorCode::OPERATION_NOT_SUPPORTED; - - if (!device.SupportsOperation(operation.opcode)) - return operation.IsGroupRequest() - ? ErrorCode::GROUP_OPERATION_NOT_SUPPORTED - : ErrorCode::OPERATION_NOT_SUPPORTED; - - if (!device.IsValidPreset(operation.index)) - return ErrorCode::INVALID_PRESET_INDEX; - - auto context = HasGattOpContext(operation); - - /* Journal update */ - device.has_journal_.Append(HasJournalRecord(operation, context)); - - /* Write to control point */ - EnqueueCtpOp(operation); - BtaGattQueue::WriteCharacteristic( - device.conn_id, device.cp_handle, operation.ToCharacteristicValue(), - GATT_WRITE, - [](uint16_t conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len, - const uint8_t* value, void* user_data) { - if (instance) - instance->OnHasPresetIndexOperation(conn_id, status, user_data); - }, - context); - - return ErrorCode::NO_ERROR; - } - - bool AreAllDevicesAvailable(const std::vector<RawAddress>& addresses) { - for (auto& addr : addresses) { - auto device = std::find_if(devices_.begin(), devices_.end(), - HasDevice::MatchAddress(addr)); - if (device == devices_.end() || !device->IsConnected()) { - return false; - } - } - return true; - } - - ErrorCode CpPresetOperationCaller( - HasCtpOp operation, - std::function<ErrorCode(HasDevice& device, HasCtpOp& operation)> - write_cb) { - LOG(INFO) << __func__ << ": Operation: " << operation; - auto status = ErrorCode::NO_ERROR; - - if (operation.IsGroupRequest()) { - /*auto csis_api = CsisClient::Get(); - if (csis_api == nullptr) { - No CSIS means no group operations - status = ErrorCode::GROUP_OPERATION_NOT_SUPPORTED; - - } else { - auto group_id = operation.GetGroupId(); - auto addresses = csis_api->GetDeviceList(group_id);*/ - std::vector<RawAddress> addresses; - tBTA_CSIP_CSET cset_info; - memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); - cset_info = BTA_CsipGetCoordinatedSet(operation.GetDeviceAddr().address[5]); - LOG(INFO) << __func__ << ": set size: " << loghex(cset_info.size); - addresses = cset_info.set_members; - - /* Perform the operation only when all the devices are available */ - if (!AreAllDevicesAvailable(addresses)) { - addresses.clear(); - } - - if (addresses.empty()) { - status = ErrorCode::OPERATION_NOT_POSSIBLE; - - } else { - /* Make this a coordinated operation */ - pending_group_operation_timeouts_.emplace( - operation.op_id, HasCtpGroupOpCoordinator(addresses, operation)); - - if (operation.IsSyncedOperation()) { - status = ErrorCode::GROUP_OPERATION_NOT_SUPPORTED; - - /* Clear the error if we find device to forward the operation */ - bool was_sent = false; - for (auto& addr : addresses) { - auto device = std::find_if(devices_.begin(), devices_.end(), - HasDevice::MatchAddress(addr)); - if (device != devices_.end()) { - status = write_cb(*device, operation); - if (status == ErrorCode::NO_ERROR) { - was_sent = true; - break; - } - } - } - if (!was_sent) status = ErrorCode::OPERATION_NOT_POSSIBLE; - - } else { - status = ErrorCode::GROUP_OPERATION_NOT_SUPPORTED; - - for (auto& addr : addresses) { - auto device = std::find_if(devices_.begin(), devices_.end(), - HasDevice::MatchAddress(addr)); - if (device != devices_.end()) { - status = write_cb(*device, operation); - if (status != ErrorCode::NO_ERROR) break; - } - } - } - - /* Erase group op coordinator on error */ - if (status != ErrorCode::NO_ERROR) { - pending_group_operation_timeouts_.erase(operation.op_id); - } - } - } else { - auto device = std::find_if(devices_.begin(), devices_.end(), - HasDevice::MatchAddress(std::get<RawAddress>( - operation.addr_or_group))); - status = ErrorCode::OPERATION_NOT_POSSIBLE; - if (device != devices_.end()) status = write_cb(*device, operation); - } - - return status; - } - - void CpPresetIndexOperation(HasCtpOp operation) { - LOG(INFO) << __func__ << ": Operation: " << operation; - - auto status = CpPresetOperationCaller( - operation, [](HasDevice& device, HasCtpOp operation) -> ErrorCode { - if (instance) - return instance->CpPresetIndexOperationWriteReq(device, operation); - return ErrorCode::OPERATION_NOT_POSSIBLE; - }); - - if (status != ErrorCode::NO_ERROR) { - switch (operation.opcode) { - case PresetCtpOpcode::READ_PRESETS: - LOG_ASSERT( - std::holds_alternative<RawAddress>(operation.addr_or_group)) - << ": Unsupported group operation!"; - - callbacks_->OnPresetInfoError( - std::get<RawAddress>(operation.addr_or_group), operation.index, - status); - break; - case PresetCtpOpcode::SET_ACTIVE_PRESET: - case PresetCtpOpcode::SET_ACTIVE_PRESET_SYNC: - callbacks_->OnActivePresetSelectError(operation.addr_or_group, - status); - break; - default: - break; - } - } - } - - ErrorCode CpPresetsCycleOperationWriteReq(HasDevice& device, - HasCtpOp& operation) { - LOG(INFO) << __func__ << ": addr: " << device.addr - << " operation: " << operation; - - if (!device.IsConnected()) return ErrorCode::OPERATION_NOT_POSSIBLE; - - if (!device.SupportsPresets()) return ErrorCode::OPERATION_NOT_SUPPORTED; - - if (!device.SupportsOperation(operation.opcode)) - return operation.IsGroupRequest() - ? ErrorCode::GROUP_OPERATION_NOT_SUPPORTED - : ErrorCode::OPERATION_NOT_SUPPORTED; - - auto context = HasGattOpContext(operation); - - /* Journal update */ - device.has_journal_.Append(HasJournalRecord(operation, context)); - - /* Write to control point */ - EnqueueCtpOp(operation); - BtaGattQueue::WriteCharacteristic( - device.conn_id, device.cp_handle, operation.ToCharacteristicValue(), - GATT_WRITE, - [](uint16_t conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len, - const uint8_t* value, void* user_data) { - if (instance) - instance->OnHasActivePresetCycleStatus(conn_id, status, user_data); - }, - context); - return ErrorCode::NO_ERROR; - } - - void CpPresetsCycleOperation(HasCtpOp operation) { - LOG(INFO) << __func__ << ": Operation: " << operation; - - auto status = CpPresetOperationCaller( - operation, [](HasDevice& device, HasCtpOp operation) -> ErrorCode { - if (instance) - return instance->CpPresetsCycleOperationWriteReq(device, operation); - return ErrorCode::OPERATION_NOT_POSSIBLE; - }); - - if (status != ErrorCode::NO_ERROR) - callbacks_->OnActivePresetSelectError(operation.addr_or_group, status); - } - - ErrorCode CpWritePresetNameOperationWriteReq(HasDevice& device, - HasCtpOp operation) { - LOG(INFO) << __func__ << ": addr: " << device.addr - << " operation: " << operation; - - if (!device.IsConnected()) return ErrorCode::OPERATION_NOT_POSSIBLE; - - if (!device.SupportsPresets()) return ErrorCode::OPERATION_NOT_SUPPORTED; - - if (!device.IsValidPreset(operation.index, true)) - return device.IsValidPreset(operation.index) - ? ErrorCode::SET_NAME_NOT_ALLOWED - : ErrorCode::INVALID_PRESET_INDEX; - - if (!device.SupportsOperation(operation.opcode)) - return ErrorCode::OPERATION_NOT_SUPPORTED; - - if (operation.name.value_or("").length() > - le_audio::has::HasPreset::kPresetNameLengthLimit) - return ErrorCode::INVALID_PRESET_NAME_LENGTH; - - auto context = HasGattOpContext(operation, operation.index); - - /* Journal update */ - device.has_journal_.Append(HasJournalRecord(operation, context)); - - /* Write to control point */ - EnqueueCtpOp(operation); - BtaGattQueue::WriteCharacteristic( - device.conn_id, device.cp_handle, operation.ToCharacteristicValue(), - GATT_WRITE, - [](uint16_t conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len, - const uint8_t* value, void* user_data) { - if (instance) - instance->OnHasPresetNameSetStatus(conn_id, status, user_data); - }, - context); - - return ErrorCode::NO_ERROR; - } - - void CpWritePresetNameOperation(HasCtpOp operation) { - LOG(INFO) << __func__ << ": operation: " << operation; - - auto status = ErrorCode::NO_ERROR; - - std::vector<RawAddress> addresses; - //address = {operation.GetDeviceAddr()}; - if (operation.IsGroupRequest()) { - /*auto csis_api = CsisClient::Get(); - if (csis_api != nullptr) { - addresses = csis_api->GetDeviceList(operation.GetGroupId());*/ - tBTA_CSIP_CSET cset_info; - memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); - cset_info = BTA_CsipGetCoordinatedSet(operation.GetDeviceAddr().address[5]); - LOG(INFO) << __func__ << ": set size: " << loghex(cset_info.size); - addresses = cset_info.set_members; - - /* Make this a coordinated operation */ - pending_group_operation_timeouts_.emplace( - operation.op_id, HasCtpGroupOpCoordinator(addresses, operation)); - //} - - } else { - addresses = {operation.GetDeviceAddr()}; - } - - status = ErrorCode::OPERATION_NOT_POSSIBLE; - - /* Perform the operation only when all the devices are available */ - if (!AreAllDevicesAvailable(addresses)) { - addresses.clear(); - } - - for (auto& addr : addresses) { - auto device = std::find_if(devices_.begin(), devices_.end(), - HasDevice::MatchAddress(addr)); - if (device != devices_.end()) { - status = CpWritePresetNameOperationWriteReq(*device, operation); - if (status != ErrorCode::NO_ERROR) { - LOG(ERROR) << __func__ - << ": Control point write error: " << (int)status; - break; - } - } - } - - if (status != ErrorCode::NO_ERROR) { - if (operation.IsGroupRequest()) - pending_group_operation_timeouts_.erase(operation.op_id); - - callbacks_->OnSetPresetNameError(operation.addr_or_group, operation.index, - status); - } - } - - bool shouldRequestSyncedOp(std::variant<RawAddress, int> addr_or_group_id, - PresetCtpOpcode opcode) { - /* Do not select locally synced ops when not performing group operations, - * You never know if the user will make another call for the other devices - * in this set even though the may support locally synced operations. - */ - if (std::holds_alternative<RawAddress>(addr_or_group_id)) return false; - - /*auto csis_api = CsisClient::Get(); - if (csis_api == nullptr) return false; - - auto addresses = csis_api->GetDeviceList(std::get<int>(addr_or_group_id));*/ - std::vector<RawAddress> addresses; - RawAddress address = std::get<RawAddress>(addr_or_group_id); - tBTA_CSIP_CSET cset_info; - memset(&cset_info, 0, sizeof(tBTA_CSIP_CSET)); - cset_info = BTA_CsipGetCoordinatedSet(address.address[5]); - LOG(INFO) << __func__ << "set size: " << loghex(cset_info.size); - addresses = cset_info.set_members; - if (addresses.empty()) return false; - - for (auto& addr : addresses) { - auto device = std::find_if(devices_.begin(), devices_.end(), - HasDevice::MatchAddress(addr)); - if (device != devices_.end()) { - if (device->SupportsOperation(opcode)) return true; - } - } - - return false; - } - - void SelectActivePreset(std::variant<RawAddress, int> addr_or_group_id, - uint8_t preset_index) override { - LOG(INFO) << __func__; - - auto opcode = shouldRequestSyncedOp(addr_or_group_id, - PresetCtpOpcode::SET_ACTIVE_PRESET_SYNC) - ? PresetCtpOpcode::SET_ACTIVE_PRESET_SYNC - : PresetCtpOpcode::SET_ACTIVE_PRESET; - - CpPresetIndexOperation(HasCtpOp(addr_or_group_id, opcode, preset_index)); - } - - void NextActivePreset( - std::variant<RawAddress, int> addr_or_group_id) override { - LOG(INFO) << __func__; - - auto opcode = shouldRequestSyncedOp(addr_or_group_id, - PresetCtpOpcode::SET_NEXT_PRESET_SYNC) - ? PresetCtpOpcode::SET_NEXT_PRESET_SYNC - : PresetCtpOpcode::SET_NEXT_PRESET; - - CpPresetsCycleOperation(HasCtpOp(addr_or_group_id, opcode)); - } - - void PreviousActivePreset( - std::variant<RawAddress, int> addr_or_group_id) override { - LOG(INFO) << __func__; - - auto opcode = shouldRequestSyncedOp(addr_or_group_id, - PresetCtpOpcode::SET_PREV_PRESET_SYNC) - ? PresetCtpOpcode::SET_PREV_PRESET_SYNC - : PresetCtpOpcode::SET_PREV_PRESET; - - CpPresetsCycleOperation(HasCtpOp(addr_or_group_id, opcode)); - } - - void GetPresetInfo(const RawAddress& address, uint8_t preset_index) override { - auto device = std::find_if(devices_.begin(), devices_.end(), - HasDevice::MatchAddress(address)); - if (device == devices_.end()) { - LOG(WARNING) << ": Device not connected to profile" - << address; - return; - } - - LOG(INFO) << __func__ << ": preset idx: " << +preset_index; - - /* Due to mandatory control point notifications or indications, preset - * details are always up to date. However we have to be able to do the - * READ_PRESET_BY_INDEX, to pass the test specification requirements. - */ - if (property_get_bool("persist.bluetooth.has.always_use_preset_cache", - true)) { - auto* preset = device->GetPreset(preset_index); - if (preset == nullptr) { - LOG(ERROR) << __func__ << ": Invalid preset request" - << address; - callbacks_->OnPresetInfoError(address, preset_index, - ErrorCode::INVALID_PRESET_INDEX); - return; - } - - callbacks_->OnPresetInfo(address, - PresetInfoReason::PRESET_INFO_REQUEST_RESPONSE, - {{.preset_index = preset_index, - .writable = preset->IsWritable(), - .available = preset->IsAvailable(), - .preset_name = preset->GetName()}}); - } else { - CpPresetIndexOperation( - HasCtpOp(address, PresetCtpOpcode::READ_PRESETS, preset_index)); - } - } - - void SetPresetName(std::variant<RawAddress, int> addr_or_group_id, - uint8_t preset_index, std::string name) override { - LOG(INFO) << __func__ << ": preset_idx: " << +preset_index - << ", name: " << name; - - CpWritePresetNameOperation(HasCtpOp(addr_or_group_id, - PresetCtpOpcode::WRITE_PRESET_NAME, - preset_index, 1 /* Don't care */, name)); - } - - void CleanUp() { - BTA_GATTC_AppDeregister(gatt_if_); - for (auto& device : devices_) { - if (device.conn_id != GATT_INVALID_CONN_ID) - BTA_GATTC_Close(device.conn_id); - DoDisconnectCleanUp(device); - } - - devices_.clear(); - pending_operations_.clear(); - } - - void Dump(int fd) const { - std::stringstream stream; - if (devices_.size()) { - stream << " {\"Known HAS devices\": ["; - for (const auto& device : devices_) { - stream << "\n {"; - device.Dump(stream); - stream << "\n },\n"; - } - stream << " ]}\n\n"; - } else { - stream << " \"No known HAS devices\"\n\n"; - } - dprintf(fd, "%s", stream.str().c_str()); - } - - void OnGroupOpCoordinatorTimeout(void* p) { - LOG(ERROR) << __func__ << ": Coordinated operation timeout: " - << " not all the devices notified their state change on time."; - - /* Clear pending group operations */ - pending_group_operation_timeouts_.clear(); - HasCtpGroupOpCoordinator::Cleanup(); - } - - private: - void WriteAllNeededCcc(const HasDevice& device) { - if (device.conn_id == GATT_INVALID_CONN_ID) { - //LOG_ERROR("Device %s is not connected", device.addr); - return; - } - - /* Write CCC values even remote should have it */ -// LOG_INFO("Subscribing for notification/indications"); - if (device.SupportsFeaturesNotification()) { - SubscribeForNotifications(device.conn_id, device.addr, - device.features_handle, - device.features_ccc_handle); - } - - if (device.SupportsPresets()) { - SubscribeForNotifications(device.conn_id, device.addr, device.cp_handle, - device.cp_ccc_handle, device.cp_ccc_val); - SubscribeForNotifications(device.conn_id, device.addr, - device.active_preset_handle, - device.active_preset_ccc_handle); - } - - if (property_get_bool("persist.bluetooth.has.always_use_preset_cache", - true) == false) { - CpReadAllPresetsOperation(HasCtpOp( - device.addr, PresetCtpOpcode::READ_PRESETS, - le_audio::has::kStartPresetIndex, le_audio::has::kMaxNumOfPresets)); - } - } - - void OnEncrypted(HasDevice& device) { - LOG(INFO) << __func__ << ": " << device.addr; - - if (device.isGattServiceValid()) { - device.is_connecting_actively = false; - NotifyHasDeviceValid(device); - callbacks_->OnPresetInfo(device.addr, PresetInfoReason::ALL_PRESET_INFO, - device.GetAllPresetInfo()); - callbacks_->OnActivePresetSelected(device.addr, - device.currently_active_preset); - WriteAllNeededCcc(device); - } else { - LOG(INFO) << __func__ << ": Gattc_Service Search request"; - BTA_GATTC_ServiceSearchRequest(device.conn_id, - &kUuidHearingAccessService); - } - } - - void NotifyHasDeviceValid(const HasDevice& device) { - LOG(INFO) << __func__ << ": addr:" << device.addr; - - std::vector<uint8_t> preset_indices; - preset_indices.reserve(device.has_presets.size()); - for (auto const& preset : device.has_presets) { - preset_indices.push_back(preset.GetIndex()); - } - - /* Notify that we are ready to go */ - callbacks_->OnConnectionState(ConnectionState::CONNECTED, device.addr); - } - - void MarkDeviceValidIfInInitialDiscovery(HasDevice& device) { - if (device.isGattServiceValid()) return; - - --device.gatt_svc_validation_steps; - - if (device.isGattServiceValid()) { - device.is_connecting_actively = false; - - std::vector<uint8_t> presets_bin; - if (device.SerializePresets(presets_bin)) { - btif_storage_add_leaudio_has_device(device.addr, presets_bin, - device.GetFeatures(), - device.currently_active_preset); - } - NotifyHasDeviceValid(device); - } - } - - void OnGattWriteCcc(uint16_t conn_id, tGATT_STATUS status, uint16_t handle, - void* user_data) { - LOG(INFO) << __func__ << ": handle=" << loghex(handle); - - auto device = GetDevice(conn_id); - if (!device) { - LOG(ERROR) << __func__ << ": unknown conn_id=" << loghex(conn_id); - BtaGattQueue::Clean(conn_id); - return; - } - - if (status == GATT_DATABASE_OUT_OF_SYNC) { - //LOG_INFO("Database out of sync for %s", device->addr); - ClearDeviceInformationAndStartSearch(device); - return; - } - - HasGattOpContext context(user_data); - bool enabling_ntf = context.context_flags & - HasGattOpContext::kContextFlagsEnableNotification; - - if (handle == device->features_ccc_handle) { - if (status == GATT_SUCCESS) - device->features_notifications_enabled = enabling_ntf; - - } else if ((handle == device->active_preset_ccc_handle) || - (handle == device->cp_ccc_handle)) { - /* Both of these CCC are mandatory */ - if (enabling_ntf && (status != GATT_SUCCESS)) { - LOG(ERROR) << __func__ - << ": Failed to register for notifications on handle=" - << loghex(handle); - BTA_GATTC_Close(conn_id); - return; - } - } - } - - void OnHasNotification(uint16_t conn_id, uint16_t handle, uint16_t len, - const uint8_t* value) { - auto device = GetDevice(conn_id); - if (!device) { - LOG(WARNING) << ": Skipping unknown device, conn_id=" << loghex(conn_id); - return; - } - - if (handle == device->features_handle) { - OnHasFeaturesValue(&(*device), GATT_SUCCESS, handle, len, value); - - } else if (handle == device->cp_handle) { - OnHasCtpValueNotification(&(*device), len, value); - - } else if (handle == device->active_preset_handle) { - OnHasActivePresetValue(&(*device), GATT_SUCCESS, handle, len, value); - } - } - - /* Gets the device from variant, possibly searching by conn_id */ - HasDevice* GetDevice( - std::variant<uint16_t, HasDevice*> conn_id_device_variant) { - HasDevice* device = nullptr; - - if (std::holds_alternative<HasDevice*>(conn_id_device_variant)) { - device = std::get<HasDevice*>(conn_id_device_variant); - } else { - auto it = std::find_if( - devices_.begin(), devices_.end(), - HasDevice::MatchConnId(std::get<uint16_t>(conn_id_device_variant))); - if (it != devices_.end()) device = &(*it); - } - - return device; - } - - void OnHasFeaturesValue( - std::variant<uint16_t, HasDevice*> conn_id_device_variant, - tGATT_STATUS status, uint16_t handle, uint16_t len, const uint8_t* value, - void* user_data = nullptr) { - LOG(INFO) << __func__; - - auto device = GetDevice(conn_id_device_variant); - if (!device) { - LOG(ERROR) << __func__ << ": Unknown device!"; - return; - } - - if (status != GATT_SUCCESS) { - if (status == GATT_DATABASE_OUT_OF_SYNC) { - //LOG_INFO("Database out of sync for %s", device->addr); - ClearDeviceInformationAndStartSearch(device); - } else { - //LOG_ERROR("Could not read characteristic at handle=0x%04x", handle); - BTA_GATTC_Close(device->conn_id); - } - return; - } - - if (len != 1) { - LOG(ERROR) << ": Invalid features value length=" << +len - << " at handle=" << loghex(handle); - BTA_GATTC_Close(device->conn_id); - return; - } - - /* Store features value */ - uint8_t features; - STREAM_TO_UINT8(features, value); - device->UpdateFeatures(features); - - if (device->isGattServiceValid()) { - btif_storage_set_leaudio_has_features(device->addr, features); - } - - /* Journal update */ - device->has_journal_.Append(HasJournalRecord(features, true)); - - /* When service is not yet validated, report the available device with - * features. - */ - if (!device->isGattServiceValid()) - callbacks_->OnDeviceAvailable(device->addr, device->GetFeatures()); - - /* Notify features */ - callbacks_->OnFeaturesUpdate(device->addr, device->GetFeatures()); - - MarkDeviceValidIfInInitialDiscovery(*device); - } - - /* Translates GATT statuses to application specific error codes */ - static ErrorCode GattStatus2SvcErrorCode(tGATT_STATUS status) { - switch (status) { - case 0x80: - /* Invalid Opcode */ - /* Unlikely to happen as we would not allow unsupported operations */ - return ErrorCode::OPERATION_NOT_SUPPORTED; - case 0x81: - /* Write Name Not Allowed */ - return ErrorCode::SET_NAME_NOT_ALLOWED; - case 0x82: - /* Synchronization Not Supported */ - return ErrorCode::OPERATION_NOT_SUPPORTED; - case 0x83: - /* Preset Operation Not Possible */ - return ErrorCode::OPERATION_NOT_POSSIBLE; - case 0x84: - /* Preset Name Too Long */ - return ErrorCode::INVALID_PRESET_NAME_LENGTH; - case 0xFE: - /* Procedure Already in Progress */ - return ErrorCode::PROCEDURE_ALREADY_IN_PROGRESS; - default: - return ErrorCode::OPERATION_NOT_POSSIBLE; - } - } - - void OnHasPresetReadResponseNotification(HasDevice& device) { - LOG(INFO) << __func__; - - while (device.ctp_notifications_.size() != 0) { - auto ntf = device.ctp_notifications_.front(); - /* Process only read response events */ - if (ntf.opcode != PresetCtpOpcode::READ_PRESET_RESPONSE) break; - - /* Update preset values */ - if (ntf.preset.has_value()) { - device.has_presets.erase(ntf.preset->GetIndex()); - device.has_presets.insert(ntf.preset.value()); - } - - /* We currently do READ_ALL_PRESETS only during the service validation. - * If service is already valid, this must be the READ_PRESET_BY_INDEX. - */ - if (device.isGattServiceValid()) { - auto info = device.GetPresetInfo(ntf.preset.value().GetIndex()); - if (info.has_value()) - callbacks_->OnPresetInfo( - device.addr, PresetInfoReason::PRESET_INFO_REQUEST_RESPONSE, - {{info.value()}}); - } - - /* Journal update */ - device.has_journal_.Append(HasJournalRecord(ntf)); - device.ctp_notifications_.pop_front(); - } - - auto in_svc_validation = !device.isGattServiceValid(); - MarkDeviceValidIfInInitialDiscovery(device); - - /* We currently do READ_ALL_PRESETS only during the service validation. - * ALL_PRESET_INFO will be sent only during this initial phase. - */ - if (in_svc_validation) { - callbacks_->OnPresetInfo(device.addr, PresetInfoReason::ALL_PRESET_INFO, - device.GetAllPresetInfo()); - - /* If this was the last validation step then send the currently active - * preset as well. - */ - if (device.isGattServiceValid()) - callbacks_->OnActivePresetSelected(device.addr, - device.currently_active_preset); - } - } - - void OnHasPresetGenericUpdate(HasDevice& device) { - LOG(ERROR) << __func__; - - std::vector<PresetInfo> updated_infos; - std::vector<PresetInfo> deleted_infos; - - /* Process the entire train of preset changes with generic updates */ - while (device.ctp_notifications_.size() != 0) { - auto nt = device.ctp_notifications_.front(); - - /* Break if not a generic update anymore */ - if (nt.opcode != PresetCtpOpcode::PRESET_CHANGED) break; - if (nt.change_id != PresetCtpChangeId::PRESET_GENERIC_UPDATE) break; - - if (nt.preset.has_value()) { - /* Erase old value if exist */ - device.has_presets.erase(nt.preset->GetIndex()); - - /* Erase in-between indices */ - if (nt.prev_index != 0) { - auto it = device.has_presets.begin(); - while (it != device.has_presets.end()) { - if ((it->GetIndex() > nt.prev_index) && - (it->GetIndex() < nt.preset->GetIndex())) { - auto info = device.GetPresetInfo(it->GetIndex()); - if (info.has_value()) deleted_infos.push_back(info.value()); - - it = device.has_presets.erase(it); - - } else { - ++it; - } - } - } - /* Update presets */ - device.has_presets.insert(*nt.preset); - - auto info = device.GetPresetInfo(nt.preset->GetIndex()); - if (info.has_value()) updated_infos.push_back(info.value()); - } - - /* Journal update */ - device.has_journal_.Append(HasJournalRecord(nt)); - device.ctp_notifications_.pop_front(); - } - - if (device.isGattServiceValid()) { - /* Update preset values in the storage */ - std::vector<uint8_t> presets_bin; - if (device.SerializePresets(presets_bin)) { - btif_storage_set_leaudio_has_presets(device.addr, presets_bin); - } - - /* Check for the matching coordinated group op. to use group callbacks */ - for (auto it = pending_group_operation_timeouts_.rbegin(); - it != pending_group_operation_timeouts_.rend(); ++it) { - auto& group_op_coordinator = it->second; - - /* Here we interested only in valid preset name changes */ - if (!((group_op_coordinator.operation.opcode == - PresetCtpOpcode::WRITE_PRESET_NAME) && - group_op_coordinator.operation.name.has_value())) - continue; - - /* Match preset update results with the triggering operation */ - auto renamed_preset_info = std::find_if( - updated_infos.begin(), updated_infos.end(), - [&group_op_coordinator](const auto& info) { - return (group_op_coordinator.operation.name.value() == - info.preset_name); - }); - if (renamed_preset_info == updated_infos.end()) continue; - - if (group_op_coordinator.SetCompleted(device.addr)) { - group_op_coordinator.preset_info_verification_list.push_back( - *renamed_preset_info); - - /* Call the proper group operation completion callback */ - if (group_op_coordinator.IsFullyCompleted()) { - callbacks_->OnPresetInfo( - group_op_coordinator.operation.GetGroupId(), - PresetInfoReason::PRESET_INFO_UPDATE, {*renamed_preset_info}); - pending_group_operation_timeouts_.erase(it->first); - } - - /* Erase it from the 'updated_infos' since later we'll be sending - * this as a group callback when the other device completes the - * coordinated group name change. - * - * WARNING: There might an issue with callbacks call reordering due to - * some of them being kept for group callbacks called later, when all - * the grouped devices complete the coordinated group rename - * operation. In most cases this should not be a major problem. - */ - updated_infos.erase(renamed_preset_info); - break; - } - } - - if (!updated_infos.empty()) - callbacks_->OnPresetInfo( - device.addr, PresetInfoReason::PRESET_INFO_UPDATE, updated_infos); - - if (!deleted_infos.empty()) - callbacks_->OnPresetInfo(device.addr, PresetInfoReason::PRESET_DELETED, - deleted_infos); - } - } - - void OnHasPresetAvailabilityChanged(HasDevice& device) { - LOG(INFO) << __func__; - - std::vector<PresetInfo> infos; - - while (device.ctp_notifications_.size() != 0) { - auto nt = device.ctp_notifications_.front(); - - /* Process only preset change notifications */ - if (nt.opcode != PresetCtpOpcode::PRESET_CHANGED) break; - - auto preset = device.has_presets.extract(nt.index).value(); - auto new_props = preset.GetProperties(); - - /* Process only the preset availability changes and then notify */ - if ((nt.change_id != PresetCtpChangeId::PRESET_AVAILABLE) && - (nt.change_id != PresetCtpChangeId::PRESET_UNAVAILABLE)) - break; - - /* Availability change */ - if (nt.change_id == PresetCtpChangeId::PRESET_AVAILABLE) { - new_props |= HasPreset::kPropertyAvailable; - } else { - new_props &= !HasPreset::kPropertyAvailable; - } - device.has_presets.insert( - HasPreset(preset.GetIndex(), new_props, preset.GetName())); - - auto info = device.GetPresetInfo(nt.index); - if (info.has_value()) infos.push_back(info.value()); - - /* Journal update */ - device.has_journal_.Append(HasJournalRecord(nt)); - device.ctp_notifications_.pop_front(); - } - - /* Update preset storage */ - if (device.isGattServiceValid()) { - std::vector<uint8_t> presets_bin; - if (device.SerializePresets(presets_bin)) { - btif_storage_set_leaudio_has_presets(device.addr, presets_bin); - } - } - - callbacks_->OnPresetInfo( - device.addr, PresetInfoReason::PRESET_AVAILABILITY_CHANGED, infos); - } - - void OnHasPresetDeleted(HasDevice& device) { - LOG(INFO) << __func__; - - std::vector<PresetInfo> infos; - bool is_deleted = false; - - while (device.ctp_notifications_.size() != 0) { - auto nt = device.ctp_notifications_.front(); - - /* Process only preset change notifications */ - if (nt.opcode != PresetCtpOpcode::PRESET_CHANGED) break; - - /* Process only the deletions and then notify */ - if (nt.change_id != PresetCtpChangeId::PRESET_DELETED) break; - - auto info = device.GetPresetInfo(nt.index); - if (info.has_value()) infos.push_back(info.value()); - - if (device.has_presets.count(nt.index)) { - is_deleted = true; - device.has_presets.erase(nt.index); - } - - /* Journal update */ - device.has_journal_.Append(HasJournalRecord(nt)); - device.ctp_notifications_.pop_front(); - } - - /* Update preset storage */ - if (device.isGattServiceValid()) { - std::vector<uint8_t> presets_bin; - if (device.SerializePresets(presets_bin)) { - btif_storage_set_leaudio_has_presets(device.addr, presets_bin); - } - } - - if (is_deleted) - callbacks_->OnPresetInfo(device.addr, PresetInfoReason::PRESET_DELETED, - infos); - } - - void ProcessCtpNotificationQueue(HasDevice& device) { - std::vector<PresetInfo> infos; - - while (device.ctp_notifications_.size() != 0) { - auto ntf = device.ctp_notifications_.front(); - LOG(INFO) << __func__ << ": ntf: " << ntf; - - if (ntf.opcode == PresetCtpOpcode::PRESET_CHANGED) { - switch (ntf.change_id) { - case PresetCtpChangeId::PRESET_GENERIC_UPDATE: - OnHasPresetGenericUpdate(device); - break; - case PresetCtpChangeId::PRESET_AVAILABLE: - OnHasPresetAvailabilityChanged(device); - break; - case PresetCtpChangeId::PRESET_UNAVAILABLE: - OnHasPresetAvailabilityChanged(device); - break; - case PresetCtpChangeId::PRESET_DELETED: - OnHasPresetDeleted(device); - break; - default: - LOG(ERROR) << __func__ << ": Invalid notification: " << ntf; - break; - } - - } else if (ntf.opcode == PresetCtpOpcode::READ_PRESET_RESPONSE) { - OnHasPresetReadResponseNotification(device); - - } else { - LOG(ERROR) << __func__ << ": Unsupported preset notification: " << ntf; - } - } - } - - void OnHasCtpValueNotification(HasDevice* device, uint16_t len, - const uint8_t* value) { - auto ntf_opt = HasCtpNtf::FromCharacteristicValue(len, value); - if (!ntf_opt.has_value()) { - LOG(ERROR) << __func__ - << ": Unhandled notification for device: " << *device; - BTA_GATTC_Close(device->conn_id); - return; - } - - auto ntf = ntf_opt.value(); - LOG(INFO) << __func__ << ":" <<ntf; - - device->ctp_notifications_.push_back(ntf); - if (ntf.is_last) ProcessCtpNotificationQueue(*device); - } - - void OnHasActivePresetValue( - std::variant<uint16_t, HasDevice*> conn_id_device_variant, - tGATT_STATUS status, uint16_t handle, uint16_t len, const uint8_t* value, - void* user_data = nullptr) { - LOG(INFO) << __func__; - - auto device = GetDevice(conn_id_device_variant); - if (!device) { - LOG(ERROR) << ": Skipping unknown device!"; - return; - } - - if (status != GATT_SUCCESS) { - if (status == GATT_DATABASE_OUT_OF_SYNC) { - //LOG_INFO("Database out of sync for %s", device->addr); - ClearDeviceInformationAndStartSearch(device); - } else { - //LOG_ERROR("Could not read characteristic at handle=0x%04x", handle); - BTA_GATTC_Close(device->conn_id); - } - } - - if (len != 1) { - LOG(ERROR) << ": Invalid preset value length=" << +len - << " at handle=" << loghex(handle); - BTA_GATTC_Close(device->conn_id); - return; - } - - /* Get the active preset value */ - auto* pp = value; - STREAM_TO_UINT8(device->currently_active_preset, pp); - - if (device->isGattServiceValid()) { - btif_storage_set_leaudio_has_active_preset( - device->addr, device->currently_active_preset); - } - - /* Journal update */ - device->has_journal_.Append( - HasJournalRecord(device->currently_active_preset, false)); - - /* If svc not marked valid, this might be the last validation step. */ - MarkDeviceValidIfInInitialDiscovery(*device); - - if (device->isGattServiceValid()) { - if (!pending_group_operation_timeouts_.empty()) { - for (auto it = pending_group_operation_timeouts_.rbegin(); - it != pending_group_operation_timeouts_.rend(); ++it) { - auto& group_op_coordinator = it->second; - - bool matches = false; - switch (group_op_coordinator.operation.opcode) { - case PresetCtpOpcode::SET_ACTIVE_PRESET: - [[fallthrough]]; - case PresetCtpOpcode::SET_NEXT_PRESET: - [[fallthrough]]; - case PresetCtpOpcode::SET_PREV_PRESET: - [[fallthrough]]; - case PresetCtpOpcode::SET_ACTIVE_PRESET_SYNC: - [[fallthrough]]; - case PresetCtpOpcode::SET_NEXT_PRESET_SYNC: - [[fallthrough]]; - case PresetCtpOpcode::SET_PREV_PRESET_SYNC: { - if (group_op_coordinator.SetCompleted(device->addr)) { - matches = true; - break; - } - } break; - default: - /* Ignore */ - break; - } - if (group_op_coordinator.IsFullyCompleted()) { - callbacks_->OnActivePresetSelected( - group_op_coordinator.operation.GetGroupId(), - device->currently_active_preset); - pending_group_operation_timeouts_.erase(it->first); - } - if (matches) break; - } - - } else { - callbacks_->OnActivePresetSelected(device->addr, - device->currently_active_preset); - } - } - } - - void DeregisterNotifications(HasDevice& device) { - /* Deregister from optional features notifications */ - if (device.features_ccc_handle != GAP_INVALID_HANDLE) { - BTA_GATTC_DeregisterForNotifications(gatt_if_, device.addr, - device.features_handle); - } - - /* Deregister from active presets notifications if presets exist */ - if (device.active_preset_ccc_handle != GAP_INVALID_HANDLE) { - BTA_GATTC_DeregisterForNotifications(gatt_if_, device.addr, - device.active_preset_handle); - } - - /* Deregister from control point notifications */ - if (device.cp_ccc_handle != GAP_INVALID_HANDLE) { - BTA_GATTC_DeregisterForNotifications(gatt_if_, device.addr, - device.cp_handle); - } - } - - /* Cleans up after the device disconnection */ - void DoDisconnectCleanUp(HasDevice& device, - bool invalidate_gatt_service = true) { - //LOG_DEBUG(": device=%s", device.addr); - - DeregisterNotifications(device); - - if (device.conn_id != GATT_INVALID_CONN_ID) { - BtaGattQueue::Clean(device.conn_id); - if (invalidate_gatt_service) device.gatt_svc_validation_steps = 0xFE; - } - - /* Clear pending operations */ - auto addr = device.addr; - pending_operations_.erase( - std::remove_if( - pending_operations_.begin(), pending_operations_.end(), - [&addr](auto& el) { - if (std::holds_alternative<RawAddress>(el.addr_or_group)) { - return std::get<RawAddress>(el.addr_or_group) == addr; - } - return false; - }), - pending_operations_.end()); - - device.ConnectionCleanUp(); - } - - /* These below are all GATT service discovery, validation, cache & storage */ - bool CacheAttributeHandles(const gatt::Service& service, HasDevice* device) { - LOG(INFO) << __func__ << ": device=" - << device->addr; - - for (const gatt::Characteristic& charac : service.characteristics) { - if (charac.uuid == kUuidActivePresetIndex) { - /* Find the mandatory CCC descriptor */ - uint16_t ccc_handle = - FindCccHandle(device->conn_id, charac.value_handle); - if (ccc_handle == GAP_INVALID_HANDLE) { - LOG(ERROR) << __func__ - << ": no HAS Active Preset CCC descriptor found!"; - return false; - } - device->active_preset_ccc_handle = ccc_handle; - device->active_preset_handle = charac.value_handle; - - } else if (charac.uuid == kUuidHearingAidPresetControlPoint) { - /* Find the mandatory CCC descriptor */ - uint16_t ccc_handle = - FindCccHandle(device->conn_id, charac.value_handle); - if (ccc_handle == GAP_INVALID_HANDLE) { - LOG(ERROR) << __func__ - << ": no HAS Control Point CCC descriptor found!"; - return false; - } - uint8_t ccc_val = 0; - if (charac.properties & GATT_CHAR_PROP_BIT_NOTIFY) - ccc_val |= GATT_CHAR_CLIENT_CONFIG_NOTIFICATION; - - if (charac.properties & GATT_CHAR_PROP_BIT_INDICATE) - ccc_val |= GATT_CHAR_CLIENT_CONFIG_INDICTION; - - if (ccc_val == 0) { - //LOG_ERROR("Invalid properties for the control point 0x%02x", charac.properties); - return false; - } - - device->cp_ccc_handle = ccc_handle; - device->cp_handle = charac.value_handle; - device->cp_ccc_val = ccc_val; - } else if (charac.uuid == kUuidHearingAidFeatures) { - /* Find the optional CCC descriptor */ - uint16_t ccc_handle = - FindCccHandle(device->conn_id, charac.value_handle); - device->features_ccc_handle = ccc_handle; - device->features_handle = charac.value_handle; - } - } - return true; - } - - bool LoadHasDetailsFromStorage(HasDevice* device) { - LOG(INFO) << __func__ << ": device=" - << device->addr; - - std::vector<uint8_t> presets_bin; - uint8_t active_preset; - - if (!btif_storage_get_leaudio_has_presets(device->addr, presets_bin, - active_preset)) - return false; - - if (!HasDevice::DeserializePresets(presets_bin.data(), presets_bin.size(), - *device)) - return false; - - VLOG(1) << ": Loading HAS service details from storage."; - - device->currently_active_preset = active_preset; - - /* Update features and refresh opcode support map */ - uint8_t val; - if (btif_storage_get_leaudio_has_features(device->addr, val)) - device->UpdateFeatures(val); - - /* With all the details loaded we can already mark it as valid */ - device->gatt_svc_validation_steps = 0; - device->is_connecting_actively = false; - - NotifyHasDeviceValid(*device); - callbacks_->OnPresetInfo(device->addr, PresetInfoReason::ALL_PRESET_INFO, - device->GetAllPresetInfo()); - callbacks_->OnActivePresetSelected(device->addr, - device->currently_active_preset); - if (device->conn_id == GATT_INVALID_CONN_ID) return true; - - /* Be mistrustful here: write CCC values even remote should have it */ -// LOG_INFO("Subscribing for notification/indications"); - WriteAllNeededCcc(*device); - - return true; - } - - bool StartInitialHasDetailsReadAndValidation(const gatt::Service& service, - HasDevice* device) { - // Validate service structure - if (device->features_handle == GAP_INVALID_HANDLE) { - /* Missing key characteristic */ - LOG(ERROR) << __func__ << ": Service has broken structure"; - return false; - } - - if (device->cp_handle != GAP_INVALID_HANDLE) { - if (device->active_preset_handle == GAP_INVALID_HANDLE) return false; - if (device->active_preset_ccc_handle == GAP_INVALID_HANDLE) return false; - } - - /* Number of reads or notifications required to validate the service */ - device->gatt_svc_validation_steps = 1 + (device->SupportsPresets() ? 2 : 0); - - /* Read the initial features */ - BtaGattQueue::ReadCharacteristic( - device->conn_id, device->features_handle, - [](uint16_t conn_id, tGATT_STATUS status, uint16_t handle, uint16_t len, - uint8_t* value, void* user_data) { - if (instance) - instance->OnHasFeaturesValue(conn_id, status, handle, len, value, - user_data); - }, - nullptr); - - /* Register for features notifications */ - if (device->SupportsFeaturesNotification()) { - SubscribeForNotifications(device->conn_id, device->addr, - device->features_handle, - device->features_ccc_handle); - } else { - LOG(WARNING) << __func__ - << ": server does not support features notification"; - } - - /* If Presets are supported we should read them all and subscribe for the - * mandatory active preset index notifications. - */ - if (device->SupportsPresets()) { - /* Subscribe for active preset notifications */ - SubscribeForNotifications(device->conn_id, device->addr, - device->active_preset_handle, - device->active_preset_ccc_handle); - - SubscribeForNotifications(device->conn_id, device->addr, - device->cp_handle, device->cp_ccc_handle, - device->cp_ccc_val); - - /* Get all the presets */ - CpReadAllPresetsOperation(HasCtpOp( - device->addr, PresetCtpOpcode::READ_PRESETS, - le_audio::has::kStartPresetIndex, le_audio::has::kMaxNumOfPresets)); - - /* Read the current active preset index */ - BtaGattQueue::ReadCharacteristic( - device->conn_id, device->active_preset_handle, - [](uint16_t conn_id, tGATT_STATUS status, uint16_t handle, - uint16_t len, uint8_t* value, void* user_data) { - if (instance) - instance->OnHasActivePresetValue(conn_id, status, handle, len, - value, user_data); - }, - nullptr); - } else { - LOG(WARNING) << __func__ - << ": server can only report HAS features, other " - "functionality is disabled"; - } - - return true; - } - - bool OnHasServiceFound(const gatt::Service& service, void* context) { - LOG(INFO) << __func__; - - auto* device = static_cast<HasDevice*>(context); - - /* Initially validate and store GATT service discovery data */ - if (!CacheAttributeHandles(service, device)) return false; - - /* If deatails are loaded from storage we are done here */ - if (LoadHasDetailsFromStorage(device)) return true; - - /* No storred details - read all the details and validate */ - return StartInitialHasDetailsReadAndValidation(service, device); - } - - /* These below are all generic event handlers calling in HAS specific code. */ - void GattcCallback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { - LOG(INFO) << __func__ << ": event = " << static_cast<int>(event); - uint8_t sec_flag = 0; - - switch (event) { - case BTA_GATTC_DEREG_EVT: - break; - - case BTA_GATTC_OPEN_EVT: - OnGattConnected(p_data->open); - break; - - case BTA_GATTC_CLOSE_EVT: - OnGattDisconnected(p_data->close); - break; - - case BTA_GATTC_SEARCH_CMPL_EVT: - OnGattServiceSearchComplete(p_data->search_cmpl); - break; - - case BTA_GATTC_NOTIF_EVT: - OnGattNotification(p_data->notify); - break; - - case BTA_GATTC_ENC_CMPL_CB_EVT: - OnLeEncryptionComplete(p_data->enc_cmpl.remote_bda, - BTM_GetSecurityFlagsByTransport(p_data->enc_cmpl.remote_bda, &sec_flag, BT_TRANSPORT_LE)); //BT_TRANSPORT_LE - break; - - case BTA_GATTC_SRVC_CHG_EVT: - OnGattServiceChangeEvent(p_data->remote_bda); - break; - - case BTA_GATTC_SRVC_DISC_DONE_EVT: - OnGattServiceDiscoveryDoneEvent(p_data->remote_bda); - break; - - default: - break; - } - } - - void OnGattConnected(const tBTA_GATTC_OPEN& evt) { - LOG(INFO) << __func__ << ": address=" - << evt.remote_bda - << ", conn_id=" << evt.conn_id; - - - auto device = std::find_if(devices_.begin(), devices_.end(), - HasDevice::MatchAddress(evt.remote_bda)); - uint8_t sec_flag = 0; - if (device == devices_.end()) { - LOG(INFO) << ": Skipping unknown device, address=" - << evt.remote_bda; - BTA_GATTC_Close(evt.conn_id); - return; - } - - if (evt.status != GATT_SUCCESS) { - if (!device->is_connecting_actively) { - // acceptlist connection failed, that's ok. - return; - } - - LOG(INFO) << "Failed to connect to server device"; - devices_.erase(device); - callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, - evt.remote_bda); - return; - } - - device->conn_id = evt.conn_id; - - tBTM_SEC_DEV_REC* p_dev_rec = btm_find_dev(device->addr); - if (p_dev_rec) { - if (p_dev_rec->sec_state == BTM_SEC_STATE_ENCRYPTING || - p_dev_rec->sec_state == BTM_SEC_STATE_AUTHENTICATING) { - /* if security collision happened, wait for encryption done - * (BTA_GATTC_ENC_CMPL_CB_EVT) - */ - return; - } - } - - /* verify bond */ - if (BTM_GetSecurityFlagsByTransport(device->addr, &sec_flag, BT_TRANSPORT_LE)) { - /* if link has been encrypted */ - if (device->isGattServiceValid()) { - instance->OnEncrypted(*device); - } else { - BTA_GATTC_ServiceSearchRequest(device->conn_id, - &kUuidHearingAccessService); - } - return; - } - - int result = BTM_SetEncryption( - evt.remote_bda, BT_TRANSPORT_LE, - [](const RawAddress* bd_addr, tBT_TRANSPORT transport, void* p_ref_data, - tBTM_STATUS status) { - if (instance) - instance->OnLeEncryptionComplete(*bd_addr, status == BTM_SUCCESS); - }, - nullptr, BTM_BLE_SEC_ENCRYPT); - - DLOG(INFO) << __func__ << ": Encryption request result: " << result; - } - - void OnGattDisconnected(const tBTA_GATTC_CLOSE& evt) { - auto device = std::find_if(devices_.begin(), devices_.end(), - HasDevice::MatchAddress(evt.remote_bda)); - if (device == devices_.end()) { - LOG(WARNING) << ": Skipping unknown device disconnect, conn_id=" - << loghex(evt.conn_id); - return; - } - LOG(INFO) << __func__ << ": device=" - << device->addr - << ": reason=" << loghex(static_cast<int>(evt.reason)); - - /* Don't notify disconnect state for background connection that failed */ - if (device->is_connecting_actively || device->isGattServiceValid()) - callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, - evt.remote_bda); - - auto peer_disconnected = (evt.reason == GATT_CONN_TIMEOUT) || - (evt.reason == GATT_CONN_TERMINATE_PEER_USER); - DoDisconnectCleanUp(*device, peer_disconnected ? false : true); - - /* Connect in background - is this ok? */ - if (peer_disconnected) - BTA_GATTC_Open(gatt_if_, device->addr, true, BT_TRANSPORT_LE, false); - } - - void OnGattServiceSearchComplete(const tBTA_GATTC_SEARCH_CMPL& evt) { - auto device = GetDevice(evt.conn_id); - if (!device) { - LOG(INFO) << ": Skipping unknown device, conn_id=" - << loghex(evt.conn_id); - return; - } - - LOG(INFO) << __func__; - - /* Ignore if our service data is valid (service discovery initiated by - * someone else?) - */ - if (!device->isGattServiceValid()) { - if (evt.status != GATT_SUCCESS) { - LOG(INFO) << __func__ << ": Service discovery failed"; - BTA_GATTC_Close(device->conn_id); - return; - } - - const std::vector<gatt::Service>* all_services = - BTA_GATTC_GetServices(device->conn_id); - - auto service = - std::find_if(all_services->begin(), all_services->end(), - [](const gatt::Service& svc) { - return svc.uuid == kUuidHearingAccessService; - }); - if (service == all_services->end()) { - LOG(INFO) << ": No service found"; - BTA_GATTC_Close(device->conn_id); - return; - } - - /* Call the service specific verifier callback */ - if (!instance->OnHasServiceFound(*service, &(*device))) { - LOG(INFO) << ": Not a valid service!"; - BTA_GATTC_Close(device->conn_id); - return; - } - } - } - - void OnGattNotification(const tBTA_GATTC_NOTIFY& evt) { - /* Reject invalid lengths */ - if (evt.len > GATT_MAX_ATTR_LEN) { - LOG(ERROR) << __func__ << ": rejected BTA_GATTC_NOTIF_EVT. is_notify = " - << evt.is_notify << ", len=" << static_cast<int>(evt.len); - } - if (!evt.is_notify) BTA_GATTC_SendIndConfirm(evt.conn_id, evt.handle, evt.trans_id); - - OnHasNotification(evt.conn_id, evt.handle, evt.len, evt.value); - } - - void OnLeEncryptionComplete(const RawAddress& address, bool success) { - LOG(INFO) << __func__ << ": " <<address; - - auto device = std::find_if(devices_.begin(), devices_.end(), - HasDevice::MatchAddress(address)); - if (device == devices_.end()) { - LOG(WARNING) << ": Skipping unknown device" - << address; - return; - } - - if (!success) { - LOG(ERROR) << ": Encryption failed for device " - << address; - - BTA_GATTC_Close(device->conn_id); - return; - } - - if (device->isGattServiceValid()) { - instance->OnEncrypted(*device); - } else { - BTA_GATTC_ServiceSearchRequest(device->conn_id, - &kUuidHearingAccessService); - } - } - - void ClearDeviceInformationAndStartSearch(HasDevice* device) { - if (!device) { -// LOG_ERROR("Device is null"); - return; - } - -// LOG_INFO("%s", device->addr); - - if (!device->isGattServiceValid()) { - //LOG_INFO("Service already invalidated"); - return; - } - - /* Invalidate service discovery results */ - DeregisterNotifications(*device); - BtaGattQueue::Clean(device->conn_id); - device->ClearSvcData(); - btif_storage_remove_leaudio_has(device->addr); - BTA_GATTC_ServiceSearchRequest(device->conn_id, &kUuidHearingAccessService); - } - - void OnGattServiceChangeEvent(const RawAddress& address) { - auto device = std::find_if(devices_.begin(), devices_.end(), - HasDevice::MatchAddress(address)); - if (device == devices_.end()) { - LOG(WARNING) << ": Skipping unknown device" << address; - return; - } - //LOG_INFO("%s", address); - ClearDeviceInformationAndStartSearch(&(*device)); - } - - void OnGattServiceDiscoveryDoneEvent(const RawAddress& address) { - auto device = std::find_if(devices_.begin(), devices_.end(), - HasDevice::MatchAddress(address)); - if (device == devices_.end()) { - LOG(WARNING) << ": Skipping unknown device" - << address; - return; - } - - LOG(INFO) << __func__ << ": address=" - << address; - - if (!device->isGattServiceValid()) - BTA_GATTC_ServiceSearchRequest(device->conn_id, - &kUuidHearingAccessService); - } - - static uint16_t FindCccHandle(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 GAP_INVALID_HANDLE; - } - - for (const gatt::Descriptor& desc : p_char->descriptors) { - if (desc.uuid == Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)) - return desc.handle; - } - - return GAP_INVALID_HANDLE; - } - - void SubscribeForNotifications( - uint16_t conn_id, const RawAddress& address, uint16_t value_handle, - uint16_t ccc_handle, - uint16_t ccc_val = GATT_CHAR_CLIENT_CONFIG_NOTIFICATION) { - if (value_handle != GAP_INVALID_HANDLE) { - tGATT_STATUS register_status = - BTA_GATTC_RegisterForNotifications(gatt_if_, address, value_handle); - LOG(INFO) << __func__ << ": BTA_GATTC_RegisterForNotifications, status=" - << loghex(+register_status) - << " value=" << loghex(value_handle) - << " ccc=" << loghex(ccc_handle); - - if (register_status != GATT_SUCCESS) return; - } - - std::vector<uint8_t> value(2); - uint8_t* value_ptr = value.data(); - UINT16_TO_STREAM(value_ptr, ccc_val); - BtaGattQueue::WriteDescriptor( - conn_id, ccc_handle, std::move(value), GATT_WRITE, - [](uint16_t conn_id, tGATT_STATUS status, uint16_t value_handle, - uint16_t len, const uint8_t* value, void* data) { - if (instance) - instance->OnGattWriteCcc(conn_id, status, value_handle, data); - }, - HasGattOpContext(HasGattOpContext::kContextFlagsEnableNotification)); - } - - uint8_t gatt_if_; - bluetooth::has::HasClientCallbacks* callbacks_; - std::list<HasDevice> devices_; - std::list<HasCtpOp> pending_operations_; - - typedef std::map<decltype(HasCtpOp::op_id), HasCtpGroupOpCoordinator> - has_operation_timeouts_t; - has_operation_timeouts_t pending_group_operation_timeouts_; -}; - -} // namespace - -alarm_t* HasCtpGroupOpCoordinator::operation_timeout_timer = nullptr; -size_t HasCtpGroupOpCoordinator::ref_cnt = 0u; -alarm_callback_t HasCtpGroupOpCoordinator::cb = [](void*) {}; - -void HasClient::Initialize(bluetooth::has::HasClientCallbacks* callbacks, - base::Closure initCb) { - if (instance) { - LOG(ERROR) << ": Already initialized!"; - return; - } - - HasCtpGroupOpCoordinator::Initialize([](void* p) { - if (instance) instance->OnGroupOpCoordinatorTimeout(p); - }); - instance = new HasClientImpl(callbacks, initCb); -} - -bool HasClient::IsHasClientRunning() { return instance; } - -HasClient* HasClient::Get(void) { - CHECK(instance); - return instance; -}; - -void HasClient::AddFromStorage(const RawAddress& addr, uint8_t features, - uint16_t is_acceptlisted) { - if (!instance) { - LOG(ERROR) << ": Not initialized yet"; - } - - instance->AddFromStorage(addr, features, is_acceptlisted); -}; - -void HasClient::CleanUp() { - HasClientImpl* ptr = instance; - instance = nullptr; - - if (ptr) { - ptr->CleanUp(); - delete ptr; - } - - HasCtpGroupOpCoordinator::Cleanup(); -}; - -void HasClient::DebugDump(int fd) { - dprintf(fd, ": Hearing Access Service Client:\n"); - if (instance) - instance->Dump(fd); - else - dprintf(fd, " no instance\n\n"); -} diff --git a/bta/has/has_client_test.cc b/bta/has/has_client_test.cc deleted file mode 100644 index a1c77bca7..000000000 --- a/bta/has/has_client_test.cc +++ /dev/null @@ -1,3202 +0,0 @@ -/* - * Copyright 2021 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <base/bind.h> -#include <base/bind_helpers.h> -#include <base/strings/string_number_conversions.h> -#include <gmock/gmock.h> -#include <gtest/gtest.h> -#include <osi/include/alarm.h> -#include <sys/socket.h> - -#include <variant> - -#include "bta/le_audio/le_audio_types.h" -#include "bta_csis_api.h" -#include "bta_gatt_api_mock.h" -#include "bta_gatt_queue_mock.h" -#include "bta_has_api.h" -#include "btif_storage_mock.h" -#include "btm_api_mock.h" -#include "gatt/database_builder.h" -#include "hardware/bt_gatt_types.h" -#include "has_types.h" -#include "mock_controller.h" -#include "mock_csis_client.h" - -bool gatt_profile_get_eatt_support(const RawAddress& addr) { return true; } -void osi_property_set_bool(const char* key, bool value); - -std::map<std::string, int> mock_function_count_map; - -namespace bluetooth { -namespace has { -namespace internal { -namespace { - -using base::HexEncode; - -using ::bluetooth::csis::CsisClient; -using ::bluetooth::has::ConnectionState; -using ::bluetooth::has::ErrorCode; -using ::bluetooth::has::HasClientCallbacks; -using ::bluetooth::has::PresetInfo; - -using ::le_audio::has::HasClient; -using ::le_audio::has::HasCtpGroupOpCoordinator; -using ::le_audio::has::HasCtpOp; -using ::le_audio::has::HasDevice; -using ::le_audio::has::HasPreset; - -using ::testing::_; -using ::testing::AnyNumber; -using ::testing::DoAll; -using ::testing::DoDefault; -using ::testing::Invoke; -using ::testing::Mock; -using ::testing::NotNull; -using ::testing::Return; -using ::testing::SaveArg; -using ::testing::Sequence; -using ::testing::SetArgPointee; -using ::testing::WithArg; - -// Disables most likely false-positives from base::SplitString() -// extern "C" const char* __asan_default_options() { -// return "detect_container_overflow=0"; -// } - -RawAddress GetTestAddress(int index) { - CHECK_LT(index, UINT8_MAX); - RawAddress result = { - {0xC0, 0xDE, 0xC0, 0xDE, 0x00, static_cast<uint8_t>(index)}}; - return result; -} - -static uint16_t GetTestConnId(const RawAddress& address) { - return address.address[RawAddress::kLength - 1]; -} - -class MockHasCallbacks : public HasClientCallbacks { - public: - MockHasCallbacks() = default; - MockHasCallbacks(const MockHasCallbacks&) = delete; - MockHasCallbacks& operator=(const MockHasCallbacks&) = delete; - - ~MockHasCallbacks() override = default; - - MOCK_METHOD((void), OnConnectionState, - (ConnectionState state, const RawAddress& address), (override)); - MOCK_METHOD((void), OnDeviceAvailable, - (const RawAddress& address, uint8_t features), (override)); - MOCK_METHOD((void), OnFeaturesUpdate, - (const RawAddress& address, uint8_t features), (override)); - MOCK_METHOD((void), OnActivePresetSelected, - ((std::variant<RawAddress, int> addr_or_group_id), - uint8_t preset_index), - (override)); - MOCK_METHOD((void), OnActivePresetSelectError, - ((std::variant<RawAddress, int> addr_or_group_id), - ErrorCode result), - (override)); - MOCK_METHOD((void), OnPresetInfo, - ((std::variant<RawAddress, int> addr_or_group_id), - PresetInfoReason change_id, - std::vector<PresetInfo> preset_change_records), - (override)); - MOCK_METHOD((void), OnPresetInfoError, - ((std::variant<RawAddress, int> addr_or_group_id), - uint8_t preset_index, ErrorCode error_code), - (override)); - MOCK_METHOD((void), OnSetPresetNameError, - ((std::variant<RawAddress, int> addr_or_group_id), - uint8_t preset_index, ErrorCode error_code), - (override)); -}; - -class HasClientTestBase : public ::testing::Test { - protected: - std::map<uint16_t, uint8_t> current_peer_active_preset_idx_; - std::map<uint16_t, uint8_t> current_peer_features_val_; - std::map<uint16_t, std::set<HasPreset, HasPreset::ComparatorDesc>> - current_peer_presets_; - - struct HasDbBuilder { - bool has; - - static constexpr uint16_t kGapSvcStartHdl = 0x0001; - static constexpr uint16_t kGapDeviceNameValHdl = 0x0003; - static constexpr uint16_t kGapSvcEndHdl = kGapDeviceNameValHdl; - - static constexpr uint16_t kSvcStartHdl = 0x0010; - static constexpr uint16_t kFeaturesValHdl = 0x0012; - static constexpr uint16_t kPresetsCtpValHdl = 0x0015; - static constexpr uint16_t kActivePresetIndexValHdl = 0x0018; - static constexpr uint16_t kSvcEndHdl = 0x001E; - - static constexpr uint16_t kGattSvcStartHdl = 0x0090; - static constexpr uint16_t kGattSvcChangedValHdl = 0x0092; - static constexpr uint16_t kGattSvcEndHdl = kGattSvcChangedValHdl + 1; - - bool features; - bool features_ntf; - - bool preset_cp; - bool preset_cp_ntf; - bool preset_cp_ind; - - bool active_preset_idx; - bool active_preset_idx_ntf; - - const gatt::Database Build() { - gatt::DatabaseBuilder bob; - - /* Generic Access Service */ - bob.AddService(kGapSvcStartHdl, kGapSvcEndHdl, Uuid::From16Bit(0x1800), - true); - /* Device Name Char. */ - bob.AddCharacteristic(kGapDeviceNameValHdl - 1, kGapDeviceNameValHdl, - Uuid::From16Bit(0x2a00), GATT_CHAR_PROP_BIT_READ); - - /* 0x0004-0x000f left empty on purpose */ - if (has) { - bob.AddService(kSvcStartHdl, kSvcEndHdl, - ::le_audio::has::kUuidHearingAccessService, true); - - if (features) { - bob.AddCharacteristic( - kFeaturesValHdl - 1, kFeaturesValHdl, - ::le_audio::has::kUuidHearingAidFeatures, - GATT_CHAR_PROP_BIT_READ | - (features_ntf ? GATT_CHAR_PROP_BIT_NOTIFY : 0)); - - if (features_ntf) { - bob.AddDescriptor(kFeaturesValHdl + 1, - Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); - } - } - - if (preset_cp) { - bob.AddCharacteristic( - kPresetsCtpValHdl - 1, kPresetsCtpValHdl, - ::le_audio::has::kUuidHearingAidPresetControlPoint, - GATT_CHAR_PROP_BIT_WRITE | - (preset_cp_ntf ? GATT_CHAR_PROP_BIT_NOTIFY : 0) | - (preset_cp_ind ? GATT_CHAR_PROP_BIT_INDICATE : 0)); - - if (preset_cp_ntf || preset_cp_ind) { - bob.AddDescriptor(kPresetsCtpValHdl + 1, - Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); - } - } - - if (active_preset_idx) { - bob.AddCharacteristic( - kActivePresetIndexValHdl - 1, kActivePresetIndexValHdl, - ::le_audio::has::kUuidActivePresetIndex, - GATT_CHAR_PROP_BIT_READ | - (active_preset_idx_ntf ? GATT_CHAR_PROP_BIT_NOTIFY : 0)); - - if (active_preset_idx_ntf) - bob.AddDescriptor(kActivePresetIndexValHdl + 1, - Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); - } - } - - /* GATTS */ - /* 0x001F-0x0090 left empty on purpose */ - bob.AddService(kGattSvcStartHdl, kGattSvcEndHdl, - Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER), true); - bob.AddCharacteristic(kGattSvcChangedValHdl - 1, kGattSvcChangedValHdl, - Uuid::From16Bit(GATT_UUID_GATT_SRV_CHGD), - GATT_CHAR_PROP_BIT_NOTIFY); - bob.AddDescriptor(kGattSvcChangedValHdl + 1, - Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); - return bob.Build(); - }; - }; - - const gatt::Characteristic* FindCharacteristicByValueHandle( - const gatt::Service* svc, uint16_t handle) { - if (svc == nullptr) return nullptr; - - auto it = - std::find_if(svc->characteristics.cbegin(), svc->characteristics.cend(), - [handle](const auto& characteristic) { - return characteristic.value_handle == handle; - }); - return (it != svc->characteristics.cend()) ? &(*it) : nullptr; - } - - void set_sample_database( - const RawAddress& address, HasDbBuilder& builder, - uint8_t features_val = 0x0, - std::optional<std::set<HasPreset, HasPreset::ComparatorDesc>> presets_op = - std::nullopt) { - uint16_t conn_id = GetTestConnId(address); - - /* For some test cases these defaults are enough */ - if (!presets_op) - presets_op = {{ - HasPreset(6, HasPreset::kPropertyAvailable, "Universal"), - HasPreset( - 55, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "YourPreset55"), - }}; - auto& presets = presets_op.value(); - auto const active_preset = presets.begin(); - - services_map[conn_id] = builder.Build().Services(); - current_peer_features_val_.insert_or_assign(conn_id, features_val); - current_peer_active_preset_idx_.insert_or_assign(conn_id, - active_preset->GetIndex()); - current_peer_presets_.insert_or_assign(conn_id, std::move(presets)); - - ON_CALL(gatt_queue, ReadCharacteristic(conn_id, _, _, _)) - .WillByDefault(Invoke([this](uint16_t conn_id, uint16_t handle, - GATT_READ_OP_CB cb, void* cb_data) { - auto* svc = gatt::FindService(services_map[conn_id], handle); - if (svc == nullptr) return; - - std::vector<uint8_t> value; - tGATT_STATUS status = GATT_SUCCESS; - - switch (handle) { - case HasDbBuilder::kGapDeviceNameValHdl: - value.resize(20); - break; - case HasDbBuilder::kFeaturesValHdl: - value.resize(1); - value[0] = current_peer_features_val_.at(conn_id); - break; - case HasDbBuilder::kActivePresetIndexValHdl: - value.resize(1); - value[0] = current_peer_active_preset_idx_.at(conn_id); - break; - case HasDbBuilder::kPresetsCtpValHdl: - /* passthrough */ - default: - status = GATT_READ_NOT_PERMIT; - break; - } - - if (cb) - cb(conn_id, status, handle, value.size(), value.data(), cb_data); - })); - - /* Default action for the Control Point operation writes */ - ON_CALL(gatt_queue, - WriteCharacteristic(conn_id, HasDbBuilder::kPresetsCtpValHdl, _, - GATT_WRITE, _, _)) - .WillByDefault(Invoke([this, address](uint16_t conn_id, uint16_t handle, - std::vector<uint8_t> value, - tGATT_WRITE_TYPE write_type, - GATT_WRITE_OP_CB cb, - void* cb_data) { - auto pp = value.data(); - auto len = value.size(); - uint8_t op, index, num_of_indices; - - const bool indicate = false; - - if (len < 1) { - if (cb) - cb(conn_id, GATT_INVALID_ATTR_LEN, handle, value.size(), - value.data(), cb_data); - return; - } - - STREAM_TO_UINT8(op, pp) - --len; - if (op > - static_cast< - std::underlying_type_t<::le_audio::has::PresetCtpOpcode>>( - ::le_audio::has::PresetCtpOpcode::OP_MAX_)) { - /* Invalid Opcode */ - if (cb) - cb(conn_id, (tGATT_STATUS)0x80, handle, value.size(), - value.data(), cb_data); - return; - } - - switch (static_cast<::le_audio::has::PresetCtpOpcode>(op)) { - case ::le_audio::has::PresetCtpOpcode::READ_PRESETS: - if (len < 2) { - if (cb) - cb(conn_id, GATT_INVALID_ATTR_LEN, handle, value.size(), - value.data(), cb_data); - - } else { - STREAM_TO_UINT8(index, pp); - STREAM_TO_UINT8(num_of_indices, pp); - len -= 2; - ASSERT_EQ(0u, len); - - InjectNotifyReadPresetsResponse(conn_id, address, handle, value, - indicate, index, num_of_indices, - cb, cb_data); - } - break; - - case ::le_audio::has::PresetCtpOpcode::SET_ACTIVE_PRESET: { - if (len < 1) { - if (cb) - cb(conn_id, GATT_INVALID_ATTR_LEN, handle, value.size(), - value.data(), cb_data); - break; - } - STREAM_TO_UINT8(index, pp); - --len; - ASSERT_EQ(0u, len); - - auto presets = current_peer_presets_.at(conn_id); - if (presets.count(index)) { - current_peer_active_preset_idx_.insert_or_assign(conn_id, - index); - if (cb) - cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(), - cb_data); - InjectActivePresetNotification(conn_id, address, handle, value, - index, cb, cb_data); - } else { - /* Preset Operation Not Possible */ - if (cb) - cb(conn_id, (tGATT_STATUS)0x83, handle, value.size(), - value.data(), cb_data); - } - } break; - - case ::le_audio::has::PresetCtpOpcode::SET_ACTIVE_PRESET_SYNC: { - auto features = current_peer_features_val_.at(conn_id); - if ((features & ::bluetooth::has:: - kFeatureBitPresetSynchronizationSupported) == - 0) { - /* Synchronization Not Supported */ - if (cb) - cb(conn_id, (tGATT_STATUS)0x82, handle, value.size(), - value.data(), cb_data); - break; - } - - if (len < 1) { - if (cb) - cb(conn_id, GATT_INVALID_ATTR_LEN, handle, value.size(), - value.data(), cb_data); - break; - } - STREAM_TO_UINT8(index, pp); - --len; - ASSERT_EQ(0u, len); - - auto csis_api = CsisClient::Get(); - int group_id = bluetooth::groups::kGroupUnknown; - if (csis_api != nullptr) { - group_id = csis_api->GetGroupId( - address, ::le_audio::uuid::kCapServiceUuid); - } - - if (group_id != bluetooth::groups::kGroupUnknown) { - if (cb) - cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(), - cb_data); - /* Send notification from all grouped devices */ - auto addresses = csis_api->GetDeviceList(group_id); - for (auto& addr : addresses) { - auto conn = GetTestConnId(addr); - InjectActivePresetNotification(conn, addr, handle, value, - index, cb, cb_data); - } - } else { - /* Preset Operation Not Possible */ - if (cb) - cb(conn_id, (tGATT_STATUS)0x83, handle, value.size(), - value.data(), cb_data); - } - } break; - - case ::le_audio::has::PresetCtpOpcode::SET_NEXT_PRESET: { - ASSERT_EQ(0u, len); - ASSERT_NE(0u, current_peer_active_preset_idx_.count(conn_id)); - ASSERT_NE(0u, current_peer_presets_.count(conn_id)); - - auto current_preset = current_peer_active_preset_idx_.at(conn_id); - auto presets = current_peer_presets_.at(conn_id); - auto current = presets.find(current_preset); - if (current != presets.end()) { - ++current; - if (current == presets.end()) current = presets.begin(); - - current_peer_active_preset_idx_.insert_or_assign( - conn_id, current->GetIndex()); - InjectActivePresetNotification(conn_id, address, handle, value, - current->GetIndex(), cb, - cb_data); - - } else { - /* Preset Operation Not Possible */ - if (cb) - cb(conn_id, (tGATT_STATUS)0x83, handle, value.size(), - value.data(), cb_data); - } - } break; - - case ::le_audio::has::PresetCtpOpcode::SET_PREV_PRESET: { - ASSERT_EQ(0u, len); - ASSERT_NE(0u, current_peer_active_preset_idx_.count(conn_id)); - ASSERT_NE(0u, current_peer_presets_.count(conn_id)); - - auto current_preset = current_peer_active_preset_idx_.at(conn_id); - auto presets = current_peer_presets_.at(conn_id); - auto rit = presets.rbegin(); - while (rit != presets.rend()) { - if (rit->GetIndex() == current_preset) { - rit++; - /* Wrap around */ - if (rit == presets.rend()) { - rit = presets.rbegin(); - } - break; - } - rit++; - } - - if (rit != presets.rend()) { - if (cb) - cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(), - cb_data); - current_peer_active_preset_idx_.insert_or_assign( - conn_id, rit->GetIndex()); - InjectActivePresetNotification(conn_id, address, handle, value, - rit->GetIndex(), cb, cb_data); - } else { - /* Preset Operation Not Possible */ - if (cb) - cb(conn_id, (tGATT_STATUS)0x83, handle, value.size(), - value.data(), cb_data); - } - } break; - - case ::le_audio::has::PresetCtpOpcode::SET_NEXT_PRESET_SYNC: { - ASSERT_EQ(0u, len); - auto features = current_peer_features_val_.at(conn_id); - if ((features & ::bluetooth::has:: - kFeatureBitPresetSynchronizationSupported) == - 0) { - /* Synchronization Not Supported */ - if (cb) - cb(conn_id, (tGATT_STATUS)0x82, handle, value.size(), - value.data(), cb_data); - break; - } - - auto current_preset = current_peer_active_preset_idx_.at(conn_id); - auto presets = current_peer_presets_.at(conn_id); - auto rit = presets.begin(); - while (rit != presets.end()) { - if (rit->GetIndex() == current_preset) { - rit++; - /* Wrap around */ - if (rit == presets.end()) { - rit = presets.begin(); - } - break; - } - rit++; - } - - if (rit != presets.end()) { - auto synced_group = mock_csis_client_module_.GetGroupId( - GetTestAddress(conn_id), ::le_audio::uuid::kCapServiceUuid); - auto addresses = - mock_csis_client_module_.GetDeviceList(synced_group); - - // Emulate locally synced op. - notify from all of the devices - for (auto addr : addresses) { - auto cid = GetTestConnId(addr); - if ((cid == conn_id) && (cb != nullptr)) - cb(cid, GATT_SUCCESS, handle, value.size(), value.data(), - cb_data); - - current_peer_active_preset_idx_.insert_or_assign( - conn_id, rit->GetIndex()); - InjectActivePresetNotification(cid, addr, handle, value, - rit->GetIndex(), cb, cb_data); - } - } else { - /* Preset Operation Not Possible */ - if (cb) - cb(conn_id, (tGATT_STATUS)0x83, handle, value.size(), - value.data(), cb_data); - } - } break; - - case ::le_audio::has::PresetCtpOpcode::SET_PREV_PRESET_SYNC: { - ASSERT_EQ(0u, len); - auto features = current_peer_features_val_.at(conn_id); - if ((features & ::bluetooth::has:: - kFeatureBitPresetSynchronizationSupported) == - 0) { - /* Synchronization Not Supported */ - if (cb) - cb(conn_id, (tGATT_STATUS)0x82, handle, value.size(), - value.data(), cb_data); - break; - } - - auto current_preset = current_peer_active_preset_idx_.at(conn_id); - auto presets = current_peer_presets_.at(conn_id); - auto rit = presets.rbegin(); - while (rit != presets.rend()) { - if (rit->GetIndex() == current_preset) { - rit++; - /* Wrap around */ - if (rit == presets.rend()) { - rit = presets.rbegin(); - } - break; - } - rit++; - } - - if (rit != presets.rend()) { - auto synced_group = mock_csis_client_module_.GetGroupId( - GetTestAddress(conn_id), ::le_audio::uuid::kCapServiceUuid); - auto addresses = - mock_csis_client_module_.GetDeviceList(synced_group); - - // Emulate locally synced op. - notify from all of the devices - for (auto addr : addresses) { - auto cid = GetTestConnId(addr); - if ((cid == conn_id) && (cb != nullptr)) - cb(cid, GATT_SUCCESS, handle, value.size(), value.data(), - cb_data); - - current_peer_active_preset_idx_.insert_or_assign( - conn_id, rit->GetIndex()); - InjectActivePresetNotification(cid, addr, handle, value, - rit->GetIndex(), cb, cb_data); - } - } else { - /* Preset Operation Not Possible */ - if (cb) - cb(conn_id, (tGATT_STATUS)0x83, handle, value.size(), - value.data(), cb_data); - } - } break; - - case ::le_audio::has::PresetCtpOpcode::WRITE_PRESET_NAME: { - STREAM_TO_UINT8(index, pp); - --len; - auto name = std::string(pp, pp + len); - len = 0; - - ASSERT_NE(0u, current_peer_presets_.count(conn_id)); - auto presets = current_peer_presets_.at(conn_id); - auto rit = presets.rbegin(); - auto current = rit; - while (rit != presets.rend()) { - if (rit->GetIndex() == index) { - current = rit; - rit++; - break; - } - rit++; - } - - auto prev_index = (rit == presets.rend()) ? 0 : rit->GetIndex(); - - ASSERT_NE(current, presets.rend()); - if (cb) - cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(), - cb_data); - - auto new_preset = HasPreset(current->GetIndex(), - current->GetProperties(), name); - presets.erase(current->GetIndex()); - presets.insert(new_preset); - - InjectPresetChanged( - conn_id, address, indicate, new_preset, prev_index, - ::le_audio::has::PresetCtpChangeId::PRESET_GENERIC_UPDATE, - true); - } break; - - default: - if (cb) - cb(conn_id, GATT_INVALID_HANDLE, handle, value.size(), - value.data(), cb_data); - break; - } - })); - } - - void SetUp(void) override { - mock_function_count_map.clear(); - controller::SetMockControllerInterface(&controller_interface_); - bluetooth::manager::SetMockBtmInterface(&btm_interface); - bluetooth::storage::SetMockBtifStorageInterface(&btif_storage_interface_); - gatt::SetMockBtaGattInterface(&gatt_interface); - gatt::SetMockBtaGattQueue(&gatt_queue); - callbacks.reset(new MockHasCallbacks()); - - encryption_result = true; - - MockCsisClient::SetMockInstanceForTesting(&mock_csis_client_module_); - ON_CALL(mock_csis_client_module_, Get()) - .WillByDefault(Return(&mock_csis_client_module_)); - ON_CALL(mock_csis_client_module_, IsCsisClientRunning()) - .WillByDefault(Return(true)); - - /* default action for GetCharacteristic function call */ - ON_CALL(gatt_interface, GetCharacteristic(_, _)) - .WillByDefault( - Invoke([&](uint16_t conn_id, - uint16_t handle) -> const gatt::Characteristic* { - std::list<gatt::Service>& services = services_map[conn_id]; - for (auto const& service : services) { - for (auto const& characteristic : service.characteristics) { - if (characteristic.value_handle == handle) { - return &characteristic; - } - } - } - - return nullptr; - })); - - /* default action for GetOwningService function call */ - ON_CALL(gatt_interface, GetOwningService(_, _)) - .WillByDefault(Invoke( - [&](uint16_t conn_id, uint16_t handle) -> const gatt::Service* { - std::list<gatt::Service>& services = services_map[conn_id]; - for (auto const& service : services) { - if (service.handle <= handle && service.end_handle >= handle) { - return &service; - } - } - - return nullptr; - })); - - ON_CALL(gatt_interface, ServiceSearchRequest(_, _)) - .WillByDefault(WithArg<0>(Invoke( - [&](uint16_t conn_id) { InjectSearchCompleteEvent(conn_id); }))); - - /* default action for GetServices function call */ - ON_CALL(gatt_interface, GetServices(_)) - .WillByDefault(WithArg<0>( - Invoke([&](uint16_t conn_id) -> std::list<gatt::Service>* { - return &services_map[conn_id]; - }))); - - /* default action for RegisterForNotifications function call */ - ON_CALL(gatt_interface, RegisterForNotifications(gatt_if, _, _)) - .WillByDefault(Return(GATT_SUCCESS)); - - /* default action for DeregisterForNotifications function call */ - ON_CALL(gatt_interface, DeregisterForNotifications(gatt_if, _, _)) - .WillByDefault(Return(GATT_SUCCESS)); - - /* default action for WriteDescriptor function call */ - ON_CALL(gatt_queue, WriteDescriptor(_, _, _, _, _, _)) - .WillByDefault( - Invoke([](uint16_t conn_id, uint16_t handle, - std::vector<uint8_t> value, tGATT_WRITE_TYPE write_type, - GATT_WRITE_OP_CB cb, void* cb_data) -> void { - if (cb) - cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(), - cb_data); - })); - - /* by default connect only direct connection requests */ - ON_CALL(gatt_interface, Open(_, _, _, _)) - .WillByDefault( - Invoke([&](tGATT_IF client_if, const RawAddress& remote_bda, - tBTM_BLE_CONN_TYPE connection_type, bool opportunistic) { - if (connection_type == BTM_BLE_DIRECT_CONNECTION) - InjectConnectedEvent(remote_bda, GetTestConnId(remote_bda)); - })); - - ON_CALL(gatt_interface, Close(_)) - .WillByDefault(Invoke( - [&](uint16_t conn_id) { InjectDisconnectedEvent(conn_id); })); - } - - void TearDown(void) override { - services_map.clear(); - gatt::SetMockBtaGattQueue(nullptr); - gatt::SetMockBtaGattInterface(nullptr); - bluetooth::storage::SetMockBtifStorageInterface(nullptr); - bluetooth::manager::SetMockBtmInterface(nullptr); - controller::SetMockControllerInterface(nullptr); - callbacks.reset(); - - current_peer_active_preset_idx_.clear(); - current_peer_features_val_.clear(); - } - - void TestAppRegister(void) { - BtaAppRegisterCallback app_register_callback; - EXPECT_CALL(gatt_interface, AppRegister(_, _, _)) - .WillOnce(DoAll(SaveArg<0>(&gatt_callback), - SaveArg<1>(&app_register_callback))); - HasClient::Initialize(callbacks.get(), base::DoNothing()); - ASSERT_TRUE(gatt_callback); - ASSERT_TRUE(app_register_callback); - app_register_callback.Run(gatt_if, GATT_SUCCESS); - ASSERT_TRUE(HasClient::IsHasClientRunning()); - Mock::VerifyAndClearExpectations(&gatt_interface); - } - - void TestAppUnregister(void) { - EXPECT_CALL(gatt_interface, AppDeregister(gatt_if)); - HasClient::CleanUp(); - ASSERT_FALSE(HasClient::IsHasClientRunning()); - gatt_callback = nullptr; - } - - void TestConnect(const RawAddress& address) { - ON_CALL(btm_interface, BTM_IsEncrypted(address, _)) - .WillByDefault(DoAll(Return(encryption_result))); - - EXPECT_CALL(gatt_interface, - Open(gatt_if, address, BTM_BLE_DIRECT_CONNECTION, _)); - HasClient::Get()->Connect(address); - - Mock::VerifyAndClearExpectations(&*callbacks); - Mock::VerifyAndClearExpectations(&gatt_queue); - Mock::VerifyAndClearExpectations(&gatt_interface); - Mock::VerifyAndClearExpectations(&btm_interface); - } - - void TestDisconnect(const RawAddress& address, uint16_t conn_id) { - EXPECT_CALL(gatt_interface, CancelOpen(_, address, _)).Times(AnyNumber()); - if (conn_id != GATT_INVALID_CONN_ID) { - assert(0); - EXPECT_CALL(gatt_interface, Close(conn_id)); - } else { - EXPECT_CALL(gatt_interface, CancelOpen(gatt_if, address, _)); - } - HasClient::Get()->Disconnect(address); - } - - void TestAddFromStorage(const RawAddress& address, uint8_t features, - bool auto_connect) { - if (auto_connect) { - EXPECT_CALL(gatt_interface, - Open(gatt_if, address, BTM_BLE_BKG_CONNECT_ALLOW_LIST, _)); - HasClient::Get()->AddFromStorage(address, features, auto_connect); - - /* Inject connected event for autoconnect/background connection */ - InjectConnectedEvent(address, GetTestConnId(address)); - } else { - EXPECT_CALL(gatt_interface, Open(gatt_if, address, _, _)).Times(0); - HasClient::Get()->AddFromStorage(address, features, auto_connect); - } - - Mock::VerifyAndClearExpectations(&gatt_interface); - } - - void InjectConnectedEvent(const RawAddress& address, uint16_t conn_id, - tGATT_STATUS status = GATT_SUCCESS) { - tBTA_GATTC_OPEN event_data = { - .status = status, - .conn_id = conn_id, - .client_if = gatt_if, - .remote_bda = address, - .transport = GATT_TRANSPORT_LE, - .mtu = 240, - }; - - connected_devices[conn_id] = address; - gatt_callback(BTA_GATTC_OPEN_EVT, (tBTA_GATTC*)&event_data); - } - - void InjectDisconnectedEvent( - uint16_t conn_id, - tGATT_DISCONN_REASON reason = GATT_CONN_TERMINATE_LOCAL_HOST, - bool allow_fake_conn = false) { - if (!allow_fake_conn) ASSERT_NE(connected_devices.count(conn_id), 0u); - - tBTA_GATTC_CLOSE event_data = { - .status = GATT_SUCCESS, - .conn_id = conn_id, - .client_if = gatt_if, - .remote_bda = connected_devices[conn_id], - .reason = reason, - }; - - connected_devices.erase(conn_id); - gatt_callback(BTA_GATTC_CLOSE_EVT, (tBTA_GATTC*)&event_data); - } - - void InjectSearchCompleteEvent(uint16_t conn_id) { - tBTA_GATTC_SEARCH_CMPL event_data = { - .status = GATT_SUCCESS, - .conn_id = conn_id, - }; - - gatt_callback(BTA_GATTC_SEARCH_CMPL_EVT, (tBTA_GATTC*)&event_data); - } - - void InjectNotificationEvent(const RawAddress& test_address, uint16_t conn_id, - uint16_t handle, std::vector<uint8_t> value, - bool indicate = false) { - tBTA_GATTC_NOTIFY event_data = { - .conn_id = conn_id, - .bda = test_address, - .handle = handle, - .len = (uint8_t)value.size(), - .is_notify = !indicate, - }; - - ASSERT_TRUE(value.size() < GATT_MAX_ATTR_LEN); - std::copy(value.begin(), value.end(), event_data.value); - gatt_callback(BTA_GATTC_NOTIF_EVT, (tBTA_GATTC*)&event_data); - } - - void SetEncryptionResult(const RawAddress& address, bool success) { - encryption_result = success; - ON_CALL(btm_interface, BTM_IsEncrypted(address, _)) - .WillByDefault(Return(success)); - ON_CALL(btm_interface, GetSecurityFlagsByTransport(address, NotNull(), _)) - .WillByDefault( - DoAll(SetArgPointee<1>(success ? BTM_SEC_FLAG_ENCRYPTED : 0), - Return(true))); - if (!success) { - EXPECT_CALL(btm_interface, - SetEncryption(address, _, NotNull(), _, BTM_BLE_SEC_ENCRYPT)) - .WillOnce(Invoke( - [success](const RawAddress& bd_addr, tBT_TRANSPORT transport, - tBTM_SEC_CALLBACK* p_callback, void* p_ref_data, - tBTM_BLE_SEC_ACT sec_act) -> tBTM_STATUS { - p_callback(&bd_addr, transport, p_ref_data, - success ? BTM_SUCCESS : BTM_FAILED_ON_SECURITY); - return BTM_SUCCESS; - })); - } - } - - void InjectNotifyReadPresetResponse(uint16_t conn_id, - RawAddress const& address, - uint16_t handle, const HasPreset& preset, - bool indicate, bool is_last) { - std::vector<uint8_t> value; - - value.push_back( - static_cast<std::underlying_type_t<::le_audio::has::PresetCtpOpcode>>( - ::le_audio::has::PresetCtpOpcode::READ_PRESET_RESPONSE)); - value.push_back(is_last ? 0x01 : 0x00); - - preset.ToCharacteristicValue(value); - InjectNotificationEvent(address, conn_id, handle, value, indicate); - } - - void InjectPresetChanged(uint16_t conn_id, RawAddress const& address, - bool indicate, const HasPreset& preset, - uint8_t prev_index, - ::le_audio::has::PresetCtpChangeId change_id, - bool is_last) { - std::vector<uint8_t> value; - - value.push_back( - static_cast<std::underlying_type_t<::le_audio::has::PresetCtpOpcode>>( - ::le_audio::has::PresetCtpOpcode::PRESET_CHANGED)); - value.push_back(static_cast<uint8_t>(change_id)); - value.push_back(is_last ? 0x01 : 0x00); - - switch (change_id) { - case ::le_audio::has::PresetCtpChangeId::PRESET_GENERIC_UPDATE: - value.push_back(prev_index); - preset.ToCharacteristicValue(value); - break; - case ::le_audio::has::PresetCtpChangeId::PRESET_DELETED: - case ::le_audio::has::PresetCtpChangeId::PRESET_AVAILABLE: - case ::le_audio::has::PresetCtpChangeId::PRESET_UNAVAILABLE: - default: - value.push_back(preset.GetIndex()); - break; - } - - InjectNotificationEvent(address, conn_id, HasDbBuilder::kPresetsCtpValHdl, - value, indicate); - } - - void InjectNotifyReadPresetsResponse( - uint16_t conn_id, RawAddress const& address, uint16_t handle, - std::vector<uint8_t> value, bool indicate, int index, int num_of_indices, - GATT_WRITE_OP_CB cb, void* cb_data) { - auto presets = current_peer_presets_.at(conn_id); - LOG_ASSERT(!presets.empty()) << __func__ << " Mocking error!"; - - /* Index is a start index, not necessary is a valid index for the - * peer device */ - auto preset = presets.find(index); - while (preset == presets.end() && - index++ <= ::le_audio::has::kMaxNumOfPresets) { - preset = presets.find(index); - } - - if (preset == presets.end()) { - /* operation not possible */ - if (cb) - cb(conn_id, (tGATT_STATUS)0x83, handle, value.size(), value.data(), - cb_data); - - return; - } - - if (cb) - cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(), cb_data); - /* Notify presets */ - int num_of_notif = 1; - while (1) { - bool last = - preset == std::prev(presets.end()) || num_of_notif == num_of_indices; - InjectNotifyReadPresetResponse(conn_id, address, handle, *preset, - indicate, (last)); - if (last) return; - - num_of_notif++; - preset++; - } - } - - void InjectActivePresetNotification(uint16_t conn_id, - RawAddress const& address, - uint16_t handle, - std::vector<uint8_t> wr_value, - uint8_t index, GATT_WRITE_OP_CB cb, - void* cb_data) { - auto presets = current_peer_presets_.at(conn_id); - LOG_ASSERT(!presets.empty()) << __func__ << " Mocking error!"; - - auto preset = presets.find(index); - if (preset == presets.end()) { - /* preset operation not possible */ - if (cb) - cb(conn_id, (tGATT_STATUS)0x83, handle, wr_value.size(), - wr_value.data(), cb_data); - return; - } - - std::vector<uint8_t> value; - value.push_back(index); - InjectNotificationEvent( - address, conn_id, HasDbBuilder::kActivePresetIndexValHdl, value, false); - } - - void SetSampleDatabaseHasNoFeatures(const RawAddress& address) { - HasDbBuilder builder = { - .has = true, - .features = false, - .features_ntf = false, - .preset_cp = true, - .preset_cp_ntf = false, - .preset_cp_ind = true, - .active_preset_idx = true, - .active_preset_idx_ntf = true, - }; - set_sample_database(address, builder); - } - - void SetSampleDatabaseHasNoPresetChange(const RawAddress& address, - uint8_t features_value = 0x00) { - HasDbBuilder builder = { - .has = true, - .features = true, - .features_ntf = false, - .preset_cp = false, - .preset_cp_ntf = false, - .preset_cp_ind = false, - .active_preset_idx = false, - .active_preset_idx_ntf = false, - }; - set_sample_database(address, builder, features_value); - } - - void SetSampleDatabaseHasNoOptionalNtf(const RawAddress& address, - uint8_t features = 0x00) { - HasDbBuilder builder = { - .has = true, - .features = true, - .features_ntf = false, - .preset_cp = true, - .preset_cp_ntf = false, - .preset_cp_ind = true, - .active_preset_idx = true, - .active_preset_idx_ntf = true, - }; - set_sample_database(address, builder, features); - } - - void SetSampleDatabaseNoHas(const RawAddress& address, - uint8_t features = 0x00) { - HasDbBuilder builder = { - .has = false, - .features = false, - .features_ntf = false, - .preset_cp = false, - .preset_cp_ntf = false, - .preset_cp_ind = false, - .active_preset_idx = true, - .active_preset_idx_ntf = true, - }; - set_sample_database(address, builder, features); - } - - void SetSampleDatabaseHasBrokenNoActivePreset(const RawAddress& address, - uint8_t features = 0x00) { - HasDbBuilder builder = { - .has = true, - .features = true, - .features_ntf = false, - .preset_cp = true, - .preset_cp_ntf = true, - .preset_cp_ind = true, - .active_preset_idx = false, - .active_preset_idx_ntf = false, - }; - set_sample_database(address, builder, features); - } - - void SetSampleDatabaseHasBrokenNoActivePresetNtf(const RawAddress& address, - uint8_t features = 0x00) { - HasDbBuilder builder = { - .has = true, - .features = true, - .features_ntf = false, - .preset_cp = true, - .preset_cp_ntf = true, - .preset_cp_ind = true, - .active_preset_idx = true, - .active_preset_idx_ntf = false, - }; - set_sample_database(address, builder, features); - } - - void SetSampleDatabaseHasOnlyFeaturesNtf(const RawAddress& address, - uint8_t features = 0x00) { - HasDbBuilder builder = { - .has = true, - .features = true, - .features_ntf = true, - .preset_cp = false, - .preset_cp_ntf = false, - .preset_cp_ind = false, - .active_preset_idx = false, - .active_preset_idx_ntf = false, - }; - set_sample_database(address, builder, features); - } - - void SetSampleDatabaseHasOnlyFeaturesNoNtf(const RawAddress& address, - uint8_t features = 0x00) { - HasDbBuilder builder = { - .has = true, - .features = true, - .features_ntf = false, - .preset_cp = false, - .preset_cp_ntf = false, - .preset_cp_ind = false, - .active_preset_idx = false, - .active_preset_idx_ntf = false, - }; - set_sample_database(address, builder, features); - } - - void SetSampleDatabaseHasPresetsNtf( - const RawAddress& address, - uint8_t features = bluetooth::has::kFeatureBitHearingAidTypeMonaural, - std::optional<std::set<HasPreset, HasPreset::ComparatorDesc>> presets = - std::nullopt) { - HasDbBuilder builder = { - .has = true, - .features = true, - .features_ntf = true, - .preset_cp = true, - .preset_cp_ntf = true, - .preset_cp_ind = true, - .active_preset_idx = true, - .active_preset_idx_ntf = true, - }; - - set_sample_database(address, builder, features, presets); - } - - void SetSampleDatabaseHasNoPresetsFlagsOnly(const RawAddress& address) { - uint8_t features = bluetooth::has::kFeatureBitHearingAidTypeMonaural; - HasDbBuilder builder = { - .has = true, - .features = true, - .features_ntf = true, - .preset_cp = false, - .preset_cp_ntf = false, - .preset_cp_ind = false, - .active_preset_idx = false, - .active_preset_idx_ntf = false, - }; - - set_sample_database(address, builder, features, std::nullopt); - } - - std::unique_ptr<MockHasCallbacks> callbacks; - bluetooth::manager::MockBtmInterface btm_interface; - bluetooth::storage::MockBtifStorageInterface btif_storage_interface_; - controller::MockControllerInterface controller_interface_; - gatt::MockBtaGattInterface gatt_interface; - gatt::MockBtaGattQueue gatt_queue; - MockCsisClient mock_csis_client_module_; - tBTA_GATTC_CBACK* gatt_callback; - const uint8_t gatt_if = 0xfe; - std::map<uint8_t, RawAddress> connected_devices; - std::map<uint16_t, std::list<gatt::Service>> services_map; - bool encryption_result; -}; - -class HasClientTest : public HasClientTestBase { - void SetUp(void) override { - HasClientTestBase::SetUp(); - TestAppRegister(); - } - void TearDown(void) override { - TestAppUnregister(); - HasClientTestBase::TearDown(); - } -}; - -TEST_F(HasClientTestBase, test_get_uninitialized) { - ASSERT_DEATH(HasClient::Get(), ""); -} - -TEST_F(HasClientTestBase, test_initialize) { - HasClient::Initialize(callbacks.get(), base::DoNothing()); - ASSERT_TRUE(HasClient::IsHasClientRunning()); - HasClient::CleanUp(); -} - -TEST_F(HasClientTestBase, test_initialize_twice) { - HasClient::Initialize(callbacks.get(), base::DoNothing()); - HasClient* has_p = HasClient::Get(); - HasClient::Initialize(callbacks.get(), base::DoNothing()); - ASSERT_EQ(has_p, HasClient::Get()); - HasClient::CleanUp(); -} - -TEST_F(HasClientTestBase, test_cleanup_initialized) { - HasClient::Initialize(callbacks.get(), base::DoNothing()); - HasClient::CleanUp(); - ASSERT_FALSE(HasClient::IsHasClientRunning()); -} - -TEST_F(HasClientTestBase, test_cleanup_uninitialized) { - HasClient::CleanUp(); - ASSERT_FALSE(HasClient::IsHasClientRunning()); -} - -TEST_F(HasClientTestBase, test_app_registration) { - TestAppRegister(); - TestAppUnregister(); -} - -TEST_F(HasClientTest, test_connect) { TestConnect(GetTestAddress(1)); } - -TEST_F(HasClientTest, test_add_from_storage) { - TestAddFromStorage(GetTestAddress(1), 0, true); - TestAddFromStorage(GetTestAddress(2), 0, false); -} - -TEST_F(HasClientTest, test_disconnect_non_connected) { - const RawAddress test_address = GetTestAddress(1); - - /* Override the default action to prevent us sendind the connected event */ - EXPECT_CALL(gatt_interface, - Open(gatt_if, test_address, BTM_BLE_DIRECT_CONNECTION, _)) - .WillOnce(Return()); - HasClient::Get()->Connect(test_address); - TestDisconnect(test_address, GATT_INVALID_CONN_ID); -} - -TEST_F(HasClientTest, test_has_connected) { - const RawAddress test_address = GetTestAddress(1); - /* Minimal possible HA device (only feature flags) */ - SetSampleDatabaseHasNoPresetChange( - test_address, bluetooth::has::kFeatureBitHearingAidTypeBinaural); - - EXPECT_CALL( - *callbacks, - OnDeviceAvailable(test_address, - bluetooth::has::kFeatureBitHearingAidTypeBinaural)); - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)); - TestConnect(test_address); -} - -TEST_F(HasClientTest, test_disconnect_connected) { - const RawAddress test_address = GetTestAddress(1); - /* Minimal possible HA device (only feature flags) */ - SetSampleDatabaseHasNoPresetChange( - test_address, bluetooth::has::kFeatureBitHearingAidTypeBinaural); - - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)) - .Times(1); - TestConnect(test_address); - - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::DISCONNECTED, test_address)) - .Times(1); - TestDisconnect(test_address, 1); -} - -TEST_F(HasClientTest, test_disconnected_while_autoconnect) { - const RawAddress test_address = GetTestAddress(1); - TestAddFromStorage(test_address, - bluetooth::has::kFeatureBitHearingAidTypeBinaural, true); - /* autoconnect - don't indicate disconnection */ - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::DISCONNECTED, test_address)) - .Times(0); - /* Verify that the device still can connect in te background */ - InjectDisconnectedEvent(1, GATT_CONN_TERMINATE_PEER_USER, true); -} - -TEST_F(HasClientTest, test_encryption_failed) { - const RawAddress test_address = GetTestAddress(1); - SetSampleDatabaseHasNoPresetChange( - test_address, bluetooth::has::kFeatureBitHearingAidTypeBinaural); - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::DISCONNECTED, test_address)) - .Times(1); - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)) - .Times(0); - SetEncryptionResult(test_address, false); - TestConnect(test_address); -} - -TEST_F(HasClientTest, test_reconnect_after_encryption_failed) { - const RawAddress test_address = GetTestAddress(1); - SetSampleDatabaseHasNoPresetChange( - test_address, bluetooth::has::kFeatureBitHearingAidTypeBinaural); - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::DISCONNECTED, test_address)) - .Times(1); - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)) - .Times(0); - SetEncryptionResult(test_address, false); - TestConnect(test_address); - - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)) - .Times(1); - SetEncryptionResult(test_address, true); - InjectConnectedEvent(test_address, GetTestConnId(test_address)); -} - -TEST_F(HasClientTest, test_reconnect_after_encryption_failed_from_storage) { - const RawAddress test_address = GetTestAddress(1); - - SetSampleDatabaseHasNoPresetChange( - test_address, bluetooth::has::kFeatureBitHearingAidTypeBinaural); - SetEncryptionResult(test_address, false); - TestAddFromStorage(test_address, 0, true); - /* autoconnect - don't indicate disconnection */ - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::DISCONNECTED, test_address)) - .Times(0); - Mock::VerifyAndClearExpectations(&btm_interface); - - /* Fake no persistent storage data */ - ON_CALL(btif_storage_interface_, GetLeaudioHasPresets(_, _, _)) - .WillByDefault([]() { return false; }); - - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)) - .Times(1); - SetEncryptionResult(test_address, true); - InjectConnectedEvent(test_address, GetTestConnId(test_address)); -} - -TEST_F(HasClientTest, test_load_from_storage_and_connect) { - const RawAddress test_address = GetTestAddress(1); - SetSampleDatabaseHasPresetsNtf(test_address, kFeatureBitDynamicPresets); - SetEncryptionResult(test_address, true); - - std::set<HasPreset, HasPreset::ComparatorDesc> has_presets = {{ - HasPreset(5, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "YourWritablePreset5"), - HasPreset(55, HasPreset::kPropertyAvailable, "YourPreset55"), - }}; - - /* Load persistent storage data */ - ON_CALL(btif_storage_interface_, GetLeaudioHasPresets(test_address, _, _)) - .WillByDefault([&has_presets](const RawAddress& address, - std::vector<uint8_t>& presets_bin, - uint8_t& active_preset) { - /* Generate presets binary to be used instead the attribute values */ - HasDevice device(address, 0); - device.has_presets = has_presets; - active_preset = 55; - - if (device.SerializePresets(presets_bin)) return true; - - return false; - }); - - EXPECT_CALL(gatt_interface, RegisterForNotifications(gatt_if, _, _)) - .Times(1 // preset control point - + 1 // active preset - + 1); // features - - EXPECT_CALL(*callbacks, - OnDeviceAvailable(test_address, - (kFeatureBitWritablePresets | - kFeatureBitPresetSynchronizationSupported | - kFeatureBitHearingAidTypeBanded))); - - std::vector<PresetInfo> loaded_preset_details; - EXPECT_CALL(*callbacks, - OnPresetInfo(std::variant<RawAddress, int>(test_address), - PresetInfoReason::ALL_PRESET_INFO, _)) - .WillOnce(SaveArg<2>(&loaded_preset_details)); - - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address), 55)); - - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)); - - /* Expect no read or write operations when loading from storage */ - EXPECT_CALL(gatt_queue, ReadCharacteristic(1, _, _, _)).Times(0); - EXPECT_CALL(gatt_queue, WriteDescriptor(1, _, _, _, _, _)).Times(3); - - TestAddFromStorage(test_address, - kFeatureBitWritablePresets | - kFeatureBitPresetSynchronizationSupported | - kFeatureBitHearingAidTypeBanded, - true); - - for (auto const& info : loaded_preset_details) { - auto preset = has_presets.find(info.preset_index); - ASSERT_NE(preset, has_presets.end()); - if (preset->GetProperties() & HasPreset::kPropertyAvailable) - ASSERT_TRUE(info.available); - if (preset->GetProperties() & HasPreset::kPropertyWritable) - ASSERT_TRUE(info.writable); - ASSERT_EQ(preset->GetName(), info.preset_name); - } -} - -TEST_F(HasClientTest, test_load_from_storage) { - const RawAddress test_address = GetTestAddress(1); - SetSampleDatabaseHasPresetsNtf(test_address, kFeatureBitDynamicPresets); - SetEncryptionResult(test_address, true); - - std::set<HasPreset, HasPreset::ComparatorDesc> has_presets = {{ - HasPreset(5, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "YourWritablePreset5"), - HasPreset(55, HasPreset::kPropertyAvailable, "YourPreset55"), - }}; - - /* Load persistent storage data */ - ON_CALL(btif_storage_interface_, GetLeaudioHasPresets(test_address, _, _)) - .WillByDefault([&has_presets](const RawAddress& address, - std::vector<uint8_t>& presets_bin, - uint8_t& active_preset) { - /* Generate presets binary to be used instead the attribute values */ - HasDevice device(address, 0); - device.has_presets = has_presets; - active_preset = 55; - - if (device.SerializePresets(presets_bin)) return true; - - return false; - }); - - EXPECT_CALL(gatt_interface, RegisterForNotifications(gatt_if, _, _)) - .Times(0); // features - - EXPECT_CALL(*callbacks, - OnDeviceAvailable(test_address, - (kFeatureBitWritablePresets | - kFeatureBitPresetSynchronizationSupported | - kFeatureBitHearingAidTypeBanded))); - - std::vector<PresetInfo> loaded_preset_details; - EXPECT_CALL(*callbacks, - OnPresetInfo(std::variant<RawAddress, int>(test_address), - PresetInfoReason::ALL_PRESET_INFO, _)) - .Times(0); - - /* Expect no read or write operations when loading from storage */ - EXPECT_CALL(gatt_queue, ReadCharacteristic(1, _, _, _)).Times(0); - EXPECT_CALL(gatt_queue, WriteDescriptor(1, _, _, _, _, _)).Times(0); - - TestAddFromStorage(test_address, - kFeatureBitWritablePresets | - kFeatureBitPresetSynchronizationSupported | - kFeatureBitHearingAidTypeBanded, - false); -} - -TEST_F(HasClientTest, test_write_to_storage) { - const RawAddress test_address = GetTestAddress(1); - - std::set<HasPreset, HasPreset::ComparatorDesc> has_presets = {{ - HasPreset(1, HasPreset::kPropertyAvailable, "Universal"), - HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "Preset2"), - }}; - SetSampleDatabaseHasPresetsNtf( - test_address, - bluetooth::has::kFeatureBitHearingAidTypeBanded | - bluetooth::has::kFeatureBitWritablePresets | - bluetooth::has::kFeatureBitDynamicPresets, - has_presets); - - std::vector<uint8_t> serialized; - EXPECT_CALL( - btif_storage_interface_, - AddLeaudioHasDevice(test_address, _, - bluetooth::has::kFeatureBitHearingAidTypeBanded | - bluetooth::has::kFeatureBitWritablePresets | - bluetooth::has::kFeatureBitDynamicPresets, - 1)) - .WillOnce(SaveArg<1>(&serialized)); - TestConnect(test_address); - - /* Deserialize the written binary to verify the content */ - HasDevice clone(test_address, - bluetooth::has::kFeatureBitHearingAidTypeBanded | - bluetooth::has::kFeatureBitWritablePresets | - bluetooth::has::kFeatureBitDynamicPresets); - ASSERT_TRUE(HasDevice::DeserializePresets(serialized.data(), - serialized.size(), clone)); - auto storage_info = clone.GetAllPresetInfo(); - ASSERT_EQ(storage_info.size(), has_presets.size()); - for (auto const& info : storage_info) { - auto preset = has_presets.find(info.preset_index); - ASSERT_NE(preset, has_presets.end()); - if (preset->GetProperties() & HasPreset::kPropertyAvailable) - ASSERT_TRUE(info.available); - if (preset->GetProperties() & HasPreset::kPropertyWritable) - ASSERT_TRUE(info.writable); - ASSERT_EQ(preset->GetName(), info.preset_name); - } -} - -TEST_F(HasClientTest, test_discovery_basic_has_no_opt_ntf) { - const RawAddress test_address = GetTestAddress(1); - auto test_conn_id = GetTestConnId(test_address); - - SetSampleDatabaseHasNoOptionalNtf(test_address); - - std::variant<RawAddress, int> addr_or_group = test_address; - std::vector<PresetInfo> preset_details; - uint8_t active_preset_index; - uint8_t has_features; - - EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _)) - .WillOnce(SaveArg<1>(&has_features)); - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)); - EXPECT_CALL(*callbacks, OnPresetInfo(_, PresetInfoReason::ALL_PRESET_INFO, _)) - .WillOnce(DoAll(SaveArg<0>(&addr_or_group), SaveArg<2>(&preset_details))); - EXPECT_CALL(*callbacks, OnActivePresetSelected(_, _)) - .WillOnce( - DoAll(SaveArg<0>(&addr_or_group), SaveArg<1>(&active_preset_index))); - TestConnect(test_address); - - /* Verify sample database content */ - ASSERT_TRUE(std::holds_alternative<RawAddress>(addr_or_group)); - ASSERT_EQ(std::get<RawAddress>(addr_or_group), test_address); - ASSERT_EQ(has_features, 0x00); - ASSERT_EQ(active_preset_index, - current_peer_presets_.at(test_conn_id).begin()->GetIndex()); - - /* Verify presets */ - uint16_t conn_id = GetTestConnId(test_address); - ASSERT_NE(preset_details.size(), 0u); - ASSERT_EQ(current_peer_presets_.at(conn_id).size(), preset_details.size()); - - for (auto const& preset : current_peer_presets_.at(conn_id)) { - auto it = - std::find_if(preset_details.cbegin(), preset_details.cend(), - [&preset](auto const& preset_info) { - return preset_info.preset_index == preset.GetIndex(); - }); - ASSERT_NE(it, preset_details.cend()); - ASSERT_EQ(preset.GetName(), it->preset_name); - ASSERT_EQ(preset.IsAvailable(), it->available); - ASSERT_EQ(preset.IsWritable(), it->writable); - } - - /* Verify active preset is there */ - ASSERT_EQ(preset_details.size(), - current_peer_presets_.at(test_conn_id).size()); - ASSERT_TRUE(std::find_if(preset_details.begin(), preset_details.end(), - [active_preset_index](auto const& preset_info) { - return preset_info.preset_index == - active_preset_index; - }) != preset_details.end()); -} - -TEST_F(HasClientTest, test_discovery_has_not_found) { - const RawAddress test_address = GetTestAddress(1); - SetSampleDatabaseNoHas(test_address); - - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)) - .Times(0); - EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _)).Times(0); - EXPECT_CALL(*callbacks, OnFeaturesUpdate(test_address, _)).Times(0); - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::DISCONNECTED, test_address)); - - TestConnect(test_address); -} - -TEST_F(HasClientTest, test_discovery_has_broken_no_active_preset) { - const RawAddress test_address = GetTestAddress(1); - SetSampleDatabaseHasBrokenNoActivePreset(test_address); - - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)) - .Times(0); - EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _)).Times(0); - EXPECT_CALL(*callbacks, OnFeaturesUpdate(test_address, _)).Times(0); - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::DISCONNECTED, test_address)); - - TestConnect(test_address); -} - -TEST_F(HasClientTest, test_discovery_has_broken_no_active_preset_ntf) { - const RawAddress test_address = GetTestAddress(1); - SetSampleDatabaseHasBrokenNoActivePresetNtf(test_address); - - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)) - .Times(0); - EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _)).Times(0); - EXPECT_CALL(*callbacks, OnFeaturesUpdate(test_address, _)).Times(0); - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::DISCONNECTED, test_address)); - - TestConnect(test_address); -} - -TEST_F(HasClientTest, test_discovery_has_features_ntf) { - const RawAddress test_address = GetTestAddress(1); - auto test_conn_id = GetTestConnId(test_address); - uint8_t has_features; - - SetSampleDatabaseHasOnlyFeaturesNtf( - test_address, bluetooth::has::kFeatureBitHearingAidTypeBanded); - - EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _)) - .WillOnce(SaveArg<1>(&has_features)); - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)) - .Times(1); - - /* Verify subscription to features */ - EXPECT_CALL(gatt_interface, RegisterForNotifications(gatt_if, _, _)) - .Times(AnyNumber()); - EXPECT_CALL(gatt_interface, - RegisterForNotifications(gatt_if, test_address, - HasDbBuilder::kFeaturesValHdl)); - - /* Verify features CCC was written */ - EXPECT_CALL(gatt_queue, WriteDescriptor(test_conn_id, _, _, _, _, _)) - .Times(AnyNumber()); - EXPECT_CALL(gatt_queue, - WriteDescriptor(test_conn_id, HasDbBuilder::kFeaturesValHdl + 1, - std::vector<uint8_t>{0x01, 0x00}, _, _, _)); - TestConnect(test_address); - - /* Verify features */ - ASSERT_EQ(has_features, bluetooth::has::kFeatureBitHearingAidTypeBanded); - - uint8_t new_features; - - /* Verify peer features change notification */ - EXPECT_CALL(*callbacks, OnFeaturesUpdate(test_address, _)) - .WillOnce(SaveArg<1>(&new_features)); - InjectNotificationEvent(test_address, test_conn_id, - HasDbBuilder::kFeaturesValHdl, - std::vector<uint8_t>({0x00})); - ASSERT_NE(has_features, new_features); -} - -TEST_F(HasClientTest, test_discovery_has_features_no_ntf) { - const RawAddress test_address = GetTestAddress(1); - auto test_conn_id = GetTestConnId(test_address); - uint8_t has_features; - - SetSampleDatabaseHasOnlyFeaturesNoNtf( - test_address, bluetooth::has::kFeatureBitHearingAidTypeBanded); - - EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _)) - .WillOnce(SaveArg<1>(&has_features)); - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)) - .Times(1); - - /* Verify no subscription to features */ - EXPECT_CALL(gatt_interface, RegisterForNotifications(gatt_if, _, _)) - .Times(AnyNumber()); - EXPECT_CALL(gatt_interface, - RegisterForNotifications(gatt_if, test_address, - HasDbBuilder::kFeaturesValHdl)) - .Times(0); - - /* Verify no features CCC was written */ - EXPECT_CALL(gatt_queue, WriteDescriptor(test_conn_id, _, _, _, _, _)) - .Times(AnyNumber()); - EXPECT_CALL(gatt_queue, - WriteDescriptor(test_conn_id, HasDbBuilder::kFeaturesValHdl + 1, - _, _, _, _)) - .Times(0); - TestConnect(test_address); - - /* Verify features */ - ASSERT_EQ(has_features, bluetooth::has::kFeatureBitHearingAidTypeBanded); -} - -TEST_F(HasClientTest, test_discovery_has_multiple_presets_ntf) { - const RawAddress test_address = GetTestAddress(1); - SetSampleDatabaseHasPresetsNtf( - test_address, bluetooth::has::kFeatureBitHearingAidTypeBanded); - - std::variant<RawAddress, int> addr_or_group = test_address; - std::vector<PresetInfo> preset_details; - uint8_t active_preset_index; - uint8_t has_features; - - EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _)) - .WillOnce(SaveArg<1>(&has_features)); - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)); - EXPECT_CALL(*callbacks, OnPresetInfo(_, PresetInfoReason::ALL_PRESET_INFO, _)) - .WillOnce(DoAll(SaveArg<0>(&addr_or_group), SaveArg<2>(&preset_details))); - EXPECT_CALL(*callbacks, OnActivePresetSelected(_, _)) - .WillOnce( - DoAll(SaveArg<0>(&addr_or_group), SaveArg<1>(&active_preset_index))); - - /* Verify subscription to control point */ - EXPECT_CALL(gatt_interface, RegisterForNotifications(gatt_if, _, _)) - .Times(AnyNumber()); - EXPECT_CALL(gatt_interface, - RegisterForNotifications(gatt_if, test_address, - HasDbBuilder::kPresetsCtpValHdl)); - - /* Verify features CCC was written */ - EXPECT_CALL(gatt_queue, WriteDescriptor(1, _, _, _, _, _)).Times(AnyNumber()); - EXPECT_CALL(gatt_queue, - WriteDescriptor(1, HasDbBuilder::kPresetsCtpValHdl + 1, - std::vector<uint8_t>{0x03, 0x00}, _, _, _)); - TestConnect(test_address); - - /* Verify features */ - ASSERT_EQ(has_features, bluetooth::has::kFeatureBitHearingAidTypeBanded); -} - -TEST_F(HasClientTest, test_active_preset_change) { - const RawAddress test_address = GetTestAddress(1); - auto test_conn_id = GetTestConnId(test_address); - - SetSampleDatabaseHasNoOptionalNtf(test_address); - - uint8_t active_preset_index; - EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _)); - EXPECT_CALL(*callbacks, - OnPresetInfo(_, PresetInfoReason::ALL_PRESET_INFO, _)); - EXPECT_CALL(*callbacks, OnActivePresetSelected(_, _)) - .WillOnce(SaveArg<1>(&active_preset_index)); - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)); - TestConnect(test_address); - - uint8_t new_active_preset; - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address), _)) - .WillOnce(SaveArg<1>(&new_active_preset)); - InjectNotificationEvent(test_address, test_conn_id, - HasDbBuilder::kActivePresetIndexValHdl, - std::vector<uint8_t>({0x00})); - - ASSERT_NE(active_preset_index, new_active_preset); - ASSERT_EQ(new_active_preset, 0x00); -} - -TEST_F(HasClientTest, test_duplicate_presets) { - const RawAddress test_address = GetTestAddress(1); - std::vector<PresetInfo> preset_details; - - /* Handle duplicates gracefully */ - SetSampleDatabaseHasPresetsNtf( - test_address, kFeatureBitWritablePresets, - {{HasPreset(5, - HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "YourWritablePreset5"), - HasPreset(5, - HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "YourWritablePreset5")}}); - - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)); - EXPECT_CALL(*callbacks, OnPresetInfo(_, PresetInfoReason::ALL_PRESET_INFO, _)) - .WillOnce(SaveArg<2>(&preset_details)); - TestConnect(test_address); - - /* Verify presets - expect 1, no duplicates */ - ASSERT_EQ(preset_details.size(), 1u); - auto preset = std::find_if( - preset_details.begin(), preset_details.end(), - [](auto const& preset_info) { return preset_info.preset_index == 5; }); - ASSERT_TRUE(preset != preset_details.end()); - ASSERT_EQ("YourWritablePreset5", preset->preset_name); - ASSERT_TRUE(preset->available); - ASSERT_TRUE(preset->writable); -} - -TEST_F(HasClientTest, test_preset_set_name_invalid_index) { - const RawAddress test_address = GetTestAddress(1); - SetSampleDatabaseHasPresetsNtf(test_address); - TestConnect(test_address); - - EXPECT_CALL(*callbacks, - OnSetPresetNameError(std::variant<RawAddress, int>(test_address), - 0x40, ErrorCode::INVALID_PRESET_INDEX)) - .Times(1); - EXPECT_CALL(gatt_queue, - WriteCharacteristic(1, HasDbBuilder::kPresetsCtpValHdl, _, - GATT_WRITE, _, _)) - .Times(0); - - HasClient::Get()->SetPresetName(test_address, 0x40, "new preset name"); -} - -TEST_F(HasClientTest, test_preset_set_name_non_writable) { - const RawAddress test_address = GetTestAddress(1); - uint16_t test_conn_id = GetTestConnId(test_address); - - SetSampleDatabaseHasPresetsNtf( - test_address, kFeatureBitWritablePresets, - {{ - HasPreset(5, HasPreset::kPropertyAvailable, "YourPreset5"), - HasPreset( - 55, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "YourWritablePreset55"), - }}); - TestConnect(test_address); - - EXPECT_CALL(*callbacks, - OnSetPresetNameError(_, _, ErrorCode::SET_NAME_NOT_ALLOWED)) - .Times(1); - EXPECT_CALL(gatt_queue, - WriteCharacteristic(1, HasDbBuilder::kPresetsCtpValHdl, _, - GATT_WRITE, _, _)) - .Times(0); - - HasClient::Get()->SetPresetName( - test_address, current_peer_presets_.at(test_conn_id).begin()->GetIndex(), - "new preset name"); -} - -TEST_F(HasClientTest, test_preset_set_name_to_long) { - const RawAddress test_address = GetTestAddress(1); - uint16_t test_conn_id = GetTestConnId(test_address); - - SetSampleDatabaseHasPresetsNtf( - test_address, kFeatureBitWritablePresets, - {{HasPreset(5, - HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "YourWritablePreset")}}); - TestConnect(test_address); - - EXPECT_CALL(*callbacks, - OnSetPresetNameError(_, _, ErrorCode::INVALID_PRESET_NAME_LENGTH)) - .Times(1); - EXPECT_CALL(gatt_queue, - WriteCharacteristic(test_conn_id, HasDbBuilder::kPresetsCtpValHdl, - _, GATT_WRITE, _, _)) - .Times(0); - - HasClient::Get()->SetPresetName(test_address, 5, - "this name is more than 40 characters long"); -} - -TEST_F(HasClientTest, test_preset_set_name) { - const RawAddress test_address = GetTestAddress(1); - uint16_t test_conn_id = GetTestConnId(test_address); - - SetSampleDatabaseHasPresetsNtf( - test_address, kFeatureBitWritablePresets, - {{HasPreset(5, - HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "YourWritablePreset5")}}); - - TestConnect(test_address); - - std::vector<uint8_t> value; - EXPECT_CALL(*callbacks, OnSetPresetNameError(_, _, _)).Times(0); - EXPECT_CALL(gatt_queue, - WriteCharacteristic(test_conn_id, HasDbBuilder::kPresetsCtpValHdl, - _, GATT_WRITE, _, _)); - - std::vector<PresetInfo> updated_preset_details; - EXPECT_CALL(*callbacks, - OnPresetInfo(std::variant<RawAddress, int>(test_address), - PresetInfoReason::PRESET_INFO_UPDATE, _)) - .WillOnce(SaveArg<2>(&updated_preset_details)); - HasClient::Get()->SetPresetName(test_address, 5, "new preset name"); - - ASSERT_EQ(1u, updated_preset_details.size()); - ASSERT_EQ(updated_preset_details[0].preset_name, "new preset name"); -} - -TEST_F(HasClientTest, test_preset_group_set_name) { - /* None of these devices support preset syncing */ - const RawAddress test_address1 = GetTestAddress(1); - SetSampleDatabaseHasPresetsNtf( - test_address1, bluetooth::has::kFeatureBitHearingAidTypeBinaural | - bluetooth::has::kFeatureBitWritablePresets); - - const RawAddress test_address2 = GetTestAddress(2); - SetSampleDatabaseHasPresetsNtf( - test_address2, bluetooth::has::kFeatureBitHearingAidTypeBinaural | - bluetooth::has::kFeatureBitWritablePresets); - - TestConnect(test_address1); - TestConnect(test_address2); - - /* Mock the csis group with two devices */ - uint8_t not_synced_group = 13; - ON_CALL(mock_csis_client_module_, GetDeviceList(not_synced_group)) - .WillByDefault( - Return(std::vector<RawAddress>({{test_address1, test_address2}}))); - ON_CALL(mock_csis_client_module_, - GetGroupId(test_address1, ::le_audio::uuid::kCapServiceUuid)) - .WillByDefault(Return(not_synced_group)); - ON_CALL(mock_csis_client_module_, - GetGroupId(test_address2, ::le_audio::uuid::kCapServiceUuid)) - .WillByDefault(Return(not_synced_group)); - - std::vector<PresetInfo> preset_details; - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address1), 55)) - .Times(0); - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address2), 55)) - .Times(0); - - /* This should be a group callback */ - EXPECT_CALL(*callbacks, - OnPresetInfo(std::variant<RawAddress, int>(not_synced_group), - PresetInfoReason::PRESET_INFO_UPDATE, _)) - .Times(1) - .WillOnce(SaveArg<2>(&preset_details)); - - /* No locally synced opcodes support so expect both devices getting writes */ - EXPECT_CALL(gatt_queue, WriteCharacteristic(GetTestConnId(test_address1), - HasDbBuilder::kPresetsCtpValHdl, - _, GATT_WRITE, _, _)) - .Times(1); - EXPECT_CALL(gatt_queue, WriteCharacteristic(GetTestConnId(test_address2), - HasDbBuilder::kPresetsCtpValHdl, - _, GATT_WRITE, _, _)) - .Times(1); - - HasClient::Get()->SetPresetName(not_synced_group, 55, "new preset name"); - ASSERT_EQ(preset_details.size(), 1u); - ASSERT_EQ(preset_details[0].preset_name, "new preset name"); - ASSERT_EQ(preset_details[0].preset_index, 55); -} - -TEST_F(HasClientTest, test_multiple_presets_get_name) { - const RawAddress test_address = GetTestAddress(1); - SetSampleDatabaseHasPresetsNtf( - test_address, kFeatureBitWritablePresets, - {{ - HasPreset( - 5, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "YourWritablePreset5"), - HasPreset(55, HasPreset::kPropertyAvailable, "YourPreset55"), - HasPreset(99, 0, "YourPreset99"), - }}); - - std::vector<PresetInfo> preset_details; - - EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _)); - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)); - EXPECT_CALL(*callbacks, OnPresetInfo(_, PresetInfoReason::ALL_PRESET_INFO, _)) - .WillOnce(SaveArg<2>(&preset_details)); - TestConnect(test_address); - - /* Get each preset info individually */ - for (auto const& preset : preset_details) { - std::vector<PresetInfo> new_preset_details; - - EXPECT_CALL(*callbacks, - OnPresetInfo(std::variant<RawAddress, int>(test_address), - PresetInfoReason::PRESET_INFO_REQUEST_RESPONSE, _)) - .Times(1) - .WillOnce(SaveArg<2>(&new_preset_details)); - HasClient::Get()->GetPresetInfo(test_address, preset.preset_index); - - Mock::VerifyAndClearExpectations(&*callbacks); - ASSERT_EQ(1u, new_preset_details.size()); - ASSERT_EQ(preset.preset_index, new_preset_details[0].preset_index); - ASSERT_EQ(preset.preset_name, new_preset_details[0].preset_name); - ASSERT_EQ(preset.writable, new_preset_details[0].writable); - ASSERT_EQ(preset.available, new_preset_details[0].available); - } -} - -TEST_F(HasClientTest, test_presets_get_name_invalid_index) { - const RawAddress test_address = GetTestAddress(1); - SetSampleDatabaseHasPresetsNtf(test_address); - TestConnect(test_address); - - EXPECT_CALL(*callbacks, - OnPresetInfoError(std::variant<RawAddress, int>(test_address), - 128, ErrorCode::INVALID_PRESET_INDEX)); - HasClient::Get()->GetPresetInfo(test_address, 128); - - EXPECT_CALL(*callbacks, - OnPresetInfoError(std::variant<RawAddress, int>(test_address), 0, - ErrorCode::INVALID_PRESET_INDEX)); - HasClient::Get()->GetPresetInfo(test_address, 0); -} - -TEST_F(HasClientTest, test_presets_changed_generic_update_no_add_or_delete) { - const RawAddress test_address = GetTestAddress(1); - uint16_t test_conn_id = GetTestConnId(test_address); - - std::set<HasPreset, HasPreset::ComparatorDesc> presets = {{ - HasPreset(1, HasPreset::kPropertyAvailable, "Universal"), - HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "Preset2"), - HasPreset(4, HasPreset::kPropertyAvailable, "Preset4"), - HasPreset(7, HasPreset::kPropertyAvailable, "Preset7"), - }}; - SetSampleDatabaseHasPresetsNtf( - test_address, - bluetooth::has::kFeatureBitHearingAidTypeBanded | - bluetooth::has::kFeatureBitDynamicPresets | - bluetooth::has::kFeatureBitWritablePresets, - presets); - - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)); - TestConnect(test_address); - - std::vector<PresetInfo> preset_details; - EXPECT_CALL(*callbacks, - OnPresetInfo(std::variant<RawAddress, int>(test_address), - PresetInfoReason::PRESET_INFO_UPDATE, _)) - .Times(1) - .WillOnce(SaveArg<2>(&preset_details)); - - /* Inject generic update on the first preset */ - auto preset_index = 2; - auto new_test_preset = HasPreset(preset_index, 0, "props new name"); - ASSERT_NE(*current_peer_presets_.at(test_conn_id).find(preset_index), - new_test_preset); - - InjectPresetChanged(test_conn_id, test_address, false, new_test_preset, - 1 /* prev_index */, - ::le_audio::has::PresetCtpChangeId::PRESET_GENERIC_UPDATE, - true /* is_last */); - - /* Verify received preset info update on the 2nd preset */ - ASSERT_EQ(1u, preset_details.size()); - ASSERT_EQ(new_test_preset.GetIndex(), preset_details[0].preset_index); - ASSERT_EQ(new_test_preset.IsAvailable(), preset_details[0].available); - ASSERT_EQ(new_test_preset.IsWritable(), preset_details[0].writable); - ASSERT_EQ(new_test_preset.GetName(), preset_details[0].preset_name); -} - -TEST_F(HasClientTest, test_presets_changed_generic_update_add_and_delete) { - const RawAddress test_address = GetTestAddress(1); - uint16_t test_conn_id = GetTestConnId(test_address); - - std::set<HasPreset, HasPreset::ComparatorDesc> presets = {{ - HasPreset(1, HasPreset::kPropertyAvailable, "Universal"), - HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "Preset2"), - HasPreset(4, HasPreset::kPropertyAvailable, "Preset4"), - HasPreset(5, HasPreset::kPropertyAvailable, "Preset5"), - HasPreset(32, HasPreset::kPropertyAvailable, "Preset32"), - }}; - SetSampleDatabaseHasPresetsNtf( - test_address, - bluetooth::has::kFeatureBitHearingAidTypeBanded | - bluetooth::has::kFeatureBitWritablePresets, - presets); - - std::vector<PresetInfo> preset_details; - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)); - EXPECT_CALL(*callbacks, - OnPresetInfo(std::variant<RawAddress, int>(test_address), - PresetInfoReason::ALL_PRESET_INFO, _)) - .Times(1) - .WillOnce(SaveArg<2>(&preset_details)); - TestConnect(test_address); - - /* Expect more OnPresetInfo call */ - std::vector<PresetInfo> updated_preset_details; - EXPECT_CALL(*callbacks, - OnPresetInfo(std::variant<RawAddress, int>(test_address), - PresetInfoReason::PRESET_INFO_UPDATE, _)) - .Times(1) - .WillOnce(SaveArg<2>(&updated_preset_details)); - - /* Expect more OnPresetInfo call */ - std::vector<PresetInfo> deleted_preset_details; - EXPECT_CALL(*callbacks, - OnPresetInfo(std::variant<RawAddress, int>(test_address), - PresetInfoReason::PRESET_DELETED, _)) - .Times(1) - .WillOnce(SaveArg<2>(&deleted_preset_details)); - - /* Inject generic updates */ - /* First event replaces all the existing presets from 1 to 8 with preset 8 - */ - auto new_test_preset1 = - HasPreset(8, HasPreset::kPropertyAvailable, "props new name9"); - InjectPresetChanged(test_conn_id, test_address, false, new_test_preset1, - 1 /* prev_index */, - ::le_audio::has::PresetCtpChangeId::PRESET_GENERIC_UPDATE, - false /* is_last */); - - /* Second event adds preset 9 to the already existing presets 1 and 8 */ - auto new_test_preset2 = - HasPreset(9, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "props new name11"); - InjectPresetChanged(test_conn_id, test_address, false, new_test_preset2, - 8 /* prev_index */, - ::le_audio::has::PresetCtpChangeId::PRESET_GENERIC_UPDATE, - true /* is_last */); - - /* Verify received preset info - expect presets 1, 32 unchanged, 8, 9 - * updated, and 2, 4, 5 deleted. - */ - ASSERT_EQ(2u, updated_preset_details.size()); - ASSERT_EQ(new_test_preset1.GetIndex(), - updated_preset_details[0].preset_index); - ASSERT_EQ(new_test_preset1.IsAvailable(), - updated_preset_details[0].available); - ASSERT_EQ(new_test_preset1.IsWritable(), updated_preset_details[0].writable); - ASSERT_EQ(new_test_preset1.GetName(), updated_preset_details[0].preset_name); - ASSERT_EQ(new_test_preset2.GetIndex(), - updated_preset_details[1].preset_index); - ASSERT_EQ(new_test_preset2.IsAvailable(), - updated_preset_details[1].available); - ASSERT_EQ(new_test_preset2.IsWritable(), updated_preset_details[1].writable); - ASSERT_EQ(new_test_preset2.GetName(), updated_preset_details[1].preset_name); - - ASSERT_EQ(3u, deleted_preset_details.size()); - ASSERT_EQ(2, deleted_preset_details[0].preset_index); - ASSERT_EQ(4, deleted_preset_details[1].preset_index); - ASSERT_EQ(5, deleted_preset_details[2].preset_index); -} - -TEST_F(HasClientTest, test_presets_changed_deleted) { - const RawAddress test_address = GetTestAddress(1); - uint16_t test_conn_id = GetTestConnId(test_address); - - std::set<HasPreset, HasPreset::ComparatorDesc> presets = {{ - HasPreset(1, HasPreset::kPropertyAvailable, "Universal"), - HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "Preset2"), - }}; - SetSampleDatabaseHasPresetsNtf( - test_address, - bluetooth::has::kFeatureBitHearingAidTypeBanded | - bluetooth::has::kFeatureBitWritablePresets | - bluetooth::has::kFeatureBitDynamicPresets, - presets); - - std::vector<PresetInfo> preset_details; - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)); - EXPECT_CALL(*callbacks, - OnPresetInfo(std::variant<RawAddress, int>(test_address), - PresetInfoReason::ALL_PRESET_INFO, _)) - .Times(1) - .WillOnce(SaveArg<2>(&preset_details)); - TestConnect(test_address); - - /* Expect second OnPresetInfo call */ - std::vector<PresetInfo> deleted_preset_details; - EXPECT_CALL(*callbacks, - OnPresetInfo(std::variant<RawAddress, int>(test_address), - PresetInfoReason::PRESET_DELETED, _)) - .Times(1) - .WillOnce(SaveArg<2>(&deleted_preset_details)); - - /* Inject preset deletion of index 2 */ - auto deleted_index = preset_details[1].preset_index; - InjectPresetChanged(test_conn_id, test_address, false, - *presets.find(deleted_index), 0 /* prev_index */, - ::le_audio::has::PresetCtpChangeId::PRESET_DELETED, - true /* is_last */); - - ASSERT_EQ(2u, preset_details.size()); - ASSERT_EQ(1u, deleted_preset_details.size()); - ASSERT_EQ(preset_details[1].preset_index, - deleted_preset_details[0].preset_index); - ASSERT_EQ(preset_details[1].writable, deleted_preset_details[0].writable); - ASSERT_EQ(preset_details[1].available, deleted_preset_details[0].available); - ASSERT_EQ(preset_details[1].preset_name, - deleted_preset_details[0].preset_name); -} - -TEST_F(HasClientTest, test_presets_changed_available) { - const RawAddress test_address = GetTestAddress(1); - uint16_t test_conn_id = GetTestConnId(test_address); - - std::set<HasPreset, HasPreset::ComparatorDesc> presets = {{ - HasPreset(1, 0, "Universal"), - HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "Preset2"), - }}; - SetSampleDatabaseHasPresetsNtf( - test_address, - bluetooth::has::kFeatureBitHearingAidTypeBanded | - bluetooth::has::kFeatureBitWritablePresets | - bluetooth::has::kFeatureBitDynamicPresets, - presets); - - std::vector<PresetInfo> preset_details; - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)); - EXPECT_CALL(*callbacks, - OnPresetInfo(std::variant<RawAddress, int>(test_address), - PresetInfoReason::ALL_PRESET_INFO, _)) - .Times(1) - .WillOnce(SaveArg<2>(&preset_details)); - TestConnect(test_address); - - /* Expect second OnPresetInfo call */ - std::vector<PresetInfo> changed_preset_details; - EXPECT_CALL(*callbacks, - OnPresetInfo(std::variant<RawAddress, int>(test_address), - PresetInfoReason::PRESET_AVAILABILITY_CHANGED, _)) - .Times(1) - .WillOnce(SaveArg<2>(&changed_preset_details)); - - /* Inject preset deletion of index 2 */ - auto changed_index = preset_details[0].preset_index; - InjectPresetChanged(test_conn_id, test_address, false, - *presets.find(changed_index), 0 /* prev_index */, - ::le_audio::has::PresetCtpChangeId::PRESET_AVAILABLE, - true /* is_last */); - - ASSERT_EQ(2u, preset_details.size()); - ASSERT_EQ(1u, changed_preset_details.size()); - ASSERT_EQ(preset_details[0].preset_index, - changed_preset_details[0].preset_index); - ASSERT_EQ(preset_details[0].writable, changed_preset_details[0].writable); - ASSERT_EQ(preset_details[0].preset_name, - changed_preset_details[0].preset_name); - /* This field should have changed */ - ASSERT_NE(preset_details[0].available, changed_preset_details[0].available); - ASSERT_TRUE(changed_preset_details[0].available); -} - -TEST_F(HasClientTest, test_presets_changed_unavailable) { - const RawAddress test_address = GetTestAddress(1); - uint16_t test_conn_id = GetTestConnId(test_address); - - std::set<HasPreset, HasPreset::ComparatorDesc> presets = {{ - HasPreset(1, HasPreset::kPropertyAvailable, "Universal"), - HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "Preset2"), - }}; - SetSampleDatabaseHasPresetsNtf( - test_address, - bluetooth::has::kFeatureBitHearingAidTypeBanded | - bluetooth::has::kFeatureBitWritablePresets | - bluetooth::has::kFeatureBitDynamicPresets, - presets); - - std::vector<PresetInfo> preset_details; - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)); - EXPECT_CALL(*callbacks, - OnPresetInfo(std::variant<RawAddress, int>(test_address), - PresetInfoReason::ALL_PRESET_INFO, _)) - .Times(1) - .WillOnce(SaveArg<2>(&preset_details)); - TestConnect(test_address); - - /* Expect second OnPresetInfo call */ - std::vector<PresetInfo> changed_preset_details; - EXPECT_CALL(*callbacks, - OnPresetInfo(std::variant<RawAddress, int>(test_address), - PresetInfoReason::PRESET_AVAILABILITY_CHANGED, _)) - .Times(1) - .WillOnce(SaveArg<2>(&changed_preset_details)); - - /* Inject preset deletion of index 2 */ - auto changed_index = preset_details[0].preset_index; - InjectPresetChanged(test_conn_id, test_address, false, - *presets.find(changed_index), 0 /* prev_index */, - ::le_audio::has::PresetCtpChangeId::PRESET_UNAVAILABLE, - true /* is_last */); - - ASSERT_EQ(2u, preset_details.size()); - ASSERT_EQ(1u, changed_preset_details.size()); - ASSERT_EQ(preset_details[0].preset_index, - changed_preset_details[0].preset_index); - ASSERT_EQ(preset_details[0].writable, changed_preset_details[0].writable); - ASSERT_EQ(preset_details[0].preset_name, - changed_preset_details[0].preset_name); - /* This field should have changed */ - ASSERT_NE(preset_details[0].available, changed_preset_details[0].available); - ASSERT_FALSE(changed_preset_details[0].available); -} - -TEST_F(HasClientTest, test_select_preset_valid) { - const RawAddress test_address = GetTestAddress(1); - SetSampleDatabaseHasPresetsNtf(test_address); - - uint8_t active_preset_index = 0; - std::vector<PresetInfo> preset_details; - - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)); - EXPECT_CALL(*callbacks, - OnPresetInfo(std::variant<RawAddress, int>(test_address), - PresetInfoReason::ALL_PRESET_INFO, _)) - .Times(1) - .WillOnce(SaveArg<2>(&preset_details)); - EXPECT_CALL(*callbacks, OnActivePresetSelected(_, _)) - .WillOnce(SaveArg<1>(&active_preset_index)); - TestConnect(test_address); - - ASSERT_TRUE(preset_details.size() > 1); - ASSERT_EQ(preset_details.front().preset_index, active_preset_index); - - uint8_t new_active_preset_index = 0; - EXPECT_CALL(*callbacks, OnActivePresetSelected(_, _)) - .WillOnce(SaveArg<1>(&new_active_preset_index)); - - HasClient::Get()->SelectActivePreset(test_address, - preset_details.back().preset_index); - Mock::VerifyAndClearExpectations(&*callbacks); - - ASSERT_NE(active_preset_index, new_active_preset_index); - ASSERT_EQ(preset_details.back().preset_index, new_active_preset_index); -} - -TEST_F(HasClientTest, test_select_group_preset_invalid_group) { - const RawAddress test_address1 = GetTestAddress(1); - SetSampleDatabaseHasPresetsNtf(test_address1); - - const RawAddress test_address2 = GetTestAddress(2); - SetSampleDatabaseHasPresetsNtf(test_address2); - - TestConnect(test_address1); - TestConnect(test_address2); - - /* Mock the csis group with no devices */ - uint8_t unlucky_group = 13; - ON_CALL(mock_csis_client_module_, GetDeviceList(unlucky_group)) - .WillByDefault(Return(std::vector<RawAddress>())); - - EXPECT_CALL(*callbacks, OnActivePresetSelectError( - std::variant<RawAddress, int>(unlucky_group), - ErrorCode::OPERATION_NOT_POSSIBLE)) - .Times(1); - - HasClient::Get()->SelectActivePreset(unlucky_group, 6); -} - -TEST_F(HasClientTest, test_select_group_preset_valid_no_preset_sync_supported) { - /* None of these devices support preset syncing */ - const RawAddress test_address1 = GetTestAddress(1); - SetSampleDatabaseHasPresetsNtf( - test_address1, bluetooth::has::kFeatureBitHearingAidTypeBinaural); - - const RawAddress test_address2 = GetTestAddress(2); - SetSampleDatabaseHasPresetsNtf( - test_address2, bluetooth::has::kFeatureBitHearingAidTypeBinaural); - - TestConnect(test_address1); - TestConnect(test_address2); - - /* Mock the csis group with two devices */ - uint8_t not_synced_group = 13; - ON_CALL(mock_csis_client_module_, GetDeviceList(not_synced_group)) - .WillByDefault( - Return(std::vector<RawAddress>({{test_address1, test_address2}}))); - ON_CALL(mock_csis_client_module_, - GetGroupId(test_address1, ::le_audio::uuid::kCapServiceUuid)) - .WillByDefault(Return(not_synced_group)); - ON_CALL(mock_csis_client_module_, - GetGroupId(test_address2, ::le_audio::uuid::kCapServiceUuid)) - .WillByDefault(Return(not_synced_group)); - - uint8_t group_active_preset_index = 0; - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address1), 55)) - .Times(0); - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address2), 55)) - .Times(0); - EXPECT_CALL(*callbacks, - OnActivePresetSelected( - std::variant<RawAddress, int>(not_synced_group), _)) - .WillOnce(SaveArg<1>(&group_active_preset_index)); - - /* No locally synced opcodes support so expect both devices getting writes */ - EXPECT_CALL(gatt_queue, WriteCharacteristic(GetTestConnId(test_address1), - HasDbBuilder::kPresetsCtpValHdl, - _, GATT_WRITE, _, _)) - .Times(1); - EXPECT_CALL(gatt_queue, WriteCharacteristic(GetTestConnId(test_address2), - HasDbBuilder::kPresetsCtpValHdl, - _, GATT_WRITE, _, _)) - .Times(1); - - HasClient::Get()->SelectActivePreset(not_synced_group, 55); - ASSERT_EQ(group_active_preset_index, 55); -} - -TEST_F(HasClientTest, test_select_group_preset_valid_preset_sync_supported) { - /* Only one of these devices support preset syncing */ - const RawAddress test_address1 = GetTestAddress(1); - uint16_t test_conn_id1 = GetTestConnId(test_address1); - SetSampleDatabaseHasPresetsNtf( - test_address1, bluetooth::has::kFeatureBitHearingAidTypeBinaural); - - const RawAddress test_address2 = GetTestAddress(2); - uint16_t test_conn_id2 = GetTestConnId(test_address2); - SetSampleDatabaseHasPresetsNtf( - test_address2, - bluetooth::has::kFeatureBitHearingAidTypeBinaural | - bluetooth::has::kFeatureBitPresetSynchronizationSupported); - - uint8_t active_preset_index1 = 0; - uint8_t active_preset_index2 = 0; - - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address1), _)) - .WillOnce(SaveArg<1>(&active_preset_index1)); - TestConnect(test_address1); - - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address2), _)) - .WillOnce(SaveArg<1>(&active_preset_index2)); - TestConnect(test_address2); - - /* Mock the csis group with two devices */ - uint8_t synced_group = 13; - ON_CALL(mock_csis_client_module_, GetDeviceList(synced_group)) - .WillByDefault( - Return(std::vector<RawAddress>({{test_address1, test_address2}}))); - ON_CALL(mock_csis_client_module_, - GetGroupId(test_address1, ::le_audio::uuid::kCapServiceUuid)) - .WillByDefault(Return(synced_group)); - ON_CALL(mock_csis_client_module_, - GetGroupId(test_address2, ::le_audio::uuid::kCapServiceUuid)) - .WillByDefault(Return(synced_group)); - - EXPECT_CALL(*callbacks, OnActivePresetSelectError( - _, ErrorCode::GROUP_OPERATION_NOT_SUPPORTED)) - .Times(0); - - /* Expect callback from the group but not from the devices */ - uint8_t group_active_preset_index = 0; - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address1), _)) - .Times(0); - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address2), _)) - .Times(0); - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(synced_group), _)) - .WillOnce(SaveArg<1>(&group_active_preset_index)); - - /* Expect Ctp write on on this device which forwards operation to the other */ - EXPECT_CALL(gatt_queue, WriteCharacteristic(test_conn_id1, - HasDbBuilder::kPresetsCtpValHdl, - _, GATT_WRITE, _, _)) - .Times(0); - EXPECT_CALL(gatt_queue, WriteCharacteristic(test_conn_id2, - HasDbBuilder::kPresetsCtpValHdl, - _, GATT_WRITE, _, _)) - .Times(1); - - HasClient::Get()->SelectActivePreset(synced_group, 55); - ASSERT_EQ(group_active_preset_index, 55); -} - -TEST_F(HasClientTest, test_select_preset_invalid) { - const RawAddress test_address = GetTestAddress(1); - uint16_t test_conn_id = GetTestConnId(test_address); - - std::set<HasPreset, HasPreset::ComparatorDesc> presets = {{ - HasPreset(1, HasPreset::kPropertyAvailable, "Universal"), - HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "Preset2"), - }}; - SetSampleDatabaseHasPresetsNtf( - test_address, - bluetooth::has::kFeatureBitHearingAidTypeBanded | - bluetooth::has::kFeatureBitWritablePresets | - bluetooth::has::kFeatureBitDynamicPresets, - presets); - - uint8_t active_preset_index = 0; - std::vector<PresetInfo> preset_details; - - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)); - EXPECT_CALL(*callbacks, - OnPresetInfo(std::variant<RawAddress, int>(test_address), - PresetInfoReason::ALL_PRESET_INFO, _)) - .Times(1) - .WillOnce(SaveArg<2>(&preset_details)); - EXPECT_CALL(*callbacks, OnActivePresetSelected(_, _)) - .WillOnce(SaveArg<1>(&active_preset_index)); - TestConnect(test_address); - - ASSERT_TRUE(preset_details.size() > 1); - ASSERT_EQ(preset_details.front().preset_index, active_preset_index); - - /* Inject preset deletion of index 2 */ - auto deleted_index = preset_details[1].preset_index; - InjectPresetChanged(test_conn_id, test_address, false, - *presets.find(deleted_index), 0 /* prev_index */, - ::le_audio::has::PresetCtpChangeId::PRESET_DELETED, - true /* is_last */); - - EXPECT_CALL(*callbacks, OnActivePresetSelectError( - std::variant<RawAddress, int>(test_address), - ErrorCode::INVALID_PRESET_INDEX)) - .Times(1); - - /* Check if preset was actually deleted - try setting it as an active one */ - HasClient::Get()->SelectActivePreset(test_address, - preset_details[1].preset_index); -} - -TEST_F(HasClientTest, test_select_preset_next) { - const RawAddress test_address = GetTestAddress(1); - - std::set<HasPreset, HasPreset::ComparatorDesc> presets = {{ - HasPreset(1, HasPreset::kPropertyAvailable, "Universal"), - HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "Preset2"), - }}; - SetSampleDatabaseHasPresetsNtf( - test_address, - bluetooth::has::kFeatureBitHearingAidTypeBanded | - bluetooth::has::kFeatureBitWritablePresets | - bluetooth::has::kFeatureBitDynamicPresets, - presets); - - uint8_t active_preset_index = 0; - std::vector<PresetInfo> preset_details; - - EXPECT_CALL(*callbacks, - OnPresetInfo(std::variant<RawAddress, int>(test_address), - PresetInfoReason::ALL_PRESET_INFO, _)) - .Times(1) - .WillOnce(SaveArg<2>(&preset_details)); - EXPECT_CALL(*callbacks, OnActivePresetSelected(_, _)) - .WillOnce(SaveArg<1>(&active_preset_index)); - TestConnect(test_address); - - ASSERT_TRUE(preset_details.size() > 1); - ASSERT_EQ(1, active_preset_index); - - /* Verify active preset change */ - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address), 2)); - HasClient::Get()->NextActivePreset(test_address); -} - -TEST_F(HasClientTest, test_select_group_preset_next_no_preset_sync_supported) { - /* None of these devices support preset syncing */ - const RawAddress test_address1 = GetTestAddress(1); - SetSampleDatabaseHasPresetsNtf( - test_address1, bluetooth::has::kFeatureBitHearingAidTypeBinaural); - - const RawAddress test_address2 = GetTestAddress(2); - SetSampleDatabaseHasPresetsNtf( - test_address2, bluetooth::has::kFeatureBitHearingAidTypeBinaural); - - TestConnect(test_address1); - TestConnect(test_address2); - - /* Mock the csis group with two devices */ - uint8_t not_synced_group = 13; - ON_CALL(mock_csis_client_module_, GetDeviceList(not_synced_group)) - .WillByDefault( - Return(std::vector<RawAddress>({{test_address1, test_address2}}))); - ON_CALL(mock_csis_client_module_, - GetGroupId(test_address1, ::le_audio::uuid::kCapServiceUuid)) - .WillByDefault(Return(not_synced_group)); - ON_CALL(mock_csis_client_module_, - GetGroupId(test_address2, ::le_audio::uuid::kCapServiceUuid)) - .WillByDefault(Return(not_synced_group)); - - uint8_t group_active_preset_index = 0; - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address1), 55)) - .Times(0); - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address2), 55)) - .Times(0); - EXPECT_CALL(*callbacks, - OnActivePresetSelected( - std::variant<RawAddress, int>(not_synced_group), _)) - .WillOnce(SaveArg<1>(&group_active_preset_index)); - - /* No locally synced opcodes support so expect both devices getting writes */ - EXPECT_CALL(gatt_queue, WriteCharacteristic(GetTestConnId(test_address1), - HasDbBuilder::kPresetsCtpValHdl, - _, GATT_WRITE, _, _)) - .Times(1); - EXPECT_CALL(gatt_queue, WriteCharacteristic(GetTestConnId(test_address2), - HasDbBuilder::kPresetsCtpValHdl, - _, GATT_WRITE, _, _)) - .Times(1); - - HasClient::Get()->NextActivePreset(not_synced_group); - ASSERT_EQ(group_active_preset_index, 55); -} - -TEST_F(HasClientTest, test_select_group_preset_next_preset_sync_supported) { - /* Only one of these devices support preset syncing */ - const RawAddress test_address1 = GetTestAddress(1); - uint16_t test_conn_id1 = GetTestConnId(test_address1); - SetSampleDatabaseHasPresetsNtf( - test_address1, bluetooth::has::kFeatureBitHearingAidTypeBinaural); - - const RawAddress test_address2 = GetTestAddress(2); - uint16_t test_conn_id2 = GetTestConnId(test_address2); - SetSampleDatabaseHasPresetsNtf( - test_address2, - bluetooth::has::kFeatureBitHearingAidTypeBinaural | - bluetooth::has::kFeatureBitPresetSynchronizationSupported); - - uint8_t active_preset_index1 = 0; - uint8_t active_preset_index2 = 0; - - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address1), _)) - .WillOnce(SaveArg<1>(&active_preset_index1)); - TestConnect(test_address1); - - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address2), _)) - .WillOnce(SaveArg<1>(&active_preset_index2)); - TestConnect(test_address2); - - /* Mock the csis group with two devices */ - uint8_t synced_group = 13; - ON_CALL(mock_csis_client_module_, GetDeviceList(synced_group)) - .WillByDefault( - Return(std::vector<RawAddress>({{test_address1, test_address2}}))); - ON_CALL(mock_csis_client_module_, - GetGroupId(test_address1, ::le_audio::uuid::kCapServiceUuid)) - .WillByDefault(Return(synced_group)); - ON_CALL(mock_csis_client_module_, - GetGroupId(test_address2, ::le_audio::uuid::kCapServiceUuid)) - .WillByDefault(Return(synced_group)); - - EXPECT_CALL(*callbacks, OnActivePresetSelectError( - _, ErrorCode::GROUP_OPERATION_NOT_SUPPORTED)) - .Times(0); - - /* Expect callback from the group but not from the devices */ - uint8_t group_active_preset_index = 0; - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address1), _)) - .Times(0); - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address2), _)) - .Times(0); - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(synced_group), _)) - .WillOnce(SaveArg<1>(&group_active_preset_index)); - - /* Expect Ctp write on on this device which forwards operation to the other */ - EXPECT_CALL(gatt_queue, WriteCharacteristic(test_conn_id1, - HasDbBuilder::kPresetsCtpValHdl, - _, GATT_WRITE, _, _)) - .Times(0); - EXPECT_CALL(gatt_queue, WriteCharacteristic(test_conn_id2, - HasDbBuilder::kPresetsCtpValHdl, - _, GATT_WRITE, _, _)) - .Times(1); - - HasClient::Get()->NextActivePreset(synced_group); - ASSERT_EQ(group_active_preset_index, 55); -} - -TEST_F(HasClientTest, test_select_preset_prev) { - const RawAddress test_address = GetTestAddress(1); - - std::set<HasPreset, HasPreset::ComparatorDesc> presets = {{ - HasPreset(1, HasPreset::kPropertyAvailable, "Universal"), - HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "Preset2"), - }}; - SetSampleDatabaseHasPresetsNtf( - test_address, - bluetooth::has::kFeatureBitHearingAidTypeBanded | - bluetooth::has::kFeatureBitWritablePresets | - bluetooth::has::kFeatureBitDynamicPresets, - presets); - - uint8_t active_preset_index = 0; - std::vector<PresetInfo> preset_details; - - EXPECT_CALL(*callbacks, - OnPresetInfo(std::variant<RawAddress, int>(test_address), - PresetInfoReason::ALL_PRESET_INFO, _)) - .Times(1) - .WillOnce(SaveArg<2>(&preset_details)); - ON_CALL(*callbacks, OnActivePresetSelected(_, _)) - .WillByDefault(SaveArg<1>(&active_preset_index)); - TestConnect(test_address); - - HasClient::Get()->SelectActivePreset(test_address, 2); - ASSERT_TRUE(preset_details.size() > 1); - ASSERT_EQ(2, active_preset_index); - - /* Verify active preset change */ - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address), 1)); - HasClient::Get()->PreviousActivePreset(test_address); -} - -TEST_F(HasClientTest, test_select_group_preset_prev_no_preset_sync_supported) { - /* None of these devices support preset syncing */ - const RawAddress test_address1 = GetTestAddress(1); - SetSampleDatabaseHasPresetsNtf( - test_address1, bluetooth::has::kFeatureBitHearingAidTypeBinaural); - - const RawAddress test_address2 = GetTestAddress(2); - SetSampleDatabaseHasPresetsNtf( - test_address2, bluetooth::has::kFeatureBitHearingAidTypeBinaural); - - TestConnect(test_address1); - TestConnect(test_address2); - - /* Mock the csis group with two devices */ - uint8_t not_synced_group = 13; - ON_CALL(mock_csis_client_module_, GetDeviceList(not_synced_group)) - .WillByDefault( - Return(std::vector<RawAddress>({{test_address1, test_address2}}))); - ON_CALL(mock_csis_client_module_, - GetGroupId(test_address1, ::le_audio::uuid::kCapServiceUuid)) - .WillByDefault(Return(not_synced_group)); - ON_CALL(mock_csis_client_module_, - GetGroupId(test_address2, ::le_audio::uuid::kCapServiceUuid)) - .WillByDefault(Return(not_synced_group)); - - uint8_t group_active_preset_index = 0; - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address1), 55)) - .Times(0); - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address2), 55)) - .Times(0); - EXPECT_CALL(*callbacks, - OnActivePresetSelected( - std::variant<RawAddress, int>(not_synced_group), _)) - .WillOnce(SaveArg<1>(&group_active_preset_index)); - - /* No locally synced opcodes support so expect both devices getting writes */ - EXPECT_CALL(gatt_queue, WriteCharacteristic(GetTestConnId(test_address1), - HasDbBuilder::kPresetsCtpValHdl, - _, GATT_WRITE, _, _)) - .Times(1); - EXPECT_CALL(gatt_queue, WriteCharacteristic(GetTestConnId(test_address2), - HasDbBuilder::kPresetsCtpValHdl, - _, GATT_WRITE, _, _)) - .Times(1); - - HasClient::Get()->PreviousActivePreset(not_synced_group); - ASSERT_EQ(group_active_preset_index, 55); -} - -TEST_F(HasClientTest, test_select_group_preset_prev_preset_sync_supported) { - /* Only one of these devices support preset syncing */ - const RawAddress test_address1 = GetTestAddress(1); - uint16_t test_conn_id1 = GetTestConnId(test_address1); - SetSampleDatabaseHasPresetsNtf( - test_address1, bluetooth::has::kFeatureBitHearingAidTypeBinaural); - - const RawAddress test_address2 = GetTestAddress(2); - uint16_t test_conn_id2 = GetTestConnId(test_address2); - SetSampleDatabaseHasPresetsNtf( - test_address2, - bluetooth::has::kFeatureBitHearingAidTypeBinaural | - bluetooth::has::kFeatureBitPresetSynchronizationSupported); - - uint8_t active_preset_index1 = 0; - uint8_t active_preset_index2 = 0; - - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address1), _)) - .WillOnce(SaveArg<1>(&active_preset_index1)); - TestConnect(test_address1); - - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address2), _)) - .WillOnce(SaveArg<1>(&active_preset_index2)); - TestConnect(test_address2); - - /* Mock the csis group with two devices */ - uint8_t synced_group = 13; - ON_CALL(mock_csis_client_module_, GetDeviceList(synced_group)) - .WillByDefault( - Return(std::vector<RawAddress>({{test_address1, test_address2}}))); - ON_CALL(mock_csis_client_module_, - GetGroupId(test_address1, ::le_audio::uuid::kCapServiceUuid)) - .WillByDefault(Return(synced_group)); - ON_CALL(mock_csis_client_module_, - GetGroupId(test_address2, ::le_audio::uuid::kCapServiceUuid)) - .WillByDefault(Return(synced_group)); - - EXPECT_CALL(*callbacks, OnActivePresetSelectError( - _, ErrorCode::GROUP_OPERATION_NOT_SUPPORTED)) - .Times(0); - - /* Expect callback from the group but not from the devices */ - uint8_t group_active_preset_index = 0; - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address1), _)) - .Times(0); - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(test_address2), _)) - .Times(0); - EXPECT_CALL(*callbacks, OnActivePresetSelected( - std::variant<RawAddress, int>(synced_group), _)) - .WillOnce(SaveArg<1>(&group_active_preset_index)); - - /* Expect Ctp write on on this device which forwards operation to the other */ - EXPECT_CALL(gatt_queue, WriteCharacteristic(test_conn_id1, - HasDbBuilder::kPresetsCtpValHdl, - _, GATT_WRITE, _, _)) - .Times(0); - EXPECT_CALL(gatt_queue, WriteCharacteristic(test_conn_id2, - HasDbBuilder::kPresetsCtpValHdl, - _, GATT_WRITE, _, _)) - .Times(1); - - HasClient::Get()->PreviousActivePreset(synced_group); - ASSERT_EQ(group_active_preset_index, 55); -} - -TEST_F(HasClientTest, test_select_has_no_presets) { - const RawAddress test_address = GetTestAddress(1); - SetSampleDatabaseHasNoPresetsFlagsOnly(test_address); - - EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _)).Times(1); - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)) - .Times(1); - TestConnect(test_address); - - /* Test this not so useful service */ - EXPECT_CALL(*callbacks, - OnActivePresetSelectError(_, ErrorCode::OPERATION_NOT_SUPPORTED)) - .Times(3); - - HasClient::Get()->SelectActivePreset(test_address, 0x01); - HasClient::Get()->NextActivePreset(test_address); - HasClient::Get()->PreviousActivePreset(test_address); -} - -static int GetSocketBufferSize(int sockfd) { - int socket_buffer_size; - socklen_t optlen = sizeof(socket_buffer_size); - getsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (void*)&socket_buffer_size, - &optlen); - return socket_buffer_size; -} - -bool SimpleJsonValidator(int fd, int* dumpsys_byte_cnt) { - std::ostringstream ss; - - char buf{0}; - bool within_double_quotes{false}; - int left_bracket{0}, right_bracket{0}; - int left_sq_bracket{0}, right_sq_bracket{0}; - while (read(fd, &buf, 1) != -1) { - switch (buf) { - (*dumpsys_byte_cnt)++; - case '"': - within_double_quotes = !within_double_quotes; - break; - case '{': - if (!within_double_quotes) { - left_bracket++; - } - break; - case '}': - if (!within_double_quotes) { - right_bracket++; - } - break; - case '[': - if (!within_double_quotes) { - left_sq_bracket++; - } - break; - case ']': - if (!within_double_quotes) { - right_sq_bracket++; - } - break; - default: - break; - } - ss << buf; - } - LOG(ERROR) << __func__ << ": " << ss.str(); - return (left_bracket == right_bracket) && - (left_sq_bracket == right_sq_bracket); -} - -TEST_F(HasClientTest, test_dumpsys) { - const RawAddress test_address = GetTestAddress(1); - - std::set<HasPreset, HasPreset::ComparatorDesc> presets = {{ - HasPreset(1, HasPreset::kPropertyAvailable, "Universal"), - HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "Preset2"), - }}; - SetSampleDatabaseHasPresetsNtf( - test_address, - bluetooth::has::kFeatureBitHearingAidTypeBanded | - bluetooth::has::kFeatureBitWritablePresets | - bluetooth::has::kFeatureBitDynamicPresets, - presets); - - uint8_t active_preset_index = 0; - std::vector<PresetInfo> preset_details; - - EXPECT_CALL(*callbacks, - OnPresetInfo(std::variant<RawAddress, int>(test_address), - PresetInfoReason::ALL_PRESET_INFO, _)) - .Times(1) - .WillOnce(SaveArg<2>(&preset_details)); - ON_CALL(*callbacks, OnActivePresetSelected(_, _)) - .WillByDefault(SaveArg<1>(&active_preset_index)); - TestConnect(test_address); - - int sv[2]; - ASSERT_EQ(0, socketpair(AF_LOCAL, SOCK_STREAM | SOCK_NONBLOCK, 0, sv)); - int socket_buffer_size = GetSocketBufferSize(sv[0]); - - HasClient::Get()->DebugDump(sv[0]); - int dumpsys_byte_cnt = 0; - ASSERT_TRUE(dumpsys_byte_cnt < socket_buffer_size); - ASSERT_TRUE(SimpleJsonValidator(sv[1], &dumpsys_byte_cnt)); -} - -TEST_F(HasClientTest, test_connect_database_out_of_sync) { - osi_property_set_bool("persist.bluetooth.has.always_use_preset_cache", false); - - const RawAddress test_address = GetTestAddress(1); - std::set<HasPreset, HasPreset::ComparatorDesc> has_presets = {{ - HasPreset(1, HasPreset::kPropertyAvailable, "Universal"), - HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "Preset2"), - }}; - SetSampleDatabaseHasPresetsNtf( - test_address, - bluetooth::has::kFeatureBitHearingAidTypeBanded | - bluetooth::has::kFeatureBitWritablePresets | - bluetooth::has::kFeatureBitDynamicPresets, - has_presets); - - EXPECT_CALL(*callbacks, OnDeviceAvailable( - test_address, - bluetooth::has::kFeatureBitHearingAidTypeBanded | - bluetooth::has::kFeatureBitWritablePresets | - bluetooth::has::kFeatureBitDynamicPresets)); - EXPECT_CALL(*callbacks, - OnConnectionState(ConnectionState::CONNECTED, test_address)); - TestConnect(test_address); - - ON_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _)) - .WillByDefault( - Invoke([this](uint16_t conn_id, uint16_t handle, - std::vector<uint8_t> value, tGATT_WRITE_TYPE write_type, - GATT_WRITE_OP_CB cb, void* cb_data) { - auto* svc = gatt::FindService(services_map[conn_id], handle); - if (svc == nullptr) return; - - tGATT_STATUS status = GATT_DATABASE_OUT_OF_SYNC; - if (cb) - cb(conn_id, status, handle, value.size(), value.data(), cb_data); - })); - - ON_CALL(gatt_interface, ServiceSearchRequest(_, _)).WillByDefault(Return()); - EXPECT_CALL(gatt_interface, ServiceSearchRequest(_, _)); - HasClient::Get()->GetPresetInfo(test_address, 1); -} - -class HasTypesTest : public ::testing::Test { - protected: - void SetUp(void) override { mock_function_count_map.clear(); } - - void TearDown(void) override {} -}; // namespace - -TEST_F(HasTypesTest, test_has_preset_serialize) { - HasPreset preset(0x01, - HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "My Writable Preset01"); - - auto sp_sz = preset.SerializedSize(); - std::vector<uint8_t> serialized(sp_sz); - - ASSERT_EQ(1 + // preset index - 1 + // properties - 1 + // name length - preset.GetName().length(), - sp_sz); - - /* Serialize should move the received buffer pointer by the size of data - */ - ASSERT_EQ(preset.Serialize(serialized.data(), serialized.size()), - serialized.data() + serialized.size()); - - /* Deserialize */ - HasPreset clone; - ASSERT_EQ(HasPreset::Deserialize(serialized.data(), serialized.size(), clone), - serialized.data() + serialized.size()); - - /* Verify */ - ASSERT_EQ(preset.GetIndex(), clone.GetIndex()); - ASSERT_EQ(preset.GetProperties(), clone.GetProperties()); - ASSERT_EQ(preset.GetName(), clone.GetName()); -} - -TEST_F(HasTypesTest, test_has_preset_serialize_output_buffer_to_small) { - HasPreset preset(0x01, - HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "My Writable Preset01"); - - /* On failure, the offset should still point on .data() */ - std::vector<uint8_t> serialized(preset.SerializedSize() - 1); - ASSERT_EQ(preset.Serialize(serialized.data(), serialized.size()), - serialized.data()); - ASSERT_EQ(preset.Serialize(serialized.data(), 0), serialized.data()); - ASSERT_EQ(preset.Serialize(serialized.data(), 1), serialized.data()); - ASSERT_EQ(preset.Serialize(serialized.data(), 10), serialized.data()); -} - -TEST_F(HasTypesTest, test_has_preset_serialize_name_to_long) { - HasPreset preset(0x01, - HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "This name is more than 40 characters long"); - - /* On failure, the offset should still point on .data() */ - std::vector<uint8_t> serialized(preset.SerializedSize()); - EXPECT_EQ(preset.Serialize(serialized.data(), serialized.size()), - serialized.data()); -} - -TEST_F(HasTypesTest, test_has_preset_deserialize_input_buffer_to_small) { - HasPreset preset(0x01, - HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "My Writable Preset01"); - - std::vector<uint8_t> serialized(preset.SerializedSize()); - - /* Serialize should move the received buffer pointer by the size of data - */ - ASSERT_EQ(preset.Serialize(serialized.data(), serialized.size()), - serialized.data() + serialized.size()); - - /* Deserialize */ - HasPreset clone; - ASSERT_EQ(HasPreset::Deserialize(serialized.data(), 0, clone), - serialized.data()); - ASSERT_EQ(HasPreset::Deserialize(serialized.data(), 1, clone), - serialized.data()); - ASSERT_EQ(HasPreset::Deserialize(serialized.data(), 11, clone), - serialized.data()); - ASSERT_EQ( - HasPreset::Deserialize(serialized.data(), serialized.size() - 1, clone), - serialized.data()); -} - -TEST_F(HasTypesTest, test_has_presets_serialize) { - HasPreset preset(0x01, - HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, - "My Writable Preset01"); - - HasPreset preset2(0x02, 0, "Nonwritable Unavailable Preset"); - - HasDevice has_device(GetTestAddress(1)); - has_device.has_presets.insert(preset); - has_device.has_presets.insert(preset2); - - auto out_buf_sz = has_device.SerializedPresetsSize(); - ASSERT_EQ(out_buf_sz, preset.SerializedSize() + preset2.SerializedSize() + 2); - - /* Serialize should append to the vector */ - std::vector<uint8_t> serialized; - ASSERT_TRUE(has_device.SerializePresets(serialized)); - ASSERT_EQ(out_buf_sz, serialized.size()); - - /* Deserialize */ - HasDevice clone(GetTestAddress(1)); - ASSERT_TRUE(HasDevice::DeserializePresets(serialized.data(), - serialized.size(), clone)); - - /* Verify */ - ASSERT_EQ(clone.has_presets.size(), has_device.has_presets.size()); - ASSERT_NE(0u, clone.has_presets.count(0x01)); - ASSERT_NE(0u, clone.has_presets.count(0x02)); - - ASSERT_EQ(clone.has_presets.find(0x01)->GetIndex(), - has_device.has_presets.find(0x01)->GetIndex()); - ASSERT_EQ(clone.has_presets.find(0x01)->GetProperties(), - has_device.has_presets.find(0x01)->GetProperties()); - ASSERT_EQ(clone.has_presets.find(0x01)->GetName(), - has_device.has_presets.find(0x01)->GetName()); - - ASSERT_EQ(clone.has_presets.find(0x02)->GetIndex(), - has_device.has_presets.find(0x02)->GetIndex()); - ASSERT_EQ(clone.has_presets.find(0x02)->GetProperties(), - has_device.has_presets.find(0x02)->GetProperties()); - ASSERT_EQ(clone.has_presets.find(0x02)->GetName(), - has_device.has_presets.find(0x02)->GetName()); -} - -TEST_F(HasTypesTest, test_group_op_coordinator_init) { - HasCtpGroupOpCoordinator::Initialize([](void*) { - /* Do nothing */ - }); - ASSERT_EQ(0u, HasCtpGroupOpCoordinator::ref_cnt); - auto address1 = GetTestAddress(1); - auto address2 = GetTestAddress(2); - - HasCtpGroupOpCoordinator wrapper( - {address1, address2}, - HasCtpOp(0x01, ::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6)); - ASSERT_EQ(2u, wrapper.ref_cnt); - - HasCtpGroupOpCoordinator::Cleanup(); - ASSERT_EQ(0u, wrapper.ref_cnt); - - ASSERT_EQ(1, mock_function_count_map["alarm_free"]); - ASSERT_EQ(1, mock_function_count_map["alarm_new"]); -} - -TEST_F(HasTypesTest, test_group_op_coordinator_copy) { - HasCtpGroupOpCoordinator::Initialize([](void*) { - /* Do nothing */ - }); - ASSERT_EQ(0u, HasCtpGroupOpCoordinator::ref_cnt); - auto address1 = GetTestAddress(1); - auto address2 = GetTestAddress(2); - - HasCtpGroupOpCoordinator wrapper( - {address1, address2}, - HasCtpOp(0x01, ::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6)); - HasCtpGroupOpCoordinator wrapper2( - {address1}, - HasCtpOp(0x01, ::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6)); - ASSERT_EQ(3u, wrapper.ref_cnt); - HasCtpGroupOpCoordinator wrapper3 = wrapper2; - auto* wrapper4 = - new HasCtpGroupOpCoordinator(HasCtpGroupOpCoordinator(wrapper2)); - ASSERT_EQ(5u, wrapper.ref_cnt); - - delete wrapper4; - ASSERT_EQ(4u, wrapper.ref_cnt); - - HasCtpGroupOpCoordinator::Cleanup(); - ASSERT_EQ(0u, wrapper.ref_cnt); - - ASSERT_EQ(1, mock_function_count_map["alarm_free"]); - ASSERT_EQ(1, mock_function_count_map["alarm_new"]); -} - -TEST_F(HasTypesTest, test_group_op_coordinator_completion) { - HasCtpGroupOpCoordinator::Initialize([](void*) { - /* Do nothing */ - LOG(INFO) << __func__ << " callback call"; - }); - ASSERT_EQ(0u, HasCtpGroupOpCoordinator::ref_cnt); - auto address1 = GetTestAddress(1); - auto address2 = GetTestAddress(2); - auto address3 = GetTestAddress(3); - - HasCtpGroupOpCoordinator wrapper( - {address1, address3}, - HasCtpOp(0x01, ::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6)); - HasCtpGroupOpCoordinator wrapper2( - {address2}, - HasCtpOp(0x01, ::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6)); - ASSERT_EQ(3u, wrapper.ref_cnt); - - ASSERT_FALSE(wrapper.IsFullyCompleted()); - - wrapper.SetCompleted(address1); - ASSERT_EQ(2u, wrapper.ref_cnt); - - wrapper.SetCompleted(address3); - ASSERT_EQ(1u, wrapper.ref_cnt); - ASSERT_FALSE(wrapper.IsFullyCompleted()); - ASSERT_EQ(0, mock_function_count_map["alarm_free"]); - - /* Non existing address completion */ - wrapper.SetCompleted(address2); - ASSERT_EQ(0, mock_function_count_map["alarm_free"]); - ASSERT_EQ(1u, wrapper.ref_cnt); - - /* Last device address completion */ - wrapper2.SetCompleted(address2); - ASSERT_TRUE(wrapper.IsFullyCompleted()); - ASSERT_EQ(0u, wrapper.ref_cnt); - ASSERT_EQ(1, mock_function_count_map["alarm_free"]); - mock_function_count_map["alarm_free"] = 0; - - HasCtpGroupOpCoordinator::Cleanup(); - - ASSERT_EQ(0, mock_function_count_map["alarm_free"]); - ASSERT_EQ(1, mock_function_count_map["alarm_new"]); -} - -} // namespace -} // namespace internal -} // namespace has -} // namespace bluetooth diff --git a/bta/has/has_ctp.cc b/bta/has/has_ctp.cc deleted file mode 100644 index 616752a67..000000000 --- a/bta/has/has_ctp.cc +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright 2021 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "has_ctp.h" -//#include "os/log.h" - -namespace le_audio { -namespace has { - -static bool ParsePresetGenericUpdate(uint16_t& len, const uint8_t* value, - HasCtpNtf& ntf) { - if (len < sizeof(ntf.prev_index) + HasPreset::kCharValueMinSize) { - LOG(ERROR) << "Invalid preset value length=" << +len - << " for generic update."; - return false; - } - - STREAM_TO_UINT8(ntf.index, value); - len -= 1; - - ntf.preset = HasPreset::FromCharacteristicValue(len, value); - return true; -} - -static bool ParsePresetIndex(uint16_t& len, const uint8_t* value, - HasCtpNtf& ntf) { - if (len < sizeof(ntf.index)) { - LOG(ERROR) << __func__ << "Invalid preset value length=" << +len - << " for generic update."; - return false; - } - - STREAM_TO_UINT8(ntf.index, value); - len -= 1; - return true; -} - -static bool ParsePresetReadResponse(uint16_t& len, const uint8_t* value, - HasCtpNtf& ntf) { - if (len < sizeof(ntf.is_last) + HasPreset::kCharValueMinSize) { - LOG(ERROR) << "Invalid preset value length=" << +len; - return false; - } - - STREAM_TO_UINT8(ntf.is_last, value); - len -= 1; - - ntf.preset = HasPreset::FromCharacteristicValue(len, value); - return true; -} - -static bool ParsePresetChanged(uint16_t len, const uint8_t* value, - HasCtpNtf& ntf) { - if (len < sizeof(ntf.is_last) + sizeof(ntf.change_id)) { - LOG(ERROR) << __func__ << "Invalid preset value length=" << +len; - return false; - } - - uint8_t change_id; - STREAM_TO_UINT8(change_id, value); - len -= 1; - if (change_id > static_cast<std::underlying_type_t<PresetCtpChangeId>>( - PresetCtpChangeId::CHANGE_ID_MAX_)) { - LOG(ERROR) << __func__ << "Invalid preset chenge_id=" << change_id; - return false; - } - ntf.change_id = PresetCtpChangeId(change_id); - STREAM_TO_UINT8(ntf.is_last, value); - len -= 1; - - switch (ntf.change_id) { - case PresetCtpChangeId::PRESET_GENERIC_UPDATE: - return ParsePresetGenericUpdate(len, value, ntf); - case PresetCtpChangeId::PRESET_AVAILABLE: - return ParsePresetIndex(len, value, ntf); - case PresetCtpChangeId::PRESET_UNAVAILABLE: - return ParsePresetIndex(len, value, ntf); - case PresetCtpChangeId::PRESET_DELETED: - return ParsePresetIndex(len, value, ntf); - default: - return false; - } - - return true; -} - -std::optional<HasCtpNtf> HasCtpNtf::FromCharacteristicValue( - uint16_t len, const uint8_t* value) { - if (len < 3) { - LOG(ERROR) << __func__ << " Invalid Cp notification."; - return std::nullopt; - } - - uint8_t op; - STREAM_TO_UINT8(op, value); - --len; - - if ((op != static_cast<std::underlying_type_t<PresetCtpOpcode>>( - PresetCtpOpcode::READ_PRESET_RESPONSE)) && - (op != static_cast<std::underlying_type_t<PresetCtpOpcode>>( - PresetCtpOpcode::PRESET_CHANGED))) { - LOG(ERROR) << __func__ - << ": Received invalid opcode in control point notification: " - << ++op; - return std::nullopt; - } - - HasCtpNtf ntf; - memset(&ntf, 0, sizeof(HasCtpNtf)); - ntf.opcode = PresetCtpOpcode(op); - if (ntf.opcode == le_audio::has::PresetCtpOpcode::PRESET_CHANGED) { - if (!ParsePresetChanged(len, value, ntf)) return std::nullopt; - - } else if (ntf.opcode == - le_audio::has::PresetCtpOpcode::READ_PRESET_RESPONSE) { - if (!ParsePresetReadResponse(len, value, ntf)) return std::nullopt; - } - - return ntf; -} - -uint16_t HasCtpOp::last_op_id_ = 0; - -std::vector<uint8_t> HasCtpOp::ToCharacteristicValue() const { - std::vector<uint8_t> value; - auto* pp = value.data(); - - switch (opcode) { - case PresetCtpOpcode::READ_PRESETS: - value.resize(3); - pp = value.data(); - UINT8_TO_STREAM( - pp, static_cast<std::underlying_type_t<PresetCtpOpcode>>(opcode)); - UINT8_TO_STREAM(pp, index); - UINT8_TO_STREAM(pp, num_of_indices); - break; - case PresetCtpOpcode::SET_ACTIVE_PRESET: - case PresetCtpOpcode::SET_ACTIVE_PRESET_SYNC: - value.resize(2); - pp = value.data(); - UINT8_TO_STREAM( - pp, static_cast<std::underlying_type_t<PresetCtpOpcode>>(opcode)); - UINT8_TO_STREAM(pp, index); - break; - - case PresetCtpOpcode::SET_NEXT_PRESET: - case PresetCtpOpcode::SET_NEXT_PRESET_SYNC: - case PresetCtpOpcode::SET_PREV_PRESET: - case PresetCtpOpcode::SET_PREV_PRESET_SYNC: - value.resize(1); - pp = value.data(); - UINT8_TO_STREAM( - pp, static_cast<std::underlying_type_t<PresetCtpOpcode>>(opcode)); - break; - - case PresetCtpOpcode::WRITE_PRESET_NAME: { - auto name_str = name.value_or(""); - value.resize(2 + name_str.length()); - pp = value.data(); - - UINT8_TO_STREAM( - pp, static_cast<std::underlying_type_t<PresetCtpOpcode>>(opcode)); - UINT8_TO_STREAM(pp, index); - memcpy(pp, name_str.c_str(), name_str.length()); - } break; - - default: - LOG_ASSERT(false) << __func__ << "Bad control point operation!"; - break; - } - - return value; -} - -#define CASE_SET_PTR_TO_TOKEN_STR(en) \ - case (en): \ - ch = #en; \ - break; - -std::ostream& operator<<(std::ostream& out, const PresetCtpChangeId value) { - const char* ch = 0; - switch (value) { - CASE_SET_PTR_TO_TOKEN_STR(PresetCtpChangeId::PRESET_GENERIC_UPDATE); - CASE_SET_PTR_TO_TOKEN_STR(PresetCtpChangeId::PRESET_DELETED); - CASE_SET_PTR_TO_TOKEN_STR(PresetCtpChangeId::PRESET_AVAILABLE); - CASE_SET_PTR_TO_TOKEN_STR(PresetCtpChangeId::PRESET_UNAVAILABLE); - default: - ch = "INVALID_CHANGE_ID"; - break; - } - return out << ch; -} - -std::ostream& operator<<(std::ostream& out, const PresetCtpOpcode value) { - const char* ch = 0; - switch (value) { - CASE_SET_PTR_TO_TOKEN_STR(PresetCtpOpcode::READ_PRESETS); - CASE_SET_PTR_TO_TOKEN_STR(PresetCtpOpcode::READ_PRESET_RESPONSE); - CASE_SET_PTR_TO_TOKEN_STR(PresetCtpOpcode::PRESET_CHANGED); - CASE_SET_PTR_TO_TOKEN_STR(PresetCtpOpcode::WRITE_PRESET_NAME); - CASE_SET_PTR_TO_TOKEN_STR(PresetCtpOpcode::SET_ACTIVE_PRESET); - CASE_SET_PTR_TO_TOKEN_STR(PresetCtpOpcode::SET_NEXT_PRESET); - CASE_SET_PTR_TO_TOKEN_STR(PresetCtpOpcode::SET_PREV_PRESET); - CASE_SET_PTR_TO_TOKEN_STR(PresetCtpOpcode::SET_ACTIVE_PRESET_SYNC); - CASE_SET_PTR_TO_TOKEN_STR(PresetCtpOpcode::SET_NEXT_PRESET_SYNC); - CASE_SET_PTR_TO_TOKEN_STR(PresetCtpOpcode::SET_PREV_PRESET_SYNC); - default: - ch = "NOT_A_VALID_OPCODE"; - break; - } - return out << ch; -} -#undef SET_CH_TO_TOKENIZED - -std::ostream& operator<<(std::ostream& out, const HasCtpOp& op) { - out << "\"HasCtpOp\": {"; - if (std::holds_alternative<int>(op.addr_or_group)) { - out << "\"group_id\": " << std::get<int>(op.addr_or_group); - } else if (std::holds_alternative<RawAddress>(op.addr_or_group)) { - //out << "\"address\": \"" - //<< ADDRESS_TO_LOGGABLE_STR(std::get<RawAddress>(op.addr_or_group)) << "\""; - } else { - out << "\"bad value\""; - } - out << ", \"id\": " << op.op_id << ", \"opcode\": \"" << op.opcode << "\"" - << ", \"index\": " << +op.index << ", \"name\": \"" - << op.name.value_or("<none>") << "\"" - << "}"; - return out; -} - -std::ostream& operator<<(std::ostream& out, const HasCtpNtf& ntf) { - out << "\"HasCtpNtf\": {"; - out << "\"opcode\": \"" << ntf.opcode << "\""; - - if (ntf.opcode == PresetCtpOpcode::READ_PRESET_RESPONSE) { - out << ", \"is_last\": " << (ntf.is_last ? "\"True\"" : "\"False\""); - if (ntf.preset.has_value()) { - out << ", \"preset\": " << ntf.preset.value(); - } else { - out << ", \"preset\": \"None\""; - } - - } else if (ntf.opcode == PresetCtpOpcode::PRESET_CHANGED) { - out << ", \"change_id\": " << ntf.change_id; - out << ", \"is_last\": " << (ntf.is_last ? "\"True\"" : "\"False\""); - switch (ntf.change_id) { - case PresetCtpChangeId::PRESET_GENERIC_UPDATE: - out << ", \"prev_index\": " << +ntf.prev_index; - if (ntf.preset.has_value()) { - out << ", \"preset\": {" << ntf.preset.value() << "}"; - } else { - out << ", \"preset\": \"None\""; - } - break; - case PresetCtpChangeId::PRESET_DELETED: - FALLTHROUGH; - case PresetCtpChangeId::PRESET_AVAILABLE: - FALLTHROUGH; - case PresetCtpChangeId::PRESET_UNAVAILABLE: - out << ", \"index\": " << +ntf.index; - break; - default: - break; - } - } - out << "}"; - - return out; -} - -} // namespace has -} // namespace le_audio diff --git a/bta/has/has_ctp.h b/bta/has/has_ctp.h deleted file mode 100644 index 963a23c7b..000000000 --- a/bta/has/has_ctp.h +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright 2021 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <list> -#include <optional> - -#include "hardware/bt_has.h" -#include "has_preset.h" -#include "osi/include/alarm.h" - -namespace le_audio { -namespace has { -/* HAS control point Change Id */ -enum class PresetCtpChangeId : uint8_t { - PRESET_GENERIC_UPDATE = 0, - PRESET_DELETED, - PRESET_AVAILABLE, - PRESET_UNAVAILABLE, - /* NOTICE: Values below are for internal use only of this particular - * implementation, and do not correspond to any bluetooth specification. - */ - CHANGE_ID_MAX_ = PRESET_UNAVAILABLE, -}; -std::ostream& operator<<(std::ostream& out, const PresetCtpChangeId value); - -/* HAS control point Opcodes */ -enum class PresetCtpOpcode : uint8_t { - READ_PRESETS = 1, - READ_PRESET_RESPONSE, - PRESET_CHANGED, - WRITE_PRESET_NAME, - SET_ACTIVE_PRESET, - SET_NEXT_PRESET, - SET_PREV_PRESET, - SET_ACTIVE_PRESET_SYNC, - SET_NEXT_PRESET_SYNC, - SET_PREV_PRESET_SYNC, - /* NOTICE: Values below are for internal use only of this particular - * implementation, and do not correspond to any bluetooth specification. - */ - OP_MAX_ = SET_PREV_PRESET_SYNC, - OP_NONE_ = OP_MAX_ + 1, -}; -std::ostream& operator<<(std::ostream& out, const PresetCtpOpcode value); - -static constexpr uint16_t PresetCtpOpcode2Bitmask(PresetCtpOpcode op) { - return ((uint16_t)0b1 << static_cast<std::underlying_type_t<PresetCtpOpcode>>( - op)); -} - -/* Mandatory opcodes if control point characteristic exists */ -static constexpr uint16_t kControlPointMandatoryOpcodesBitmask = - PresetCtpOpcode2Bitmask(PresetCtpOpcode::READ_PRESETS) | - PresetCtpOpcode2Bitmask(PresetCtpOpcode::SET_ACTIVE_PRESET) | - PresetCtpOpcode2Bitmask(PresetCtpOpcode::SET_NEXT_PRESET) | - PresetCtpOpcode2Bitmask(PresetCtpOpcode::SET_PREV_PRESET); - -/* Optional coordinated operation opcodes */ -static constexpr uint16_t kControlPointSynchronizedOpcodesBitmask = - PresetCtpOpcode2Bitmask(PresetCtpOpcode::SET_ACTIVE_PRESET_SYNC) | - PresetCtpOpcode2Bitmask(PresetCtpOpcode::SET_NEXT_PRESET_SYNC) | - PresetCtpOpcode2Bitmask(PresetCtpOpcode::SET_PREV_PRESET_SYNC); - -/* Represents HAS Control Point value notification */ -struct HasCtpNtf { - PresetCtpOpcode opcode; - PresetCtpChangeId change_id; - bool is_last; - union { - uint8_t index; - uint8_t prev_index; - }; - std::optional<HasPreset> preset; - - static std::optional<HasCtpNtf> FromCharacteristicValue(uint16_t len, - const uint8_t* value); -}; -std::ostream& operator<<(std::ostream& out, const HasCtpNtf& value); - -/* Represents HAS Control Point operation request */ -struct HasCtpOp { - std::variant<RawAddress, int> addr_or_group; - PresetCtpOpcode opcode; - uint8_t index; - uint8_t num_of_indices; - std::optional<std::string> name; - uint16_t op_id; - - HasCtpOp(std::variant<RawAddress, int> addr_or_group_id, PresetCtpOpcode op, - uint8_t index = bluetooth::has::kHasPresetIndexInvalid, - uint8_t num_of_indices = 1, - std::optional<std::string> name = std::nullopt) - : addr_or_group(addr_or_group_id), - opcode(op), - index(index), - num_of_indices(num_of_indices), - name(name) { - /* Skip 0 on roll-over */ - last_op_id_ += 1; - if (last_op_id_ == 0) last_op_id_ = 1; - op_id = last_op_id_; - } - - std::vector<uint8_t> ToCharacteristicValue(void) const; - - bool IsGroupRequest() const { - return std::holds_alternative<int>(addr_or_group); - } - - int GetGroupId() const { - return std::holds_alternative<int>(addr_or_group) - ? std::get<int>(addr_or_group) - : -1; - } - - RawAddress GetDeviceAddr() const { - return std::holds_alternative<RawAddress>(addr_or_group) - ? std::get<RawAddress>(addr_or_group) - : RawAddress::kEmpty; - } - - bool IsSyncedOperation() const { - return (opcode == PresetCtpOpcode::SET_ACTIVE_PRESET_SYNC) || - (opcode == PresetCtpOpcode::SET_NEXT_PRESET_SYNC) || - (opcode == PresetCtpOpcode::SET_PREV_PRESET_SYNC); - } - - private: - /* It's fine for this to roll-over eventually */ - static uint16_t last_op_id_; -}; -std::ostream& operator<<(std::ostream& out, const HasCtpOp& value); - -/* Used to track group operations. SetCompleted() allows to mark - * a single device as operation-completed when notification is received. - * When all the devices are SetComplete'd, timeout timer is being canceled and - * a group operation can be considered completed (IsFullyCompleted() == true). - * - * NOTICE: A single callback and reference counter is being used for all the - * coordinator instances, therefore creating more instances result - * in timeout timer being rescheduled. User should remove all the - * pending op. coordinators in the timer timeout callback. - */ -struct HasCtpGroupOpCoordinator { - std::list<RawAddress> devices; - HasCtpOp operation; - std::list<bluetooth::has::PresetInfo> preset_info_verification_list; - - static size_t ref_cnt; - static alarm_t* operation_timeout_timer; - static constexpr uint16_t kOperationTimeoutMs = 10000u; - static alarm_callback_t cb; - - static void Initialize(alarm_callback_t c = nullptr) { - operation_timeout_timer = nullptr; - ref_cnt = 0; - cb = c; - } - - static void Cleanup() { - if (operation_timeout_timer != nullptr) { - if (alarm_is_scheduled(operation_timeout_timer)) { - DLOG(INFO) << __func__ << +ref_cnt; - alarm_cancel(operation_timeout_timer); - } - alarm_free(operation_timeout_timer); - operation_timeout_timer = nullptr; - } - - ref_cnt = 0; - } - - static bool IsFullyCompleted() { return ref_cnt == 0; } - static bool IsPending() { return ref_cnt != 0; } - - HasCtpGroupOpCoordinator() = delete; - HasCtpGroupOpCoordinator& operator=(const HasCtpGroupOpCoordinator&) = delete; - /* NOTICE: It cannot be non-copyable if we want to put it into the std::map. - * The default copy constructor and copy assignment operator would break the - * reference counting, so we must increment ref_cnt for all the temporary - * copies. - */ - HasCtpGroupOpCoordinator(const HasCtpGroupOpCoordinator& other) - : devices(other.devices), - operation(other.operation), - preset_info_verification_list(other.preset_info_verification_list) { - ref_cnt += other.devices.size(); - } - - HasCtpGroupOpCoordinator(const std::vector<RawAddress>& targets, - HasCtpOp operation) - : operation(operation) { - LOG_ASSERT(targets.size() != 0) << " Empty device list error."; - if (targets.size() != 1) { - LOG_ASSERT(operation.IsGroupRequest()) << " Must be a group operation!"; - LOG_ASSERT(operation.GetGroupId() != -1) << " Must set valid group_id!"; - } - - devices = std::list<RawAddress>(targets.cbegin(), targets.cend()); - - ref_cnt += devices.size(); - if (operation_timeout_timer == nullptr) { - operation_timeout_timer = alarm_new("GroupOpTimer"); - } - - if (alarm_is_scheduled(operation_timeout_timer)) - alarm_cancel(operation_timeout_timer); - - LOG_ASSERT(cb != nullptr) << " Timeout timer callback not set!"; - alarm_set_on_mloop(operation_timeout_timer, kOperationTimeoutMs, cb, - nullptr); - } - - ~HasCtpGroupOpCoordinator() { - /* Check if cleanup wasn't already called */ - if (ref_cnt != 0) { - ref_cnt -= devices.size(); - if (ref_cnt == 0) { - Cleanup(); - } - } - } - - bool SetCompleted(RawAddress addr) { - auto result = false; - - auto it = std::find(devices.begin(), devices.end(), addr); - if (it != devices.end()) { - devices.erase(it); - --ref_cnt; - result = true; - } - - if (ref_cnt == 0) { - alarm_cancel(operation_timeout_timer); - alarm_free(operation_timeout_timer); - operation_timeout_timer = nullptr; - } - - return result; - } -}; - -} // namespace has -} // namespace le_audio diff --git a/bta/has/has_journal.cc b/bta/has/has_journal.cc deleted file mode 100644 index 8a79375f8..000000000 --- a/bta/has/has_journal.cc +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2021 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "has_journal.h" - -#include "internal_include/bt_trace.h" - -namespace le_audio { -namespace has { - -std::ostream& operator<<(std::ostream& os, const HasJournalRecord& r) { - os << "{"; - - char eventtime[20] = ""; - char temptime[20] = ""; - struct tm* tstamp = localtime(&r.timestamp.tv_sec); - if (tstamp != NULL) { - strftime(temptime, sizeof(temptime), "%H:%M:%S", tstamp); - } - snprintf(eventtime, sizeof(eventtime), "%s.%03ld", temptime, - r.timestamp.tv_nsec / 1000000); - os << "\"time\": \"" << eventtime << "\", "; - - if (r.is_operation) { - os << std::get<HasCtpOp>(r.event); - os << ", \"status\": \"" << loghex(r.op_status) << "\""; - - } else if (r.is_notification) { - os << std::get<HasCtpNtf>(r.event) << ", "; - - } else if (r.is_active_preset_change) { - os << "\"Active preset changed\": {\"active_preset_idx\": " - << +std::get<uint8_t>(r.event) << "}"; - - } else { - os << "\"Features changed\": {\"features\": \"" - << loghex(std::get<uint8_t>(r.event)) << "\"}"; - } - - os << "}"; - return os; -} -} // namespace has -} // namespace le_audio diff --git a/bta/has/has_journal.h b/bta/has/has_journal.h deleted file mode 100644 index 99bdc4986..000000000 --- a/bta/has/has_journal.h +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2021 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <sys/time.h> -#include <time.h> - -#include <list> -#include <variant> - -//#include "common/time_util.h" -#include "has_ctp.h" - -/* Journal and journal entry classes used by the state dumping functionality. */ -namespace le_audio { -namespace has { -static constexpr uint8_t kHasJournalNumRecords = 20; - -struct HasJournalRecord { - /* Indicates which value the `event` contains (due to ambiguous uint8_t) */ - bool is_operation : 1, is_notification : 1, is_features_change : 1, - is_active_preset_change : 1; - std::variant<HasCtpOp, HasCtpNtf, uint8_t> event; - struct timespec timestamp; - - /* Operation context handle to match on GATT write response */ - void* op_context_handle; - - /* Status of the operation to be set once it gets completed */ - uint8_t op_status; - - HasJournalRecord(const HasCtpOp& op, void* context) - : event(op), op_context_handle(context) { - clock_gettime(CLOCK_REALTIME, ×tamp); - is_operation = true; - is_notification = false; - is_features_change = false; - is_active_preset_change = false; - } - - HasJournalRecord(const HasCtpNtf& ntf) : event(ntf) { - clock_gettime(CLOCK_REALTIME, ×tamp); - is_operation = false; - is_notification = true; - is_features_change = false; - is_active_preset_change = false; - } - - HasJournalRecord(uint8_t value, bool is_feat_change) : event(value) { - clock_gettime(CLOCK_REALTIME, ×tamp); - is_operation = false; - is_notification = false; - if (is_feat_change) { - is_active_preset_change = false; - is_features_change = true; - } else { - is_active_preset_change = true; - is_features_change = false; - } - } -}; -std::ostream& operator<<(std::ostream& os, const HasJournalRecord& r); - -template <class valT, size_t cache_max> -class CacheList { - public: - valT& Append(valT data) { - items_.push_front(std::move(data)); - - if (items_.size() > cache_max) { - items_.pop_back(); - } - - return items_.front(); - } - - using iterator = typename std::list<valT>::iterator; - iterator begin(void) { return items_.begin(); } - iterator end(void) { return items_.end(); } - - using const_iterator = typename std::list<valT>::const_iterator; - const_iterator begin(void) const { return items_.begin(); } - const_iterator end(void) const { return items_.end(); } - - void Erase(iterator it) { - if (it != items_.end()) items_.erase(it); - } - - void Clear(void) { items_.clear(); } - bool isEmpty(void) { return items_.empty(); } - - private: - typename std::list<valT> items_; -}; - -using HasJournal = CacheList<HasJournalRecord, kHasJournalNumRecords>; -} // namespace has -} // namespace le_audio diff --git a/bta/has/has_preset.cc b/bta/has/has_preset.cc deleted file mode 100644 index b54831150..000000000 --- a/bta/has/has_preset.cc +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2021 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "has_preset.h" - -namespace le_audio { -namespace has { - -std::optional<HasPreset> HasPreset::FromCharacteristicValue( - uint16_t& len, const uint8_t* value) { - if ((len < kCharValueMinSize) || - (len > kCharValueMinSize + kPresetNameLengthLimit)) { - LOG(ERROR) << __func__ << " Preset record to long: " << len; - return std::nullopt; - } - - HasPreset preset; - STREAM_TO_UINT8(preset.index_, value); - --len; - STREAM_TO_UINT8(preset.properties_, value); - --len; - preset.name_ = std::string(value, value + len); - - return preset; -} - -void HasPreset::ToCharacteristicValue(std::vector<uint8_t>& value) const { - auto initial_offset = value.size(); - - value.resize(value.size() + kCharValueMinSize + name_.size()); - auto pp = value.data() + initial_offset; - - UINT8_TO_STREAM(pp, index_); - UINT8_TO_STREAM(pp, properties_); - ARRAY_TO_STREAM(pp, name_.c_str(), (int)name_.size()); -} - -uint8_t* HasPreset::Serialize(uint8_t* p_out, size_t buffer_size) const { - if (buffer_size < SerializedSize()) { - LOG(ERROR) << "Invalid output buffer size!"; - return p_out; - } - - uint8_t name_len = name_.length(); - if (name_len > kPresetNameLengthLimit) { - LOG(ERROR) << __func__ - << " Invalid preset name length. Cannot be serialized!"; - return p_out; - } - - /* Serialized data length */ - UINT8_TO_STREAM(p_out, name_len + 2); - - UINT8_TO_STREAM(p_out, index_); - UINT8_TO_STREAM(p_out, properties_); - ARRAY_TO_STREAM(p_out, name_.c_str(), (int)name_.size()); - return p_out; -} - -const uint8_t* HasPreset::Deserialize(const uint8_t* p_in, size_t len, - HasPreset& preset) { - const uint8_t nonamed_size = HasPreset(0, 0).SerializedSize(); - auto* p_curr = p_in; - - if (len < nonamed_size) { - LOG(ERROR) << "Invalid buffer size " << +len << ". Cannot deserialize."; - return p_in; - } - - uint8_t serialized_data_len; - STREAM_TO_UINT8(serialized_data_len, p_curr); - if (serialized_data_len < 2) { - LOG(ERROR) << __func__ << " Invalid data size. Cannot be deserialized!"; - return p_in; - } - - auto name_len = serialized_data_len - 2; - if ((name_len > kPresetNameLengthLimit) || - ((size_t)nonamed_size + name_len > len)) { - LOG(ERROR) << __func__ - << " Invalid preset name length. Cannot be deserialized!"; - return p_in; - } - - STREAM_TO_UINT8(preset.index_, p_curr); - STREAM_TO_UINT8(preset.properties_, p_curr); - if (name_len) preset.name_ = std::string((const char*)p_curr, name_len); - - return p_curr + name_len; -} - -std::ostream& operator<<(std::ostream& os, const HasPreset& b) { - os << "{\"index\": " << +b.GetIndex(); - os << ", \"name\": \"" << b.GetName() << "\""; - os << ", \"is_available\": " << (b.IsAvailable() ? "\"True\"" : "\"False\""); - os << ", \"is_writable\": " << (b.IsWritable() ? "\"True\"" : "\"False\""); - os << "}"; - return os; -} - -} // namespace has -} // namespace le_audio diff --git a/bta/has/has_preset.h b/bta/has/has_preset.h deleted file mode 100644 index fae011ade..000000000 --- a/bta/has/has_preset.h +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2021 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <optional> -#include <string> - -#include "bt_types.h" -#include "hardware/bt_has.h" - -namespace le_audio { -namespace has { -/* Represents preset instance. It stores properties such as preset name, - * preset index and if it supports renaming. Also stores all the needed - * GATT characteristics and descriptor informations. - */ -class HasPreset { - private: - mutable std::string name_; - mutable uint8_t properties_; - uint8_t index_; - - public: - static constexpr size_t kCharValueMinSize = 1 /*index*/ + 1 /*properties*/; - - static constexpr uint8_t kPropertyWritable = 0x01; - static constexpr uint8_t kPropertyAvailable = 0x02; - - static constexpr uint8_t kPresetNameLengthLimit = 40; - - HasPreset(uint8_t index, uint8_t props = 0, - std::optional<std::string> name = std::nullopt) - : properties_(props), index_(index) { - name_ = name.value_or(""); - } - HasPreset() - : name_(""), - properties_(0), - index_(bluetooth::has::kHasPresetIndexInvalid) {} - - auto& GetName() const { return name_; } - decltype(index_) GetIndex() const { return index_; } - decltype(properties_) GetProperties() const { return properties_; } - bool IsWritable() const { return properties_ & kPropertyWritable; } - bool IsAvailable() const { return properties_ & kPropertyAvailable; } - - HasPreset& operator=(const HasPreset& other) { - LOG_ASSERT(index_ == other.GetIndex()) - << "Assigning immutable preset index!"; - - if ((this != &other) && (*this != other)) { - index_ = other.GetIndex(); - name_ = other.GetName(); - } - return *this; - } - - bool operator==(const HasPreset& b) const { - return (index_ == b.index_) && (properties_ == b.properties_) && - (name_ == b.name_); - } - bool operator!=(const HasPreset& b) const { - return (index_ != b.index_) || (properties_ != b.properties_) || - (name_ != b.name_); - } - bool operator<(const HasPreset& b) const { return index_ < b.index_; } - friend std::ostream& operator<<(std::ostream& os, const HasPreset& b); - - struct ComparatorDesc { - using is_transparent = void; - bool operator()(HasPreset const& a, int index) const { - return a.index_ < index; - } - bool operator()(int index, HasPreset const& a) const { - return index < a.index_; - } - bool operator()(HasPreset const& a, HasPreset const& b) const { - return a.index_ < b.index_; - } - }; - - static std::optional<HasPreset> FromCharacteristicValue(uint16_t& len, - const uint8_t* value); - void ToCharacteristicValue(std::vector<uint8_t>& value) const; - - /* Calculates buffer space that the preset will use when serialized */ - uint8_t SerializedSize() const { - return (sizeof(index_) + sizeof(properties_) + 1 /* name length */ - + name_.length()); - } - /* Serializes into binary blob for the persistent storage */ - uint8_t* Serialize(uint8_t* p_out, size_t buffer_size) const; - /* Deserializes binary blob read from the persistent storage */ - static const uint8_t* Deserialize(const uint8_t* p_in, size_t len, - HasPreset& preset); -}; - -} // namespace has -} // namespace le_audio diff --git a/bta/has/has_types.cc b/bta/has/has_types.cc deleted file mode 100644 index 6d886687f..000000000 --- a/bta/has/has_types.cc +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2021 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "has_types.h" - -namespace le_audio { -namespace has { - -std::ostream& operator<<(std::ostream& os, const HasDevice& b) { - os << "HAP device: {" - << "addr: " << b.addr << ", conn id: " << b.conn_id << "}"; - return os; -} - -} // namespace has -} // namespace le_audio diff --git a/bta/has/has_types.h b/bta/has/has_types.h deleted file mode 100644 index 66d0dd3fb..000000000 --- a/bta/has/has_types.h +++ /dev/null @@ -1,420 +0,0 @@ -/* - * Copyright 2021 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <numeric> -#include <optional> -#include <set> -#include <vector> - -#include "bta_gatt_api.h" -#include "gap_api.h" -#include "hardware/bt_has.h" -#include "has_ctp.h" -#include "has_journal.h" -#include "has_preset.h" - -namespace le_audio { -namespace has { - -/* Helper class to pass some minimal context through the GATT operation API. */ -union HasGattOpContext { - public: - void* ptr = nullptr; - struct { - /* Ctp. Operation ID or 0 if not a control point operation context */ - uint16_t ctp_op_id; - - /* Additional user flags */ - uint8_t context_flags; - }; - - /* Flags describing operation context */ - static constexpr uint8_t kContextFlagsEnableNotification = 0x01; - static constexpr uint8_t kIsNotNull = 0x02; - - static constexpr uint8_t kStatusCodeNotSet = 0xF0; - - HasGattOpContext(const HasCtpOp& ctp_op, uint8_t flags = 0) { - ctp_op_id = ctp_op.op_id; - /* Differ from nullptr in at least 1 bit when everything else is 0 */ - context_flags = flags | kIsNotNull; - } - HasGattOpContext(uint8_t flags) : ctp_op_id(0) { - context_flags = flags | kIsNotNull; - } - HasGattOpContext(void* pp) { - ptr = pp; - /* Differ from nullptr in at least 1 bit when everything else is 0 */ - context_flags |= kIsNotNull; - } - operator void*() { return ptr; } -}; - -/* Context must be constrained to void* size to pass through the GATT API */ -static_assert(sizeof(HasGattOpContext) <= sizeof(void*)); - -/* Service UUIDs */ -static const bluetooth::Uuid kUuidHearingAccessService = - bluetooth::Uuid::From16Bit(0x1854); -static const bluetooth::Uuid kUuidHearingAidFeatures = - bluetooth::Uuid::From16Bit(0x2BDA); -static const bluetooth::Uuid kUuidHearingAidPresetControlPoint = - bluetooth::Uuid::From16Bit(0x2BDB); -static const bluetooth::Uuid kUuidActivePresetIndex = - bluetooth::Uuid::From16Bit(0x2BDC); - -static const uint8_t kStartPresetIndex = 1; -static const uint8_t kMaxNumOfPresets = 255; - -/* Base device class for the GATT-based service clients */ -class GattServiceDevice { - public: - RawAddress addr; - uint16_t conn_id = GATT_INVALID_CONN_ID; - uint16_t service_handle = GAP_INVALID_HANDLE; - bool is_connecting_actively = false; - - uint8_t gatt_svc_validation_steps = 0xFE; - bool isGattServiceValid() { return gatt_svc_validation_steps == 0; } - - GattServiceDevice(const RawAddress& addr, bool connecting_actively = false) - : addr(addr), is_connecting_actively(connecting_actively) {} - - GattServiceDevice() : GattServiceDevice(RawAddress::kEmpty) {} - - bool IsConnected() const { return conn_id != GATT_INVALID_CONN_ID; } - - class MatchAddress { - private: - RawAddress addr; - - public: - MatchAddress(RawAddress addr) : addr(addr) {} - bool operator()(const GattServiceDevice& other) const { - return (addr == other.addr); - } - }; - - class MatchConnId { - private: - uint16_t conn_id; - - public: - MatchConnId(uint16_t conn_id) : conn_id(conn_id) {} - bool operator()(const GattServiceDevice& other) const { - return (conn_id == other.conn_id); - } - }; - - void Dump(std::ostream& os) const { - os << "\"addr\": \"" << addr << "\""; - os << ", \"conn_id\": " << conn_id; - os << ", \"is_gatt_service_valid\": " - << (gatt_svc_validation_steps == 0 ? "\"True\"" : "\"False\"") << "(" - << +gatt_svc_validation_steps << ")"; - os << ", \"is_connecting_actively\": " - << (is_connecting_actively ? "\"True\"" : "\"False\""); - } -}; - -/* Build on top of the base GattServiceDevice extends the base device context - * with service specific informations such as the currently active preset, - * all available presets, and supported optional operations. It also stores - * HAS service specific GATT informations such as characteristic handles. - */ -class HasDevice : public GattServiceDevice { - uint8_t features = 0x00; - uint16_t supported_opcodes_bitmask = 0x0000; - - void RefreshSupportedOpcodesBitmask(void) { - supported_opcodes_bitmask = 0; - - /* Some opcodes are mandatory but the characteristics aren't - these are - * conditional then. - */ - if ((cp_handle != GAP_INVALID_HANDLE) && - (active_preset_handle != GAP_INVALID_HANDLE)) { - supported_opcodes_bitmask |= kControlPointMandatoryOpcodesBitmask; - } - - if (features & bluetooth::has::kFeatureBitPresetSynchronizationSupported) { - supported_opcodes_bitmask |= kControlPointMandatoryOpcodesBitmask; - supported_opcodes_bitmask |= kControlPointSynchronizedOpcodesBitmask; - } - - if (features & bluetooth::has::kFeatureBitWritablePresets) { - supported_opcodes_bitmask |= - PresetCtpOpcode2Bitmask(PresetCtpOpcode::WRITE_PRESET_NAME); - } - } - - public: - /* Char handle and current ccc value */ - uint16_t active_preset_handle = GAP_INVALID_HANDLE; - uint16_t active_preset_ccc_handle = GAP_INVALID_HANDLE; - uint16_t cp_handle = GAP_INVALID_HANDLE; - uint16_t cp_ccc_handle = GAP_INVALID_HANDLE; - uint8_t cp_ccc_val = 0; - uint16_t features_handle = GAP_INVALID_HANDLE; - uint16_t features_ccc_handle = GAP_INVALID_HANDLE; - - bool features_notifications_enabled = false; - - /* Presets in the ascending order of their indices */ - std::set<HasPreset, HasPreset::ComparatorDesc> has_presets; - uint8_t currently_active_preset = bluetooth::has::kHasPresetIndexInvalid; - - std::list<HasCtpNtf> ctp_notifications_; - HasJournal has_journal_; - - HasDevice(const RawAddress& addr, uint8_t features) - : GattServiceDevice(addr) { - UpdateFeatures(features); - } - - void ConnectionCleanUp() { - conn_id = GATT_INVALID_CONN_ID; - is_connecting_actively = false; - ctp_notifications_.clear(); - } - - using GattServiceDevice::GattServiceDevice; - - uint8_t GetFeatures() const { return features; } - - void UpdateFeatures(uint8_t new_features) { - features = new_features; - /* Update the dependent supported feature set */ - RefreshSupportedOpcodesBitmask(); - } - - void ClearSvcData() { - GattServiceDevice::service_handle = GAP_INVALID_HANDLE; - GattServiceDevice::gatt_svc_validation_steps = 0xFE; - - active_preset_handle = GAP_INVALID_HANDLE; - active_preset_ccc_handle = GAP_INVALID_HANDLE; - cp_handle = GAP_INVALID_HANDLE; - cp_ccc_handle = GAP_INVALID_HANDLE; - features_handle = GAP_INVALID_HANDLE; - features_ccc_handle = GAP_INVALID_HANDLE; - - features = 0; - features_notifications_enabled = false; - - supported_opcodes_bitmask = 0x00; - currently_active_preset = bluetooth::has::kHasPresetIndexInvalid; - - has_presets.clear(); - } - - inline bool SupportsPresets() const { - return (active_preset_handle != GAP_INVALID_HANDLE) && - (cp_handle != GAP_INVALID_HANDLE); - } - - inline bool SupportsActivePresetNotification() const { - return active_preset_ccc_handle != GAP_INVALID_HANDLE; - } - - inline bool SupportsFeaturesNotification() const { - return features_ccc_handle != GAP_INVALID_HANDLE; - } - - inline bool HasFeaturesNotificationEnabled() const { - return features_notifications_enabled; - } - - inline bool SupportsOperation(PresetCtpOpcode op) { - auto mask = PresetCtpOpcode2Bitmask(op); - return (supported_opcodes_bitmask & mask) == mask; - } - - bool IsValidPreset(uint8_t preset_index, bool writable_only = false) const { - if (has_presets.count(preset_index)) { - return writable_only ? has_presets.find(preset_index)->IsWritable() - : true; - } - return false; - } - - const HasPreset* GetPreset(uint8_t preset_index, - bool writable_only = false) const { - if (has_presets.count(preset_index)) { - decltype(has_presets)::iterator preset = has_presets.find(preset_index); - if (writable_only) return preset->IsWritable() ? &*preset : nullptr; - return &*preset; - } - return nullptr; - } - - std::optional<bluetooth::has::PresetInfo> GetPresetInfo(uint8_t index) const { - if (has_presets.count(index)) { - auto preset = *has_presets.find(index); - return bluetooth::has::PresetInfo({.preset_index = preset.GetIndex(), - .writable = preset.IsWritable(), - .available = preset.IsAvailable(), - .preset_name = preset.GetName()}); - } - return std::nullopt; - } - - std::vector<bluetooth::has::PresetInfo> GetAllPresetInfo() const { - std::vector<bluetooth::has::PresetInfo> all_info; - all_info.reserve(has_presets.size()); - - for (auto const& preset : has_presets) { - DLOG(INFO) << __func__ << " preset: " << preset; - all_info.push_back({.preset_index = preset.GetIndex(), - .writable = preset.IsWritable(), - .available = preset.IsAvailable(), - .preset_name = preset.GetName()}); - } - return all_info; - } - - /* Calculates the buffer space that all the preset will use when serialized */ - uint8_t SerializedPresetsSize() const { - /* Two additional bytes are for the header and the number of presets */ - return std::accumulate(has_presets.begin(), has_presets.end(), 0, - [](uint8_t current, auto const& preset) { - return current + preset.SerializedSize(); - }) + - 2; - } - - /* Serializes all the presets into a binary blob for persistent storage */ - bool SerializePresets(std::vector<uint8_t>& out) const { - auto buffer_size = SerializedPresetsSize(); - auto buffer_offset = out.size(); - - out.resize(out.size() + buffer_size); - auto p_out = out.data() + buffer_offset; - - UINT8_TO_STREAM(p_out, kHasDeviceBinaryBlobHdr); - UINT8_TO_STREAM(p_out, has_presets.size()); - - auto* const p_end = p_out + buffer_size; - for (auto& preset : has_presets) { - if (p_out + preset.SerializedSize() >= p_end) { - LOG(ERROR) << "Serialization error."; - return false; - } - p_out = preset.Serialize(p_out, p_end - p_out); - } - - return true; - } - - /* Deserializes all the presets from a binary blob read from the persistent - * storage. - */ - static bool DeserializePresets(const uint8_t* p_in, size_t len, - HasDevice& device) { - HasPreset preset; - if (len < 2 + preset.SerializedSize()) { - LOG(ERROR) << "Deserialization error. Invalid input buffer size length."; - return false; - } - auto* p_end = p_in + len; - - uint8_t hdr; - STREAM_TO_UINT8(hdr, p_in); - if (hdr != kHasDeviceBinaryBlobHdr) { - LOG(ERROR) << __func__ << " Deserialization error. Bad header."; - return false; - } - - uint8_t num_presets; - STREAM_TO_UINT8(num_presets, p_in); - - device.has_presets.clear(); - while (p_in < p_end) { - auto* p_new = HasPreset::Deserialize(p_in, p_end - p_in, preset); - if (p_new <= p_in) { - LOG(ERROR) << "Deserialization error. Invalid preset found."; - device.has_presets.clear(); - return false; - } - - device.has_presets.insert(preset); - p_in = p_new; - } - - return device.has_presets.size() == num_presets; - } - - friend std::ostream& operator<<(std::ostream& os, const HasDevice& b); - - void Dump(std::ostream& os) const { - GattServiceDevice::Dump(os); - os << ", \"features\": \"" << loghex(features) << "\""; - os << ", \"features_notifications_enabled\": " - << (features_notifications_enabled ? "\"Enabled\"" : "\"Disabled\""); - os << ", \"ctp_notifications size\": " << ctp_notifications_.size(); - os << ",\n"; - - os << " " - << "\"presets\": ["; - for (auto const& preset : has_presets) { - os << "\n " << preset << ","; - } - os << "\n ],\n"; - - os << " " - << "\"Ctp. notifications process queue\": {"; - if (ctp_notifications_.size() != 0) { - size_t ntf_pos = 0; - for (auto const& ntf : ctp_notifications_) { - os << "\n "; - if (ntf_pos == 0) { - os << "\"latest\": "; - } else { - os << "\"-" << ntf_pos << "\": "; - } - - os << ntf << ","; - ++ntf_pos; - } - } - os << "\n },\n"; - - os << " " - << "\"event history\": {"; - size_t pos = 0; - for (auto const& record : has_journal_) { - os << "\n "; - if (pos == 0) { - os << "\"latest\": "; - } else { - os << "\"-" << pos << "\": "; - } - - os << record << ","; - ++pos; - } - os << "\n }"; - } - - private: - static constexpr int kHasDeviceBinaryBlobHdr = 0x55; -}; - -} // namespace has -} // namespace le_audio diff --git a/bta/include/bta_has_api.h b/bta/include/bta_has_api.h deleted file mode 100644 index ae9cc5255..000000000 --- a/bta/include/bta_has_api.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2021 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <base/callback.h> - -#include <string> - -#include "hardware/bt_has.h" - -namespace le_audio { -namespace has { -class HasClient { - public: - virtual ~HasClient() = default; - - static void Initialize(bluetooth::has::HasClientCallbacks* callbacks, - base::Closure initCb); - static void CleanUp(); - static HasClient* Get(); - static void DebugDump(int fd); - static bool IsHasClientRunning(); - static void AddFromStorage(const RawAddress& addr, uint8_t features, - uint16_t is_acceptlisted); - virtual void Connect(const RawAddress& addr) = 0; - virtual void Disconnect(const RawAddress& addr) = 0; - virtual void SelectActivePreset( - std::variant<RawAddress, int> addr_or_group_id, uint8_t preset_index) = 0; - virtual void NextActivePreset( - std::variant<RawAddress, int> addr_or_group_id) = 0; - virtual void PreviousActivePreset( - std::variant<RawAddress, int> addr_or_group_id) = 0; - virtual void GetPresetInfo(const RawAddress& addr, uint8_t preset_index) = 0; - virtual void SetPresetName(std::variant<RawAddress, int> addr_or_group_id, - uint8_t preset_index, std::string name) = 0; -}; - -} // namespace has -} // namespace le_audio diff --git a/btif/Android.bp b/btif/Android.bp index bf86d2902..b28016b99 100644 --- a/btif/Android.bp +++ b/btif/Android.bp @@ -109,7 +109,6 @@ cc_library_static { "src/btif_gatt_test.cc", "src/btif_gatt_util.cc", "src/btif_hearing_aid.cc", - "src/btif_has_client.cc", "src/btif_hf.cc", "src/btif_hf_client.cc", "src/btif_hh.cc", @@ -163,6 +162,7 @@ cc_library_static { "libcrypto", ], whole_static_libs: [ + "libPlatformProperties", "libbt-audio-hal-interface-qti", "libbt-common-qti", //"libbtif_ext", diff --git a/btif/include/btif_ahim.h b/btif/include/btif_ahim.h index 8ecc3516c..9ba613fa1 100644 --- a/btif/include/btif_ahim.h +++ b/btif/include/btif_ahim.h @@ -28,8 +28,7 @@ * ******************************************************************************/ /* - * Changes from Qualcomm Innovation Center are provided under the following license: - * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the @@ -193,8 +192,6 @@ void btif_ahim_update_current_profile(uint8_t profile); void btif_ahim_update_src_metadata(const source_metadata_t& source_metadata); void btif_ahim_update_sink_metadata(const sink_metadata_t& sink_metadata); -void btif_ahim_set_latency_mode(bool is_low_latency); - bool btif_ahim_init_hal(thread_t *t, uint8_t profile); void btif_ahim_cleanup_hal(uint8_t profile); diff --git a/btif/include/btif_storage.h b/btif/include/btif_storage.h index 667711dbc..0608ce603 100644 --- a/btif/include/btif_storage.h +++ b/btif/include/btif_storage.h @@ -294,17 +294,6 @@ bool btif_storage_get_hearing_aid_prop( /** Store service changed CCCD value for remote client */ void btif_storage_set_svc_chg_cccd(const RawAddress& bd_addr, uint8_t cccd); -/** Loads information about bonded HAS devices */ -void btif_storage_load_bonded_leaudio_has_devices(void); - -/** Deletes the bonded HAS device info from NVRAM */ -void btif_storage_remove_leaudio_has(const RawAddress& address); - -/** Set/Unset the HAS device acceptlist flag. */ -void btif_storage_set_leaudio_has_acceptlist(const RawAddress& address, - bool add_to_acceptlist); - - /** Get service changed CCCD value for remote client */ uint8_t btif_storage_get_svc_chg_cccd(const RawAddress& bda); diff --git a/btif/src/bluetooth.cc b/btif/src/bluetooth.cc index b93e15b3f..65b69eb0b 100644 --- a/btif/src/bluetooth.cc +++ b/btif/src/bluetooth.cc @@ -46,7 +46,6 @@ #include <hardware/bt_hd.h> #include <hardware/bt_hf.h> #include <hardware/bt_hearing_aid.h> -#include <hardware/bt_has.h> #include <hardware/bt_hf_client.h> #ifdef DIR_FINDING_FEATURE #include <hardware/bt_atp_locator.h> @@ -67,7 +66,6 @@ #include <hardware/bt_vendor_rc.h> #include "bt_utils.h" #include "bta_sys.h" -#include "bta/include/bta_has_api.h" #include "bta/include/bta_hearing_aid_api.h" #include "bta/include/bta_hf_client_api.h" #include "btif/include/btif_debug_btsnoop.h" @@ -100,7 +98,6 @@ using base::Bind; using bluetooth::hearing_aid::HearingAidInterface; -using bluetooth::has::HasClientInterface; #ifdef DIR_FINDING_FEATURE using bluetooth::atp_locator::AtpLocatorInterface; #endif @@ -147,9 +144,6 @@ extern btsdp_interface_t* btif_sdp_get_interface(); /*Hearing Aid client*/ extern HearingAidInterface* btif_hearing_aid_get_interface(); -/* Hearing Access client */ -extern HasClientInterface* btif_has_client_get_interface(); - /* List all test interface here */ /* vendor */ extern btvendor_interface_t *btif_vendor_get_interface(); @@ -438,7 +432,6 @@ static void dump(int fd, const char** arguments) { osi_allocator_debug_dump(fd); alarm_debug_dump(fd); HearingAid::DebugDump(fd); - le_audio::has::HasClient::DebugDump(fd); connection_manager::dump(fd); bluetooth::bqr::DebugDump(fd); #if (BTSNOOP_MEM == TRUE) @@ -519,9 +512,6 @@ static const void* get_profile_interface(const char* profile_id) { if (is_profile(profile_id, BT_PROFILE_HEARING_AID_ID)) return btif_hearing_aid_get_interface(); - if (is_profile(profile_id, BT_PROFILE_HAP_CLIENT_ID)) - return btif_has_client_get_interface(); - #ifdef DIR_FINDING_FEATURE if (is_profile(profile_id, BT_PROFILE_ATP_LOCATOR_ID)) return btif_atp_locator_get_interface(); diff --git a/btif/src/btif_a2dp_source.cc b/btif/src/btif_a2dp_source.cc index df89c6295..6f2bd4224 100644 --- a/btif/src/btif_a2dp_source.cc +++ b/btif/src/btif_a2dp_source.cc @@ -72,7 +72,9 @@ using ::bluetooth::audio::a2dp::SessionType; #include "btif_hf.h" #include "btif_av.h" #include "bta_sys.h" +#ifdef ADV_AUDIO_FEATURE #include "btif_acm.h" +#endif using system_bt_osi::BluetoothMetricsLogger; using system_bt_osi::A2dpSessionMetrics; @@ -181,8 +183,11 @@ extern int btif_av_get_tws_pair_idx(int index); extern void btif_av_clear_pending_start_flag(); extern bool btif_av_is_tws_suspend_triggered(int index); +#ifdef ADV_AUDIO_FEATURE extern bool btif_acm_check_in_call_tracker_timer_exist(); extern void stop_stream_acm_initiator_now(); +#endif + extern bool btif_av_check_is_reconfig_pending_flag_set(RawAddress address); extern bool bt_split_a2dp_sink_enabled; @@ -1849,6 +1854,7 @@ void btif_a2dp_source_process_request(tA2DP_CTRL_CMD cmd) { status = A2DP_CTRL_ACK_INCALL_FAILURE; break; } +#ifdef ADV_AUDIO_FEATURE if (btif_ahim_is_aosp_aidl_hal_enabled() && (btif_acm_check_in_call_tracker_timer_exist() || (!btif_acm_get_is_inCall() && btif_acm_is_call_active()))) { @@ -1856,6 +1862,7 @@ void btif_a2dp_source_process_request(tA2DP_CTRL_CMD cmd) { status = A2DP_CTRL_ACK_INCALL_FAILURE; break; } +#endif if (btif_ba_is_active()) { ba_send_message(BTIF_BA_AUDIO_START_REQ_EVT, 0, NULL, false); status = A2DP_CTRL_ACK_PENDING; diff --git a/btif/src/btif_ahim.cc b/btif/src/btif_ahim.cc index 9c5e0cbfd..de201bba7 100644 --- a/btif/src/btif_ahim.cc +++ b/btif/src/btif_ahim.cc @@ -28,8 +28,7 @@ * ******************************************************************************/ /* - * Changes from Qualcomm Innovation Center are provided under the following license: - * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the @@ -74,10 +73,11 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE #include "audio_hal_interface/aidl/a2dp_encoding.h" #include "audio_hal_interface/aidl/le_audio_software.h" #include <hardware/audio.h> -#include <hardware/bt_apm.h> #include <vector> +#ifdef ADV_AUDIO_FEATURE #include <hardware/bt_pacs_client.h> #include <btif_vmcp.h> +#endif #include <aidl/vendor/qti/hardware/bluetooth/audio/LeAudioVendorConfiguration.h> #include <aidl/vendor/qti/hardware/bluetooth/audio/VendorCodecType.h> @@ -102,7 +102,9 @@ using VendorConfiguration = ::aidl::android::hardware::bluetooth::audio::LeAudioCodecConfiguration::VendorConfiguration; using ::aidl::android::hardware::bluetooth::audio::UnicastCapability; using vendor::qti::hardware::bluetooth_audio::V2_1::LC3ChannelMode; +#ifdef ADV_AUDIO_FEATURE using bluetooth::bap::pacs::CodecIndex; +#endif //using ::bluetooth::audio::BitsPerSample; LeAudioClientInterface* leAudioClientInterface = nullptr; LeAudioClientInterface::Sink* unicastSinkClientInterface = nullptr; @@ -196,10 +198,6 @@ void btif_ahim_update_current_profile(uint8_t profile) } void btif_ahim_process_request(tA2DP_CTRL_CMD cmd, uint8_t profile, uint8_t direction) { - if (btif_check_dual_mode()) { - btif_ahim_process_request_DM(cmd, profile, direction); - return; - } std::lock_guard<std::mutex> lock(active_profile_mtx); if (btif_ahim_is_aosp_aidl_hal_enabled()) { cur_active_profile = profile; @@ -236,60 +234,6 @@ void btif_ahim_process_request(tA2DP_CTRL_CMD cmd, uint8_t profile, } } -void btif_ahim_process_request_DM(tA2DP_CTRL_CMD cmd, uint8_t profile, - uint8_t direction) { - RawAddress peer_bda; - std::lock_guard<std::mutex> lock(active_profile_mtx); - switch(profile) { - case A2DP: - btif_av_get_active_peer_addr(&peer_bda); - LOG(INFO) << __func__ << ": Sending AIDL request to AV, " - << "Peer BDA: " << peer_bda << ", isEmpty: " << peer_bda.IsEmpty(); - if (!peer_bda.IsEmpty()) { - cur_active_profile = profile; - } - btif_dispatch_sm_event(BTIF_AV_PROCESS_HIDL_REQ_EVT, - (char*)&cmd, sizeof(cmd)); - break; - case AUDIO_GROUP_MGR: - btif_av_get_active_peer_addr(&peer_bda); - LOG(INFO) << __func__ << ": Sending AIDL request to Audio Group Manager, " - << "Peer BDA: " << peer_bda << ", isEmpty: " << peer_bda.IsEmpty(); - if (peer_bda.IsEmpty()) { - cur_active_profile = profile; - } - if (pclient_cbs[AUDIO_GROUP_MGR - 1] && - pclient_cbs[AUDIO_GROUP_MGR - 1]->client_cb) { - LOG(INFO) << __func__ << ": Calling call back for Audio Group Manager"; - pclient_cbs[AUDIO_GROUP_MGR - 1]->client_cb(cmd, direction); - } - else - BTIF_TRACE_ERROR("%s, Audio Group Manager is not registered with AHIM", - __func__); - break; - case BROADCAST: - btif_av_get_active_peer_addr(&peer_bda); - LOG(INFO) << __func__ << ": Sending AIDL request to BROADCAST, " - << "Peer BDA: " << peer_bda; - if (peer_bda.IsEmpty()) { - cur_active_profile = profile; - } - if (pclient_cbs[BROADCAST - 1] && - pclient_cbs[BROADCAST - 1]->client_cb) { - BTIF_TRACE_IMP("%s: calling call back for BROADCAST", __func__); - pclient_cbs[BROADCAST - 1]->client_cb(cmd, direction); - } - else - BTIF_TRACE_ERROR("%s, BROADCAST is not registered with AHIM", __func__); - break; - } -} - -void btif_ahim_set_latency_mode(bool is_low_latency) { - LOG(INFO) << __func__ << ", is_low_latency: " << is_low_latency; - btif_apm_set_latency_mode(is_low_latency); -} - void btif_ahim_update_src_metadata (const source_metadata_t& source_metadata) { auto track_count = source_metadata.track_count; auto usage = source_metadata.tracks->usage; @@ -298,32 +242,17 @@ void btif_ahim_update_src_metadata (const source_metadata_t& source_metadata) { << ", usage: " << usage; // pass on the callbacks to ACM only for new vendor - if (btif_ahim_is_aosp_aidl_hal_enabled()) { - LOG(INFO) << __func__ << ": Current Active Profile: " << loghex(cur_active_profile); - if(cur_active_profile == A2DP) { - if (btif_check_dual_mode()) { - std::unique_lock<std::mutex> guard(src_metadata_wait_mutex_); - src_metadata_wait = false; - btif_report_a2dp_src_metadata_update((source_metadata_t *)&source_metadata); - src_metadata_wait_cv.wait_for(guard, std::chrono::milliseconds(3200), - []{return src_metadata_wait;}); - LOG(INFO) << __func__ << ": A2DP Src waiting completed!!"; - } else { - btif_report_a2dp_src_metadata_update((source_metadata_t *)&source_metadata); - } - } else if(cur_active_profile == AUDIO_GROUP_MGR || - cur_active_profile == BROADCAST) { - LOG(INFO) << __func__ << ": Sending AIDL request to Audio Group Manager"; - if (pclient_cbs[AUDIO_GROUP_MGR - 1] && - pclient_cbs[AUDIO_GROUP_MGR - 1]->src_meta_update) { - LOG(INFO) << __func__ << ": Calling call back for Audio Group Manager"; - std::unique_lock<std::mutex> guard(src_metadata_wait_mutex_); - src_metadata_wait = false; - pclient_cbs[AUDIO_GROUP_MGR - 1]->src_meta_update(source_metadata); - src_metadata_wait_cv.wait_for(guard, std::chrono::milliseconds(3200), - []{return src_metadata_wait;}); - LOG(INFO) << __func__ << ": Src waiting completed"; - } + if(btif_ahim_is_aosp_aidl_hal_enabled()) { + BTIF_TRACE_IMP("%s: sending AIDL request to Audio Group Manager", __func__); + if (pclient_cbs[AUDIO_GROUP_MGR - 1] && + pclient_cbs[AUDIO_GROUP_MGR - 1]->src_meta_update) { + BTIF_TRACE_IMP("%s: calling call back for Audio Group Manager", __func__); + std::unique_lock<std::mutex> guard(src_metadata_wait_mutex_); + src_metadata_wait = false; + pclient_cbs[AUDIO_GROUP_MGR - 1]->src_meta_update(source_metadata); + src_metadata_wait_cv.wait_for(guard, std::chrono::milliseconds(3200), + []{return src_metadata_wait;}); + BTIF_TRACE_IMP("%s: src waiting completed", __func__); } } } @@ -367,7 +296,6 @@ bool btif_ahim_init_hal(thread_t *t, uint8_t profile) { std::lock_guard<std::mutex>lock(session_mtx); BTIF_TRACE_IMP("%s: AIDL", __func__); if (profile == A2DP) { - if (!btif_check_dual_mode()) { if (unicastSinkClientInterface != nullptr) { leAudioClientInterface->ReleaseSink(unicastSinkClientInterface); unicastSinkClientInterface = nullptr; @@ -380,7 +308,6 @@ bool btif_ahim_init_hal(thread_t *t, uint8_t profile) { leAudioClientInterface->ReleaseSink(broadcastSinkClientInterface); broadcastSinkClientInterface = nullptr; } - } return bluetooth::audio::aidl::a2dp::init(t); } else { if(leAudioClientInterface == nullptr) { @@ -613,6 +540,8 @@ LeAudioConfiguration fetch_offload_audio_config(int profile, int direction) { uint16_t type = pclient_cbs[profile - 1]->get_profile_status_cb(); uint16_t frame_duration = pclient_cbs[profile - 1]->get_frame_length_cb(direction); + +#ifdef ADV_AUDIO_FEATURE bool is_lc3q_supported = false; CodecIndex codec_type = (CodecIndex) pclient_cbs[profile - 1]->get_codec_type(direction); if (codec_type == CodecIndex::CODEC_INDEX_SOURCE_APTX_ADAPTIVE_LE || @@ -622,7 +551,6 @@ LeAudioConfiguration fetch_offload_audio_config(int profile, int direction) { << frame_duration << ", from leaudio_configs.xml"; } uint8_t encoder_version = 0; - if(1) { /*if (codec_type == CodecIndex::CODEC_INDEX_SOURCE_APTX_ADAPTIVE_LE || pclient_cbs[profile - 1]->get_is_codec_type_lc3q(direction)) {*/ VendorConfiguration vendor_config; @@ -740,7 +668,7 @@ LeAudioConfiguration fetch_offload_audio_config(int profile, int direction) { } } return ucast_config; - } else { +#else /* ADV_AUDIO_FEATURE */ // TODO to fill the right PD Lc3Configuration lc3_config = { .pcmBitDepth = 24, @@ -776,7 +704,7 @@ LeAudioConfiguration fetch_offload_audio_config(int profile, int direction) { }); } return ucast_config; - } +#endif /* ADV_AUDIO_FEATURE */ } bool leAudio_get_selected_hal_codec_config(AudioConfigurationAIDL *lea_config, @@ -813,6 +741,7 @@ bool btif_ahim_setup_codec(uint8_t profile) { uint16_t profile_type = btif_ahim_get_lea_active_profile(profile); BTIF_TRACE_IMP("%s: AIDL, profile_type: %d", __func__, profile_type); if(profile_type == BAP || profile_type == GCP) { // ToAIr only +#ifdef ADV_AUDIO_FEATURE CodecIndex codec_type = (CodecIndex) pclient_cbs[profile - 1]->get_codec_type(TX_ONLY_CONFIG); if (codec_type == CodecIndex::CODEC_INDEX_SOURCE_APTX_ADAPTIVE_R4) { if (!leAudio_get_selected_hal_codec_config(&lea_tx_config, profile, @@ -831,6 +760,7 @@ bool btif_ahim_setup_codec(uint8_t profile) { if(unicastSourceClientInterface) unicastSourceClientInterface->UpdateAudioConfigToHal(lea_rx_config); } else { +#endif if (!leAudio_get_selected_hal_codec_config(&lea_tx_config, profile, TX_ONLY_CONFIG)) { LOG(ERROR) << __func__ << ": Failed to get CodecConfiguration"; @@ -842,7 +772,9 @@ bool btif_ahim_setup_codec(uint8_t profile) { // TODO to fill both session/single session configs based on profile if(unicastSinkClientInterface) unicastSinkClientInterface->UpdateAudioConfigToHal(lea_tx_config); +#ifdef ADV_AUDIO_FEATURE } +#endif } else if(profile_type == BAP_CALL || profile_type == GCP_RX || profile_type == WMCP_TX) { // Toair and FromAir @@ -900,6 +832,7 @@ void btif_ahim_start_session(uint8_t profile) { btif_ahim_get_lea_active_profile(profile); BTIF_TRACE_IMP("%s: AIDL, profile_type: %d", __func__, profile_type); if(profile_type == BAP || profile_type == GCP) { // ToAIr only +#ifdef ADV_AUDIO_FEATURE CodecIndex codec_type = (CodecIndex) pclient_cbs[profile - 1]->get_codec_type(TX_ONLY_CONFIG); if (codec_type == CodecIndex::CODEC_INDEX_SOURCE_APTX_ADAPTIVE_R4) { if(unicastSinkClientInterface) @@ -907,9 +840,12 @@ void btif_ahim_start_session(uint8_t profile) { if(unicastSourceClientInterface) unicastSourceClientInterface->StartSession(); } else { +#endif if(unicastSinkClientInterface) unicastSinkClientInterface->StartSession(); +#ifdef ADV_AUDIO_FEATURE } +#endif } else if(profile_type == BAP_CALL || profile_type == GCP_RX || profile_type == WMCP_TX) { // Toair and FromAir @@ -1312,6 +1248,7 @@ size_t btif_ahim_read(uint8_t* p_buf, uint32_t len) { return bluetooth::audio::aidl::a2dp::read(p_buf, len); } +#ifdef ADV_AUDIO_FEATURE void btif_ahim_update_audio_config() { AudioConfigurationAIDL lea_tx_config, lea_rx_config; uint16_t profile_type = btif_ahim_get_lea_active_profile(AUDIO_GROUP_MGR); @@ -1380,6 +1317,7 @@ if(unicastSinkClientInterface) else return 0xFFFF; } +#endif void btif_ahim_set_remote_delay(uint16_t delay_report, uint8_t profile) { BTIF_TRACE_IMP("%s: AIDL, profile: %d, delay_report: %d", diff --git a/btif/src/btif_dm.cc b/btif/src/btif_dm.cc index 9eaf52471..87c56f9f3 100644 --- a/btif/src/btif_dm.cc +++ b/btif/src/btif_dm.cc @@ -74,6 +74,10 @@ #include "btif_dm.h" +#ifdef OS_ANDROID +#include <android/sysprop/BluetoothProperties.sysprop.h> +#endif + #include <base/bind.h> #include <base/logging.h> #include <signal.h> @@ -170,6 +174,9 @@ const Uuid UUID_HEARING_AID = Uuid::FromString("FDF0"); #define NUM_TIMEOUT_RETRIES 2 +#ifndef PROPERTY_DEFAULT_DEVICE_NAME +#define PROPERTY_DEFAULT_DEVICE_NAME "bluetooth.device.default_name" +#endif #define PROPERTY_PRODUCT_MODEL "ro.product.model" #define DEFAULT_LOCAL_NAME_MAX 31 #if (DEFAULT_LOCAL_NAME_MAX > BTM_MAX_LOC_BD_NAME_LEN) @@ -4483,9 +4490,23 @@ void btif_dm_read_energy_info() { BTA_DmBleGetEnergyInfo(bta_energy_info_cb); } static char* btif_get_default_local_name() { if (btif_default_local_name[0] == '\0') { int max_len = sizeof(btif_default_local_name) - 1; - if (BTM_DEF_LOCAL_NAME[0] != '\0') { - strncpy(btif_default_local_name, BTM_DEF_LOCAL_NAME, max_len); - } else { + + // Use the stable sysprop on Android devices, otherwise use osi_property_get +#ifdef OS_ANDROID + std::optional<std::string> default_local_name = + android::sysprop::BluetoothProperties::getDefaultDeviceName(); + if (default_local_name.has_value() && default_local_name.value() != "") { + strncpy(btif_default_local_name, default_local_name.value().c_str(), + max_len); + } +#else + char prop_name[PROPERTY_VALUE_MAX]; + osi_property_get(PROPERTY_DEFAULT_DEVICE_NAME, prop_name, ""); + strncpy(btif_default_local_name, prop_name, max_len); +#endif + + // If no value was placed in the btif_default_local_name then use model name + if (btif_default_local_name[0] == '\0') { char prop_model[PROPERTY_VALUE_MAX]; osi_property_get(PROPERTY_PRODUCT_MODEL, prop_model, ""); strncpy(btif_default_local_name, prop_model, max_len); diff --git a/btif/src/btif_has_client.cc b/btif/src/btif_has_client.cc deleted file mode 100644 index bbbe172ca..000000000 --- a/btif/src/btif_has_client.cc +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright 2021 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <base/bind.h> -#include <base/bind_helpers.h> -#include <base/location.h> -#include <base/logging.h> -#include <hardware/bluetooth.h> -#include <hardware/bt_has.h> -#include "bta_closure_api.h" - -#include <bitset> -#include <string> -#include <vector> - -#include "bta_has_api.h" -#include "btif_common.h" -#include "btif_storage.h" -#include "stack/include/btu.h" - -using base::Bind; -using base::Owned; -using base::Passed; -using base::Unretained; -using bluetooth::has::ConnectionState; -using bluetooth::has::ErrorCode; -using bluetooth::has::HasClientCallbacks; -using bluetooth::has::HasClientInterface; -using bluetooth::has::PresetInfo; -using bluetooth::has::PresetInfoReason; - -using le_audio::has::HasClient; - -namespace { -std::unique_ptr<HasClientInterface> has_client_instance; - -class HearingAaccessClientServiceInterfaceImpl : public HasClientInterface, - public HasClientCallbacks { - ~HearingAaccessClientServiceInterfaceImpl() override = default; - - void Init(HasClientCallbacks* callbacks) override { - DVLOG(2) << __func__; - this->callbacks_ = callbacks; - - do_in_bta_thread( - FROM_HERE, - Bind(&HasClient::Initialize, this, - jni_thread_wrapper( - FROM_HERE, - Bind(&btif_storage_load_bonded_leaudio_has_devices)))); - } - - void Connect(const RawAddress& addr) override { - DVLOG(2) << __func__ << " addr: " << addr; - do_in_bta_thread(FROM_HERE, Bind(&HasClient::Connect, - Unretained(HasClient::Get()), addr)); - - do_in_jni_thread( - FROM_HERE, Bind(&btif_storage_set_leaudio_has_acceptlist, addr, true)); - } - - void Disconnect(const RawAddress& addr) override { - DVLOG(2) << __func__ << " addr: " << addr; - do_in_bta_thread(FROM_HERE, Bind(&HasClient::Disconnect, - Unretained(HasClient::Get()), addr)); - - do_in_jni_thread( - FROM_HERE, Bind(&btif_storage_set_leaudio_has_acceptlist, addr, false)); - } - - void SelectActivePreset(std::variant<RawAddress, int> addr_or_group_id, - uint8_t preset_index) override { - DVLOG(2) << __func__ << " preset_index: " << preset_index; - - do_in_bta_thread( - FROM_HERE, - Bind(&HasClient::SelectActivePreset, Unretained(HasClient::Get()), - std::move(addr_or_group_id), preset_index)); - } - - void NextActivePreset( - std::variant<RawAddress, int> addr_or_group_id) override { - DVLOG(2) << __func__; - - do_in_bta_thread(FROM_HERE, Bind(&HasClient::NextActivePreset, - Unretained(HasClient::Get()), - std::move(addr_or_group_id))); - } - - void PreviousActivePreset( - std::variant<RawAddress, int> addr_or_group_id) override { - DVLOG(2) << __func__; - - do_in_bta_thread(FROM_HERE, Bind(&HasClient::PreviousActivePreset, - Unretained(HasClient::Get()), - std::move(addr_or_group_id))); - } - - void GetPresetInfo(const RawAddress& addr, uint8_t preset_index) override { - DVLOG(2) << __func__ << " addr: " << addr - << " preset_index: " << preset_index; - - do_in_bta_thread( - FROM_HERE, Bind(&HasClient::GetPresetInfo, Unretained(HasClient::Get()), - addr, preset_index)); - } - - void SetPresetName(std::variant<RawAddress, int> addr_or_group_id, - uint8_t preset_index, std::string preset_name) override { - DVLOG(2) << __func__ << " preset_index: " << preset_index - << " preset_name: " << preset_name; - - do_in_bta_thread( - FROM_HERE, Bind(&HasClient::SetPresetName, Unretained(HasClient::Get()), - std::move(addr_or_group_id), preset_index, - std::move(preset_name))); - } - - void RemoveDevice(const RawAddress& addr) override { - DVLOG(2) << __func__ << " addr: " << addr; - - /* RemoveDevice can be called on devices that don't have BAS enabled */ - if (HasClient::IsHasClientRunning()) { - do_in_bta_thread(FROM_HERE, Bind(&HasClient::Disconnect, - Unretained(HasClient::Get()), addr)); - } - - do_in_jni_thread(FROM_HERE, Bind(&btif_storage_remove_leaudio_has, addr)); - } - - void Cleanup(void) override { - DVLOG(2) << __func__; - do_in_bta_thread(FROM_HERE, Bind(&HasClient::CleanUp)); - } - - void OnConnectionState(ConnectionState state, - const RawAddress& addr) override { - DVLOG(2) << __func__ << " addr: " << addr; - do_in_jni_thread(FROM_HERE, Bind(&HasClientCallbacks::OnConnectionState, - Unretained(callbacks_), state, addr)); - } - - void OnDeviceAvailable(const RawAddress& addr, uint8_t features) override { - DVLOG(2) << __func__ << " addr: " << addr << " features: " << features; - - do_in_jni_thread(FROM_HERE, Bind(&HasClientCallbacks::OnDeviceAvailable, - Unretained(callbacks_), addr, features)); - } - - void OnFeaturesUpdate(const RawAddress& addr, uint8_t features) override { - DVLOG(2) << __func__ << " addr: " << addr - << " ha_features: " << std::bitset<8>(features); - - do_in_jni_thread(FROM_HERE, Bind(&HasClientCallbacks::OnFeaturesUpdate, - Unretained(callbacks_), addr, features)); - } - - void OnActivePresetSelected(std::variant<RawAddress, int> addr_or_group_id, - uint8_t preset_index) override { - DVLOG(2) << __func__ << " preset_index: " << preset_index; - - do_in_jni_thread(FROM_HERE, - Bind(&HasClientCallbacks::OnActivePresetSelected, - Unretained(callbacks_), std::move(addr_or_group_id), - preset_index)); - } - - void OnActivePresetSelectError(std::variant<RawAddress, int> addr_or_group_id, - ErrorCode result_code) override { - DVLOG(2) << __func__ << " result_code: " - << static_cast<std::underlying_type<ErrorCode>::type>(result_code); - - do_in_jni_thread( - FROM_HERE, - Bind(&HasClientCallbacks::OnActivePresetSelectError, - Unretained(callbacks_), std::move(addr_or_group_id), result_code)); - } - - void OnPresetInfo(std::variant<RawAddress, int> addr_or_group_id, - PresetInfoReason change_id, - std::vector<PresetInfo> detail_records) override { - DVLOG(2) << __func__; - for (const auto& rec : detail_records) { - DVLOG(2) << "\t index: " << +rec.preset_index << ", change_id: " - << (std::underlying_type<PresetInfoReason>::type)change_id - << ", writable: " << rec.writable - << ", available: " << rec.available - << ", name: " << rec.preset_name; - } - - do_in_jni_thread(FROM_HERE, - Bind(&HasClientCallbacks::OnPresetInfo, - Unretained(callbacks_), std::move(addr_or_group_id), - change_id, std::move(detail_records))); - } - - void OnPresetInfoError(std::variant<RawAddress, int> addr_or_group_id, - uint8_t preset_index, ErrorCode result_code) override { - DVLOG(2) << __func__ << " result_code: " - << static_cast<std::underlying_type<ErrorCode>::type>(result_code); - - do_in_jni_thread( - FROM_HERE, - Bind(&HasClientCallbacks::OnPresetInfoError, Unretained(callbacks_), - std::move(addr_or_group_id), preset_index, result_code)); - } - - void OnSetPresetNameError(std::variant<RawAddress, int> addr_or_group_id, - uint8_t preset_index, - ErrorCode result_code) override { - DVLOG(2) << __func__ << " result_code: " - << static_cast<std::underlying_type<ErrorCode>::type>(result_code); - - do_in_jni_thread( - FROM_HERE, - Bind(&HasClientCallbacks::OnSetPresetNameError, Unretained(callbacks_), - std::move(addr_or_group_id), preset_index, result_code)); - } - - private: - HasClientCallbacks* callbacks_; -}; - -} /* namespace */ - -HasClientInterface* btif_has_client_get_interface(void) { - if (!has_client_instance) - has_client_instance.reset(new HearingAaccessClientServiceInterfaceImpl()); - - return has_client_instance.get(); -} diff --git a/btif/src/btif_storage.cc b/btif/src/btif_storage.cc index 98e58ab73..f2787e9be 100644 --- a/btif/src/btif_storage.cc +++ b/btif/src/btif_storage.cc @@ -55,7 +55,6 @@ #include "bta_closure_api.h" #include "bta_hd_api.h" #include "bta_hearing_aid_api.h" -#include "bta_has_api.h" #include "bta_hh_api.h" #include "btif_api.h" #include "btif_config.h" @@ -2539,142 +2538,6 @@ void btif_storage_remove_svc_chg_cccd(const RawAddress& bd_addr) { bd_addr)); } -constexpr char HAS_IS_ACCEPTLISTED[] = "LeAudioHasIsAcceptlisted"; -constexpr char HAS_FEATURES[] = "LeAudioHasFlags"; -constexpr char HAS_ACTIVE_PRESET[] = "LeAudioHasActivePreset"; -constexpr char HAS_SERIALIZED_PRESETS[] = "LeAudioHasSerializedPresets"; - -void btif_storage_add_leaudio_has_device(const RawAddress& address, - std::vector<uint8_t> presets_bin, - uint8_t features, - uint8_t active_preset) { - do_in_jni_thread( - FROM_HERE, - Bind( - [](const RawAddress& address, std::vector<uint8_t> presets_bin, - uint8_t features, uint8_t active_preset) { - const std::string& name = address.ToString(); - - btif_config_set_int(name.c_str(), HAS_FEATURES, features); - btif_config_set_int(name.c_str(), HAS_ACTIVE_PRESET, active_preset); - btif_config_set_bin(name.c_str(), HAS_SERIALIZED_PRESETS, - presets_bin.data(), presets_bin.size()); - - btif_config_set_int(name.c_str(), HAS_IS_ACCEPTLISTED, true); - btif_config_save(); - }, - address, std::move(presets_bin), features, active_preset)); -} - -void btif_storage_set_leaudio_has_active_preset(const RawAddress& address, - uint8_t active_preset) { - do_in_jni_thread(FROM_HERE, - Bind( - [](const RawAddress& address, uint8_t active_preset) { - const std::string& name = address.ToString(); - - btif_config_set_int(name.c_str(), HAS_ACTIVE_PRESET, - active_preset); - btif_config_save(); - }, - address, active_preset)); -} - -bool btif_storage_get_leaudio_has_features(const RawAddress& address, - uint8_t& features) { - std::string name = address.ToString(); - - int value; - if (!btif_config_get_int(name.c_str(), HAS_FEATURES, &value)) return false; - - features = value; - return true; -} - -void btif_storage_set_leaudio_has_features(const RawAddress& address, - uint8_t features) { - do_in_jni_thread(FROM_HERE, - Bind( - [](const RawAddress& address, uint8_t features) { - const std::string& name = address.ToString(); - - btif_config_set_int(name.c_str(), HAS_FEATURES, features); - btif_config_save(); - }, - address, features)); -} - -void btif_storage_load_bonded_leaudio_has_devices() { -/* for (const auto& bd_addr : btif_config_get_paired_devices()) {*/ - for (const auto& bd_addr : btif_config_get_paired_devices()) { - const std::string& name = bd_addr.ToString(); - - if (!btif_config_exist(name, HAS_IS_ACCEPTLISTED) && - !btif_config_exist(name, HAS_FEATURES)) - continue; - - int value; - uint16_t is_acceptlisted = 0; - if (btif_config_get_int(name, HAS_IS_ACCEPTLISTED, &value)) - is_acceptlisted = value; - - uint8_t features = 0; - if (btif_config_get_int(name, HAS_FEATURES, &value)) features = value; - do_in_bta_thread(FROM_HERE, Bind(&le_audio::has::HasClient::AddFromStorage, - bd_addr, features, is_acceptlisted)); - } -} - -void btif_storage_remove_leaudio_has(const RawAddress& address) { - std::string addrstr = address.ToString(); - btif_config_remove(addrstr.c_str(), HAS_IS_ACCEPTLISTED); - btif_config_remove(addrstr.c_str(), HAS_FEATURES); - btif_config_remove(addrstr.c_str(), HAS_ACTIVE_PRESET); - btif_config_remove(addrstr.c_str(), HAS_SERIALIZED_PRESETS); - btif_config_save(); -} - -void btif_storage_set_leaudio_has_acceptlist(const RawAddress& address, - bool add_to_acceptlist) { - std::string addrstr = address.ToString(); - - btif_config_set_int(addrstr.c_str(), HAS_IS_ACCEPTLISTED, add_to_acceptlist); - btif_config_save(); -} - -void btif_storage_set_leaudio_has_presets(const RawAddress& address, - std::vector<uint8_t> presets_bin) { - do_in_jni_thread( - FROM_HERE, - Bind( - [](const RawAddress& address, std::vector<uint8_t> presets_bin) { - const std::string& name = address.ToString(); - - btif_config_set_bin(name.c_str(), HAS_SERIALIZED_PRESETS, - presets_bin.data(), presets_bin.size()); - btif_config_save(); - }, - address, std::move(presets_bin))); -} - -bool btif_storage_get_leaudio_has_presets(const RawAddress& address, - std::vector<uint8_t>& presets_bin, - uint8_t& active_preset) { - std::string name = address.ToString(); - - int value; - if (!btif_config_get_int(name.c_str(), HAS_ACTIVE_PRESET, &value)) return false; - active_preset = value; - - auto bin_sz = btif_config_get_bin_length(name.c_str(), HAS_SERIALIZED_PRESETS); - presets_bin.resize(bin_sz); - if (!btif_config_get_bin(name.c_str(), HAS_SERIALIZED_PRESETS, presets_bin.data(), - &bin_sz)) - return false; - - return true; -} - void btif_storage_set_encr_data_cccd(const RawAddress& bd_addr, uint8_t cccd) { do_in_jni_thread(FROM_HERE, Bind( [](const RawAddress& bd_addr, uint8_t cccd) { @@ -2777,4 +2640,3 @@ bt_status_t btif_storage_remove_enc_key_material(const RawAddress* remote_bd_add btif_config_save(); return ret ? BT_STATUS_SUCCESS : BT_STATUS_FAIL; } - diff --git a/include/hardware/bluetooth.h b/include/hardware/bluetooth.h index b1c85e010..5185cc8d2 100644 --- a/include/hardware/bluetooth.h +++ b/include/hardware/bluetooth.h @@ -51,7 +51,6 @@ #define BT_PROFILE_AV_RC_ID "avrcp" #define BT_PROFILE_AV_RC_CTRL_ID "avrcp_ctrl" #define BT_PROFILE_HEARING_AID_ID "hearing_aid" -#define BT_PROFILE_HAP_CLIENT_ID "has_client" #define BT_KEYSTORE_ID "bluetooth_keystore" /** Bluetooth Device Name */ diff --git a/include/hardware/bt_has.h b/include/hardware/bt_has.h deleted file mode 100644 index 739edc041..000000000 --- a/include/hardware/bt_has.h +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2021 HIMSA II K/S - www.himsa.com. - * Represented by EHIMA - www.ehima.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include <hardware/bluetooth.h> - -#include <variant> -#include <vector> - -namespace bluetooth { -namespace has { - -/** Connection State */ -enum class ConnectionState : uint8_t { - DISCONNECTED = 0, - CONNECTING, - CONNECTED, - DISCONNECTING, -}; - -/** Results codes for the failed preset operations */ -enum class ErrorCode : uint8_t { - NO_ERROR = 0, - SET_NAME_NOT_ALLOWED, // Preset cannot be written (read only preset) - OPERATION_NOT_SUPPORTED, // If theres no optional characteristic, - // or request opcode is invalid or not supported - OPERATION_NOT_POSSIBLE, // Operation cannot be performed at this time - INVALID_PRESET_NAME_LENGTH, - INVALID_PRESET_INDEX, - GROUP_OPERATION_NOT_SUPPORTED, - PROCEDURE_ALREADY_IN_PROGRESS, -}; - -enum class PresetInfoReason : uint8_t { - ALL_PRESET_INFO = 0, - PRESET_INFO_UPDATE, - PRESET_DELETED, - PRESET_AVAILABILITY_CHANGED, - PRESET_INFO_REQUEST_RESPONSE, -}; - -struct PresetInfo { - uint8_t preset_index; - - bool writable; - bool available; - std::string preset_name; -}; - -/** Service supported feature bits */ -static constexpr uint8_t kFeatureBitHearingAidTypeBinaural = 0x00; -static constexpr uint8_t kFeatureBitHearingAidTypeMonaural = 0x01; -static constexpr uint8_t kFeatureBitHearingAidTypeBanded = 0x02; -static constexpr uint8_t kFeatureBitPresetSynchronizationSupported = 0x04; -static constexpr uint8_t kFeatureBitIndependentPresets = 0x08; -static constexpr uint8_t kFeatureBitDynamicPresets = 0x10; -static constexpr uint8_t kFeatureBitWritablePresets = 0x20; - -/** Invalid values for the group and preset identifiers */ -static constexpr uint8_t kHasPresetIndexInvalid = 0x00; -static constexpr int kHasGroupIdInvalid = -1; - -class HasClientCallbacks { - public: - virtual ~HasClientCallbacks() = default; - - /** Callback for profile connection state change */ - virtual void OnConnectionState(ConnectionState state, - const RawAddress& addr) = 0; - - /** Callback for the new available device */ - virtual void OnDeviceAvailable(const RawAddress& addr, uint8_t features) = 0; - - /** Callback for getting device HAS flags */ - virtual void OnFeaturesUpdate(const RawAddress& addr, uint8_t features) = 0; - - /** Callback for the currently active preset */ - virtual void OnActivePresetSelected( - std::variant<RawAddress, int> addr_or_group_id, uint8_t preset_index) = 0; - - /** Callbacks for the active preset selection error */ - virtual void OnActivePresetSelectError( - std::variant<RawAddress, int> addr_or_group_id, ErrorCode error_code) = 0; - - /** Callbacks for the preset details event */ - virtual void OnPresetInfo(std::variant<RawAddress, int> addr_or_group_id, - PresetInfoReason change_id, - std::vector<PresetInfo> info_records) = 0; - - /** Callback for the preset details get error */ - virtual void OnPresetInfoError(std::variant<RawAddress, int> addr_or_group_id, - uint8_t preset_index, - ErrorCode error_code) = 0; - - /** Callback for the preset name set error */ - virtual void OnSetPresetNameError( - std::variant<RawAddress, int> addr_or_group_id, uint8_t preset_index, - ErrorCode error_code) = 0; -}; - -class HasClientInterface { - public: - virtual ~HasClientInterface() = default; - - /** Register the Hearing Aid Service Client profile callbacks */ - virtual void Init(HasClientCallbacks* callbacks) = 0; - - /** Connect to HAS service */ - virtual void Connect(const RawAddress& addr) = 0; - - /** Disconnect from HAS service */ - virtual void Disconnect(const RawAddress& addr) = 0; - - /** Select preset by the index as currently active */ - virtual void SelectActivePreset( - std::variant<RawAddress, int> addr_or_group_id, uint8_t preset_index) = 0; - - /** Select next preset as currently active */ - virtual void NextActivePreset( - std::variant<RawAddress, int> addr_or_group_id) = 0; - - /** Select previous preset as currently active */ - virtual void PreviousActivePreset( - std::variant<RawAddress, int> addr_or_group_id) = 0; - - /** Get preset name by the index */ - virtual void GetPresetInfo(const RawAddress& addr, uint8_t preset_index) = 0; - - /** Set preset name by the index */ - virtual void SetPresetName(std::variant<RawAddress, int> addr_or_group_id, - uint8_t preset_index, std::string name) = 0; - - /** Called when HAS capable device is unbonded */ - virtual void RemoveDevice(const RawAddress& addr) = 0; - - /** Closes the interface */ - virtual void Cleanup(void) = 0; -}; - -} // namespace has -} // namespace bluetooth diff --git a/internal_include/bt_target.h b/internal_include/bt_target.h index ac594ff68..ef07f9743 100644 --- a/internal_include/bt_target.h +++ b/internal_include/bt_target.h @@ -67,6 +67,7 @@ #include "bdroid_buildcfg.h" #endif +#include <hardware/bt_av.h> #include "bt_types.h" /* This must be defined AFTER buildcfg.h */ //------------------Added from bdroid_buildcfg.h--------------------- @@ -467,13 +468,6 @@ #define BTM_MAX_LOC_BD_NAME_LEN 248 #endif -/* Fixed Default String. When this is defined as null string, the device's - * product model name is used as the default local name. - */ -#ifndef BTM_DEF_LOCAL_NAME -#define BTM_DEF_LOCAL_NAME "" -#endif - /* Maximum service name stored with security authorization (0 if not needed) */ #ifndef BTM_SEC_SERVICE_NAME_LEN #define BTM_SEC_SERVICE_NAME_LEN BT_MAX_SERVICE_NAME_LEN |