/*
* 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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_csis_client.h"
#include "stack/include/bt_uuid16.h"
#include "stack/include/btm_status.h"
#include "test/common/mock_functions.h"
#include "types/bt_transport.h"
// TODO(b/369381361) Enfore -Wmissing-prototypes
#pragma GCC diagnostic ignored "-Wmissing-prototypes"
bool gatt_profile_get_eatt_support(const RawAddress& /*addr*/) { return true; }
void osi_property_set_bool(const char* key, bool value);
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 ::bluetooth::le_audio::has::HasClient;
using ::bluetooth::le_audio::has::HasCtpGroupOpCoordinator;
using ::bluetooth::le_audio::has::HasCtpOp;
using ::bluetooth::le_audio::has::HasDevice;
using ::bluetooth::le_audio::has::HasPreset;
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::AtLeast;
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) {
EXPECT_LT(index, UINT8_MAX);
RawAddress result = {{0xC0, 0xDE, 0xC0, 0xDE, 0x00, static_cast(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 addr_or_group_id), uint8_t preset_index), (override));
MOCK_METHOD((void), OnActivePresetSelectError,
((std::variant addr_or_group_id), ErrorCode result), (override));
MOCK_METHOD((void), OnPresetInfo,
((std::variant addr_or_group_id), PresetInfoReason change_id,
std::vector preset_change_records),
(override));
MOCK_METHOD((void), OnPresetInfoError,
((std::variant addr_or_group_id), uint8_t preset_index,
ErrorCode error_code),
(override));
MOCK_METHOD((void), OnSetPresetNameError,
((std::variant addr_or_group_id), uint8_t preset_index,
ErrorCode error_code),
(override));
};
class HasClientTestBase : public ::testing::Test {
protected:
std::map current_peer_active_preset_idx_;
std::map current_peer_features_val_;
std::map> 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,
::bluetooth::le_audio::has::kUuidHearingAccessService, true);
if (features) {
bob.AddCharacteristic(
kFeaturesValHdl - 1, kFeaturesValHdl,
::bluetooth::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,
::bluetooth::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,
::bluetooth::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> 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 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 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>(
::bluetooth::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<::bluetooth::le_audio::has::PresetCtpOpcode>(op)) {
case ::bluetooth::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 ::bluetooth::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 ::bluetooth::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,
::bluetooth::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 ::bluetooth::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 ::bluetooth::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 ::bluetooth::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), ::bluetooth::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 ::bluetooth::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), ::bluetooth::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 ::bluetooth::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,
::bluetooth::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 {
__android_log_set_minimum_priority(ANDROID_LOG_VERBOSE);
reset_mock_function_count_map();
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;
ON_CALL(btm_interface, IsLinkKeyKnown(_, _)).WillByDefault(DoAll(Return(true)));
ON_CALL(btm_interface, SetEncryption(_, _, _, _, _))
.WillByDefault(Invoke([this](const RawAddress& bd_addr, tBT_TRANSPORT /*transport*/,
tBTM_SEC_CALLBACK* /*p_callback*/, void* /*p_ref_data*/,
tBTM_BLE_SEC_ACT /*sec_act*/) -> tBTM_STATUS {
InjectEncryptionEvent(bd_addr);
return tBTM_STATUS::BTM_SUCCESS;
}));
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& 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& 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* {
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 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);
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 = BT_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 = {
.conn_id = conn_id,
.status = GATT_SUCCESS,
.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 = {
.conn_id = conn_id,
.status = GATT_SUCCESS,
};
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 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 InjectEncryptionEvent(const RawAddress& test_address) {
tBTA_GATTC_ENC_CMPL_CB event_data = {
.client_if = static_cast(GetTestConnId(test_address)),
.remote_bda = test_address,
};
gatt_callback(BTA_GATTC_ENC_CMPL_CB_EVT, (tBTA_GATTC*)&event_data);
}
void SetEncryptionResult(const RawAddress& address, bool success) {
encryption_result = success;
ON_CALL(btm_interface, BTM_IsEncrypted(address, _))
.WillByDefault(DoAll(Return(encryption_result)));
ON_CALL(btm_interface, IsLinkKeyKnown(address, _)).WillByDefault(DoAll(Return(true)));
}
void InjectNotifyReadPresetResponse(uint16_t conn_id, RawAddress const& address, uint16_t handle,
const HasPreset& preset, bool indicate, bool is_last) {
std::vector value;
value.push_back(
static_cast>(
::bluetooth::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,
::bluetooth::le_audio::has::PresetCtpChangeId change_id, bool is_last) {
std::vector value;
value.push_back(
static_cast>(
::bluetooth::le_audio::has::PresetCtpOpcode::PRESET_CHANGED));
value.push_back(static_cast(change_id));
value.push_back(is_last ? 0x01 : 0x00);
switch (change_id) {
case ::bluetooth::le_audio::has::PresetCtpChangeId::PRESET_GENERIC_UPDATE:
value.push_back(prev_index);
preset.ToCharacteristicValue(value);
break;
case ::bluetooth::le_audio::has::PresetCtpChangeId::PRESET_DELETED:
case ::bluetooth::le_audio::has::PresetCtpChangeId::PRESET_AVAILABLE:
case ::bluetooth::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 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_that(!presets.empty(), "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++ <= ::bluetooth::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 wr_value, uint8_t index,
GATT_WRITE_OP_CB cb, void* cb_data) {
auto presets = current_peer_presets_.at(conn_id);
log::assert_that(!presets.empty(), "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 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> 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 callbacks;
bluetooth::manager::MockBtmInterface btm_interface;
bluetooth::storage::MockBtifStorageInterface btif_storage_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 connected_devices;
std::map> services_map;
bool encryption_result;
};
class HasClientTest : public HasClientTestBase {
void SetUp(void) override {
HasClientTestBase::SetUp();
TestAppRegister();
}
void TearDown(void) override {
com::android::bluetooth::flags::provider_->reset_flags();
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_connect_after_remove) {
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);
Mock::VerifyAndClearExpectations(&gatt_interface);
EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address));
// Device has no Link Key
ON_CALL(btm_interface, IsLinkKeyKnown(test_address, _)).WillByDefault(DoAll(Return(true)));
HasClient::Get()->Connect(test_address);
Mock::VerifyAndClearExpectations(&callbacks);
}
TEST_F(HasClientTest,
test_disconnect_non_connected_without_hap_connect_only_requested_device_flag) {
com::android::bluetooth::flags::provider_->hap_connect_only_requested_device(false);
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_disconnect_non_connected) {
com::android::bluetooth::flags::provider_->hap_connect_only_requested_device(true);
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_without_hap_connect_only_requested_device_flag) {
/* TODO: this test shall be removed b/370405555 */
com::android::bluetooth::flags::provider_->hap_connect_only_requested_device(false);
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);
EXPECT_CALL(gatt_queue, Clean(1)).Times(AtLeast(1));
TestDisconnect(test_address, 1);
}
TEST_F(HasClientTest, test_disconnect_connected) {
com::android::bluetooth::flags::provider_->hap_connect_only_requested_device(true);
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);
EXPECT_CALL(gatt_queue, Clean(1)).Times(AtLeast(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_service_discovery_complete_before_encryption) {
const RawAddress test_address = GetTestAddress(1);
SetSampleDatabaseHasPresetsNtf(test_address, bluetooth::has::kFeatureBitHearingAidTypeBinaural);
EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address)).Times(0);
EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address)).Times(0);
SetEncryptionResult(test_address, false);
ON_CALL(btm_interface, SetEncryption(_, _, _, _, _))
.WillByDefault(Return(tBTM_STATUS::BTM_SUCCESS));
TestConnect(test_address);
auto test_conn_id = GetTestConnId(test_address);
InjectSearchCompleteEvent(test_conn_id);
Mock::VerifyAndClearExpectations(callbacks.get());
EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address)).Times(1);
SetEncryptionResult(test_address, true);
InjectEncryptionEvent(test_address);
Mock::VerifyAndClearExpectations(callbacks.get());
}
TEST_F(HasClientTest, test_disconnect_when_link_key_is_gone) {
const RawAddress test_address = GetTestAddress(1);
SetSampleDatabaseHasPresetsNtf(test_address, bluetooth::has::kFeatureBitHearingAidTypeBinaural);
EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address)).Times(0);
EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address)).Times(0);
ON_CALL(btm_interface, BTM_IsEncrypted(test_address, _)).WillByDefault(DoAll(Return(false)));
ON_CALL(btm_interface, SetEncryption(test_address, _, _, _, _))
.WillByDefault(Return(tBTM_STATUS::BTM_ERR_KEY_MISSING));
auto test_conn_id = GetTestConnId(test_address);
EXPECT_CALL(gatt_interface, Close(test_conn_id)).Times(1);
InjectConnectedEvent(test_address, GetTestConnId(test_address));
Mock::VerifyAndClearExpectations(callbacks.get());
}
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 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& 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 loaded_preset_details;
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(test_address),
PresetInfoReason::ALL_PRESET_INFO, _))
.WillOnce(SaveArg<2>(&loaded_preset_details));
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(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 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& 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 loaded_preset_details;
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(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 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 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 addr_or_group = test_address;
std::vector 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(addr_or_group));
ASSERT_EQ(std::get(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(0u, preset_details.size());
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_cp_not_usable_read_all_presets) {
osi_property_set_bool("persist.bluetooth.has.always_use_preset_cache", false);
const RawAddress test_address = GetTestAddress(1);
std::set 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);
ON_CALL(gatt_queue, ReadCharacteristic(_, HasDbBuilder::kActivePresetIndexValHdl, _, _))
.WillByDefault(Invoke([&](uint16_t conn_id, uint16_t handle, GATT_READ_OP_CB cb,
void* cb_data) -> void {
std::vector value;
tGATT_STATUS status = GATT_ERROR;
if (cb) {
cb(conn_id, status, handle, value.size(), value.data(), cb_data);
}
}));
EXPECT_CALL(*callbacks,
OnDeviceAvailable(test_address, bluetooth::has::kFeatureBitHearingAidTypeBanded |
bluetooth::has::kFeatureBitWritablePresets |
bluetooth::has::kFeatureBitDynamicPresets));
EXPECT_CALL(gatt_queue, Clean(1)).Times(AtLeast(1));
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{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({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 addr_or_group = test_address;
std::vector 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{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(test_address), _))
.WillOnce(SaveArg<1>(&new_active_preset));
InjectNotificationEvent(test_address, test_conn_id, HasDbBuilder::kActivePresetIndexValHdl,
std::vector({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 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(1u, preset_details.size());
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(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 value;
EXPECT_CALL(*callbacks, OnSetPresetNameError(_, _, _)).Times(0);
EXPECT_CALL(gatt_queue, WriteCharacteristic(test_conn_id, HasDbBuilder::kPresetsCtpValHdl, _,
GATT_WRITE, _, _));
std::vector updated_preset_details;
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(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({{test_address1, test_address2}})));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address1, ::bluetooth::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(not_synced_group));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address2, ::bluetooth::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(not_synced_group));
std::vector preset_details;
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(test_address1), 55))
.Times(0);
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(test_address2), 55))
.Times(0);
/* This should be a group callback */
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(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(1u, preset_details.size());
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 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 new_preset_details;
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(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(test_address), 128,
ErrorCode::INVALID_PRESET_INDEX));
HasClient::Get()->GetPresetInfo(test_address, 128);
EXPECT_CALL(*callbacks, OnPresetInfoError(std::variant(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 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 preset_details;
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(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 */,
::bluetooth::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 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 preset_details;
EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(test_address),
PresetInfoReason::ALL_PRESET_INFO, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details));
TestConnect(test_address);
/* Expect more OnPresetInfo call */
std::vector updated_preset_details;
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(test_address),
PresetInfoReason::PRESET_INFO_UPDATE, _))
.Times(1)
.WillOnce(SaveArg<2>(&updated_preset_details));
/* Expect more OnPresetInfo call */
std::vector deleted_preset_details;
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(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 */,
::bluetooth::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 */,
::bluetooth::le_audio::has::PresetCtpChangeId::PRESET_GENERIC_UPDATE,
false /* is_last */);
/* Third event deletes preset 1 with the generic update */
InjectPresetChanged(test_conn_id, test_address, false, new_test_preset1, 0 /* prev_index */,
::bluetooth::le_audio::has::PresetCtpChangeId::PRESET_GENERIC_UPDATE,
true /* is_last */);
/* Verify received preset info - expect presets 1, 32 unchanged, 8, 9
* updated, and 1, 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(4u, 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);
ASSERT_EQ(1, deleted_preset_details[3].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 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 preset_details;
EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(test_address),
PresetInfoReason::ALL_PRESET_INFO, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details));
TestConnect(test_address);
/* Expect second OnPresetInfo call */
std::vector deleted_preset_details;
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(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 */,
::bluetooth::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 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 preset_details;
EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(test_address),
PresetInfoReason::ALL_PRESET_INFO, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details));
TestConnect(test_address);
/* Expect second OnPresetInfo call */
std::vector changed_preset_details;
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(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 */,
::bluetooth::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 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 preset_details;
EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(test_address),
PresetInfoReason::ALL_PRESET_INFO, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details));
TestConnect(test_address);
/* Expect second OnPresetInfo call */
std::vector changed_preset_details;
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(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 */,
::bluetooth::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 preset_details;
EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(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_GT(preset_details.size(), 1u);
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()));
EXPECT_CALL(*callbacks, OnActivePresetSelectError(std::variant(unlucky_group),
ErrorCode::OPERATION_NOT_POSSIBLE))
.Times(1);
HasClient::Get()->SelectActivePreset(unlucky_group, 6);
}
TEST_F(HasClientTest, test_select_preset_not_available) {
/* 1. Initial condition: HA containinig two presets:
* a) with isAvailable set to 0b1
* b) with isAvailable set to 0b0
* 2. HA is connected
* 3. Presets are read, preset a) is selected
* 4. Attempt of selecting preset b)
* 5. Preset b is not selected, operation aborts, event with error code is received
*/
const RawAddress test_address = GetTestAddress(1);
uint16_t test_conn_id = GetTestConnId(test_address);
std::set presets = {{
HasPreset(1, HasPreset::kPropertyAvailable, "Universal"),
HasPreset(2, HasPreset::kPropertyWritable, "Preset2"),
}};
SetSampleDatabaseHasPresetsNtf(test_address,
bluetooth::has::kFeatureBitHearingAidTypeBanded |
bluetooth::has::kFeatureBitWritablePresets |
bluetooth::has::kFeatureBitDynamicPresets,
presets);
uint8_t active_preset_index = 0;
std::vector preset_details;
EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(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_GT(preset_details.size(), 1u);
ASSERT_EQ(preset_details.front().preset_index, active_preset_index);
EXPECT_CALL(*callbacks, OnActivePresetSelectError(std::variant(test_address),
ErrorCode::OPERATION_NOT_POSSIBLE))
.Times(1);
EXPECT_CALL(*callbacks, OnActivePresetSelected(_, _)).Times(0);
HasClient::Get()->SelectActivePreset(test_address, preset_details[1].preset_index);
}
TEST_F(HasClientTest, test_select_group_preset_not_available) {
/* 1. Initial condition: 2 HAs (non-binaural) containinig two presets:
* a) with isAvailable set to 0b1
* b) with isAvailable set to 0b0
* 2. HAs are connected
* 3. HAs are made into coordinated set
* 3. Presets are read on both HAs, preset a) is selected
* 4. Attempt of selecting preset b) for a group
* 5. Preset b) is not selected, operation aborts, event with error code is received
*/
const RawAddress test_address1 = GetTestAddress(1);
const RawAddress test_address2 = GetTestAddress(2);
std::set presets = {{
HasPreset(1, HasPreset::kPropertyAvailable, "Universal"),
HasPreset(2, HasPreset::kPropertyWritable, "Preset2"),
}};
SetSampleDatabaseHasPresetsNtf(test_address1,
bluetooth::has::kFeatureBitHearingAidTypeBanded |
bluetooth::has::kFeatureBitWritablePresets |
bluetooth::has::kFeatureBitDynamicPresets,
presets);
SetSampleDatabaseHasPresetsNtf(test_address2,
bluetooth::has::kFeatureBitHearingAidTypeBanded |
bluetooth::has::kFeatureBitWritablePresets |
bluetooth::has::kFeatureBitDynamicPresets,
presets);
uint8_t active_preset_index1 = 0;
uint8_t active_preset_index2 = 0;
std::vector preset_details1;
std::vector preset_details2;
EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address1));
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(test_address1),
PresetInfoReason::ALL_PRESET_INFO, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details1));
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(test_address1), _))
.WillOnce(SaveArg<1>(&active_preset_index1));
TestConnect(test_address1);
EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address2));
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(test_address2),
PresetInfoReason::ALL_PRESET_INFO, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details2));
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(test_address2), _))
.WillOnce(SaveArg<1>(&active_preset_index2));
TestConnect(test_address2);
uint8_t group_id = 13;
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address1, ::bluetooth::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(group_id));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address2, ::bluetooth::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(group_id));
ON_CALL(mock_csis_client_module_, GetDeviceList(group_id))
.WillByDefault(Return(std::vector({{test_address1, test_address2}})));
ASSERT_GT(preset_details1.size(), 1u);
ASSERT_GT(preset_details2.size(), 1u);
ASSERT_EQ(preset_details1.front().preset_index, active_preset_index1);
ASSERT_EQ(preset_details2.front().preset_index, active_preset_index2);
EXPECT_CALL(*callbacks, OnActivePresetSelectError(_, ErrorCode::OPERATION_NOT_POSSIBLE)).Times(1);
EXPECT_CALL(*callbacks, OnActivePresetSelected(_, _)).Times(0);
HasClient::Get()->SelectActivePreset(group_id, preset_details1[1].preset_index);
}
TEST_F(HasClientTest, test_select_group_preset_not_available_binaural) {
/* 1. Initial condition: 2 HAs (binaural) containinig two presets sets:
* set I
* a) with isAvailable set to 0b1
* b) with isAvailable set to 0b0
* set II
* a) with isAvailable set to 0b1
* b) with isAvailable set to 0b1
*
* 2. HAs are connected
* 3. HAs are made into coordinated set
* 3. Presets are read on both HAs, preset a) is selected
* 4. Attempt of selecting preset b) for a group
* 5. Preset b) is not selected, operation aborts, event with error code is received
*/
const RawAddress test_address1 = GetTestAddress(1);
const RawAddress test_address2 = GetTestAddress(2);
std::set presets1 = {{
HasPreset(1, HasPreset::kPropertyAvailable, "Universal"),
HasPreset(2, HasPreset::kPropertyWritable, "Preset2"),
}};
std::set presets2 = {{
HasPreset(1, HasPreset::kPropertyAvailable, "Universal"),
HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, "Preset2"),
}};
SetSampleDatabaseHasPresetsNtf(test_address1,
bluetooth::has::kFeatureBitHearingAidTypeBinaural |
bluetooth::has::kFeatureBitWritablePresets |
bluetooth::has::kFeatureBitDynamicPresets,
presets1);
SetSampleDatabaseHasPresetsNtf(test_address2,
bluetooth::has::kFeatureBitHearingAidTypeBinaural |
bluetooth::has::kFeatureBitWritablePresets |
bluetooth::has::kFeatureBitDynamicPresets,
presets2);
uint8_t active_preset_index1 = 0;
uint8_t active_preset_index2 = 0;
std::vector preset_details1;
std::vector preset_details2;
EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address1));
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(test_address1),
PresetInfoReason::ALL_PRESET_INFO, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details1));
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(test_address1), _))
.WillOnce(SaveArg<1>(&active_preset_index1));
TestConnect(test_address1);
EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address2));
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(test_address2),
PresetInfoReason::ALL_PRESET_INFO, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details2));
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(test_address2), _))
.WillOnce(SaveArg<1>(&active_preset_index2));
TestConnect(test_address2);
uint8_t group_id = 13;
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address1, ::bluetooth::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(group_id));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address2, ::bluetooth::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(group_id));
ON_CALL(mock_csis_client_module_, GetDeviceList(group_id))
.WillByDefault(Return(std::vector({{test_address1, test_address2}})));
ASSERT_GT(preset_details1.size(), 1u);
ASSERT_GT(preset_details2.size(), 1u);
ASSERT_EQ(preset_details1.front().preset_index, active_preset_index1);
ASSERT_EQ(preset_details2.front().preset_index, active_preset_index2);
EXPECT_CALL(*callbacks, OnActivePresetSelectError(_, ErrorCode::OPERATION_NOT_POSSIBLE)).Times(1);
EXPECT_CALL(*callbacks, OnActivePresetSelected(_, _)).Times(0);
HasClient::Get()->SelectActivePreset(group_id, preset_details1[1].preset_index);
}
TEST_F(HasClientTest, test_select_group_preset_not_available_binaural_independent) {
/* 1. Initial condition: 2 HAs (binaural) containinig two presets sets:
* set I
* a) with isAvailable set to 0b1
* b) with isAvailable set to 0b0
* set II
* a) with isAvailable set to 0b1
* b) with isAvailable set to 0b1
* Both devices have independent presets set to 0b1
* 2. HAs are connected
* 3. HAs are made into coordinated set
* 3. Presets are read on both HAs, preset a) is selected
* 4. Attempt of selecting preset b) for a group
* 5. Preset b) is not selected, operation aborts, event with error code is received
*/
const RawAddress test_address1 = GetTestAddress(1);
const RawAddress test_address2 = GetTestAddress(2);
std::set presets1 = {{
HasPreset(1, HasPreset::kPropertyAvailable, "Universal"),
HasPreset(2, HasPreset::kPropertyWritable, "Preset2"),
}};
std::set presets2 = {{
HasPreset(1, HasPreset::kPropertyAvailable, "Universal"),
HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable, "Preset2"),
}};
SetSampleDatabaseHasPresetsNtf(test_address1,
bluetooth::has::kFeatureBitHearingAidTypeBinaural |
bluetooth::has::kFeatureBitWritablePresets |
bluetooth::has::kFeatureBitDynamicPresets |
bluetooth::has::kFeatureBitIndependentPresets,
presets1);
SetSampleDatabaseHasPresetsNtf(test_address2,
bluetooth::has::kFeatureBitHearingAidTypeBinaural |
bluetooth::has::kFeatureBitWritablePresets |
bluetooth::has::kFeatureBitDynamicPresets |
bluetooth::has::kFeatureBitIndependentPresets,
presets2);
uint8_t active_preset_index1 = 0;
uint8_t active_preset_index2 = 0;
std::vector preset_details1;
std::vector preset_details2;
EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address1));
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(test_address1),
PresetInfoReason::ALL_PRESET_INFO, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details1));
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(test_address1), _))
.WillOnce(SaveArg<1>(&active_preset_index1));
TestConnect(test_address1);
EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address2));
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(test_address2),
PresetInfoReason::ALL_PRESET_INFO, _))
.Times(1)
.WillOnce(SaveArg<2>(&preset_details2));
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(test_address2), _))
.WillOnce(SaveArg<1>(&active_preset_index2));
TestConnect(test_address2);
uint8_t group_id = 13;
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address1, ::bluetooth::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(group_id));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address2, ::bluetooth::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(group_id));
ON_CALL(mock_csis_client_module_, GetDeviceList(group_id))
.WillByDefault(Return(std::vector({{test_address1, test_address2}})));
ASSERT_GT(preset_details1.size(), 1u);
ASSERT_GT(preset_details2.size(), 1u);
ASSERT_EQ(preset_details1.front().preset_index, active_preset_index1);
ASSERT_EQ(preset_details2.front().preset_index, active_preset_index2);
EXPECT_CALL(*callbacks, OnActivePresetSelected(_, _)).Times(1);
HasClient::Get()->SelectActivePreset(group_id, preset_details1[1].preset_index);
}
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({{test_address1, test_address2}})));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address1, ::bluetooth::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(not_synced_group));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address2, ::bluetooth::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(not_synced_group));
uint8_t group_active_preset_index = 0;
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(test_address1), 55))
.Times(0);
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(test_address2), 55))
.Times(0);
EXPECT_CALL(*callbacks,
OnActivePresetSelected(std::variant(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(test_address1), _))
.WillOnce(SaveArg<1>(&active_preset_index1));
TestConnect(test_address1);
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(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({{test_address1, test_address2}})));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address1, ::bluetooth::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(synced_group));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address2, ::bluetooth::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(test_address1), _))
.Times(0);
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(test_address2), _))
.Times(0);
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(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 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 preset_details;
EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(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_GT(preset_details.size(), 1u);
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 */,
::bluetooth::le_audio::has::PresetCtpChangeId::PRESET_DELETED, true /* is_last */);
EXPECT_CALL(*callbacks, OnActivePresetSelectError(std::variant(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 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 preset_details;
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(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_GT(preset_details.size(), 1u);
ASSERT_EQ(1, active_preset_index);
/* Verify active preset change */
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(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({{test_address1, test_address2}})));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address1, ::bluetooth::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(not_synced_group));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address2, ::bluetooth::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(not_synced_group));
uint8_t group_active_preset_index = 0;
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(test_address1), 55))
.Times(0);
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(test_address2), 55))
.Times(0);
EXPECT_CALL(*callbacks,
OnActivePresetSelected(std::variant(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(test_address1), _))
.WillOnce(SaveArg<1>(&active_preset_index1));
TestConnect(test_address1);
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(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({{test_address1, test_address2}})));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address1, ::bluetooth::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(synced_group));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address2, ::bluetooth::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(test_address1), _))
.Times(0);
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(test_address2), _))
.Times(0);
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(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 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 preset_details;
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(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_GT(preset_details.size(), 1u);
ASSERT_EQ(2, active_preset_index);
/* Verify active preset change */
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(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({{test_address1, test_address2}})));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address1, ::bluetooth::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(not_synced_group));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address2, ::bluetooth::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(not_synced_group));
uint8_t group_active_preset_index = 0;
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(test_address1), 55))
.Times(0);
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(test_address2), 55))
.Times(0);
EXPECT_CALL(*callbacks,
OnActivePresetSelected(std::variant(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(test_address1), _))
.WillOnce(SaveArg<1>(&active_preset_index1));
TestConnect(test_address1);
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(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({{test_address1, test_address2}})));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address1, ::bluetooth::le_audio::uuid::kCapServiceUuid))
.WillByDefault(Return(synced_group));
ON_CALL(mock_csis_client_module_,
GetGroupId(test_address2, ::bluetooth::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(test_address1), _))
.Times(0);
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(test_address2), _))
.Times(0);
EXPECT_CALL(*callbacks, OnActivePresetSelected(std::variant(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("{}", 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 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 preset_details;
EXPECT_CALL(*callbacks, OnPresetInfo(std::variant(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 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 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 { reset_mock_function_count_map(); }
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 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 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 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 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 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, ::bluetooth::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6));
ASSERT_EQ(2u, wrapper.ref_cnt);
HasCtpGroupOpCoordinator::Cleanup();
ASSERT_EQ(0u, wrapper.ref_cnt);
ASSERT_EQ(1, get_func_call_count("alarm_free"));
ASSERT_EQ(1, get_func_call_count("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, ::bluetooth::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6));
HasCtpGroupOpCoordinator wrapper2(
{address1}, HasCtpOp(0x01, ::bluetooth::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, get_func_call_count("alarm_free"));
ASSERT_EQ(1, get_func_call_count("alarm_new"));
}
TEST_F(HasTypesTest, test_group_op_coordinator_completion) {
HasCtpGroupOpCoordinator::Initialize([](void*) {
/* Do nothing */
log::info("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, ::bluetooth::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6));
HasCtpGroupOpCoordinator wrapper2(
{address2}, HasCtpOp(0x01, ::bluetooth::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, get_func_call_count("alarm_free"));
/* Non existing address completion */
wrapper.SetCompleted(address2);
ASSERT_EQ(0, get_func_call_count("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);
const int alarm_free_count = get_func_call_count("alarm_free");
ASSERT_EQ(1, alarm_free_count);
HasCtpGroupOpCoordinator::Cleanup();
ASSERT_EQ(alarm_free_count, get_func_call_count("alarm_free"));
ASSERT_EQ(1, get_func_call_count("alarm_new"));
}
} // namespace
} // namespace internal
} // namespace has
} // namespace bluetooth