blob: b13524d7cdb3ae1b4813c999a573ab913b7a87c0 [file] [log] [blame]
/*
* 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 <aics/api.h>
#include <base/functional/bind.h>
#include <com_android_bluetooth_flags.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <log/log.h>
#include "bta/include/bta_vc_api.h"
#include "bta/le_audio/le_audio_types.h"
#include "bta/test/common/bta_gatt_api_mock.h"
#include "bta/test/common/bta_gatt_queue_mock.h"
#include "bta/test/common/btm_api_mock.h"
#include "bta/test/common/mock_csis_client.h"
#include "bta/test/common/mock_device_groups.h"
#include "bta/vc/types.h"
#include "gatt/database_builder.h"
#include "hardware/bt_gatt_types.h"
#include "include/bind_helpers.h"
#include "osi/test/alarm_mock.h"
#include "stack/include/bt_uuid16.h"
#include "stack/include/btm_status.h"
#include "test/common/mock_functions.h"
#include "types/bluetooth/uuid.h"
#include "types/raw_address.h"
struct alarm_t {
alarm_callback_t cb = nullptr;
void* data = nullptr;
bool on_main_loop = false;
};
using ::testing::NiceMock;
namespace bluetooth {
namespace vc {
namespace internal {
namespace {
using base::Bind;
using base::Unretained;
using bluetooth::aics::GainMode;
using bluetooth::aics::Mute;
using bluetooth::vc::ConnectionState;
using bluetooth::vc::VolumeControlCallbacks;
using testing::_;
using testing::DoAll;
using testing::DoDefault;
using testing::Invoke;
using testing::Mock;
using testing::NotNull;
using testing::Return;
using testing::SaveArg;
using testing::SetArgPointee;
using testing::WithArg;
static RawAddress GetTestAddress(int index) {
EXPECT_LT(index, UINT8_MAX);
RawAddress result = {{0xC0, 0xDE, 0xC0, 0xDE, 0x00, static_cast<uint8_t>(index)}};
return result;
}
class MockVolumeControlCallbacks : public VolumeControlCallbacks {
public:
MockVolumeControlCallbacks() = default;
MockVolumeControlCallbacks(const MockVolumeControlCallbacks&) = delete;
MockVolumeControlCallbacks& operator=(const MockVolumeControlCallbacks&) = delete;
~MockVolumeControlCallbacks() override = default;
MOCK_METHOD((void), OnConnectionState, (ConnectionState state, const RawAddress& address),
(override));
MOCK_METHOD((void), OnDeviceAvailable,
(const RawAddress& address, int group_id, uint8_t num_offset, uint8_t num_inputs),
(override));
MOCK_METHOD((void), OnVolumeStateChanged,
(const RawAddress& address, uint8_t volume, bool mute, uint8_t flags,
bool isAutonomous),
(override));
MOCK_METHOD((void), OnGroupVolumeStateChanged,
(int group_id, uint8_t volume, bool mute, bool isAutonomous), (override));
MOCK_METHOD((void), OnExtAudioOutVolumeOffsetChanged,
(const RawAddress& address, uint8_t ext_output_id, int16_t offset), (override));
MOCK_METHOD((void), OnExtAudioOutLocationChanged,
(const RawAddress& address, uint8_t ext_output_id, uint32_t location), (override));
MOCK_METHOD((void), OnExtAudioOutDescriptionChanged,
(const RawAddress& address, uint8_t ext_output_id, std::string descr), (override));
MOCK_METHOD((void), OnExtAudioInStateChanged,
(const RawAddress& address, uint8_t ext_input_id, int8_t gain_setting, Mute mute,
GainMode gain_mode),
(override));
MOCK_METHOD((void), OnExtAudioInSetGainSettingFailed,
(const RawAddress& address, uint8_t ext_input_id), (override));
MOCK_METHOD((void), OnExtAudioInSetMuteFailed, (const RawAddress& address, uint8_t ext_input_id),
(override));
MOCK_METHOD((void), OnExtAudioInSetGainModeFailed,
(const RawAddress& address, uint8_t ext_input_id), (override));
MOCK_METHOD((void), OnExtAudioInStatusChanged,
(const RawAddress& address, uint8_t ext_input_id, VolumeInputStatus status),
(override));
MOCK_METHOD((void), OnExtAudioInTypeChanged,
(const RawAddress& address, uint8_t ext_input_id, VolumeInputType type), (override));
MOCK_METHOD((void), OnExtAudioInGainSettingPropertiesChanged,
(const RawAddress& address, uint8_t ext_input_id, uint8_t unit, int8_t min,
int8_t max),
(override));
MOCK_METHOD((void), OnExtAudioInDescriptionChanged,
(const RawAddress& address, uint8_t ext_input_id, std::string description,
bool is_writable),
(override));
};
class VolumeControlTest : public ::testing::Test {
private:
void set_sample_database(uint16_t conn_id, bool vcs, bool vcs_broken, bool aics, bool aics_broken,
bool vocs, bool vocs_broken) {
gatt::DatabaseBuilder builder;
builder.AddService(0x0001, 0x0003, Uuid::From16Bit(0x1800), true);
builder.AddCharacteristic(0x0002, 0x0003, Uuid::From16Bit(0x2a00), GATT_CHAR_PROP_BIT_READ);
/* 0x0004-0x000f RFU */
if (vcs) {
/* VCS */
builder.AddService(0x0010, 0x0026, kVolumeControlUuid, true);
if (aics) {
builder.AddIncludedService(0x0011, kVolumeAudioInputUuid, 0x0030, 0x003e);
builder.AddIncludedService(0x0012, kVolumeAudioInputUuid, 0x0050, 0x005f);
}
if (vocs) {
builder.AddIncludedService(0x0013, kVolumeOffsetUuid, 0x0070, 0x0079);
builder.AddIncludedService(0x0014, kVolumeOffsetUuid, 0x0080, 0x008b);
}
/* 0x0015-0x001f RFU */
builder.AddCharacteristic(0x0020, 0x0021, kVolumeControlStateUuid,
GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
builder.AddDescriptor(0x0022, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
if (!vcs_broken) {
builder.AddCharacteristic(0x0023, 0x0024, kVolumeControlPointUuid,
GATT_CHAR_PROP_BIT_WRITE);
}
builder.AddCharacteristic(0x0025, 0x0026, kVolumeFlagsUuid, GATT_CHAR_PROP_BIT_READ);
/* 0x0027-0x002f RFU */
if (aics) {
/* AICS 1st instance */
builder.AddService(0x0030, 0x003e, kVolumeAudioInputUuid, false);
builder.AddCharacteristic(0x0031, 0x0032, kVolumeAudioInputStateUuid,
GATT_CHAR_PROP_BIT_READ);
builder.AddDescriptor(0x0033, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
builder.AddCharacteristic(0x0034, 0x0035, kVolumeAudioInputGainSettingPropertiesUuid,
GATT_CHAR_PROP_BIT_READ);
builder.AddCharacteristic(0x0036, 0x0037, kVolumeAudioInputTypeUuid,
GATT_CHAR_PROP_BIT_READ);
builder.AddCharacteristic(0x0038, 0x0039, kVolumeAudioInputStatusUuid,
GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
builder.AddDescriptor(0x003a, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
builder.AddCharacteristic(0x003b, 0x003c, kVolumeAudioInputControlPointUuid,
GATT_CHAR_PROP_BIT_WRITE);
builder.AddCharacteristic(0x003d, 0x003e, kVolumeAudioInputDescriptionUuid,
GATT_CHAR_PROP_BIT_READ);
/* 0x003f-0x004f RFU */
/* AICS 2nd instance */
builder.AddService(0x0050, 0x005f, kVolumeAudioInputUuid, false);
builder.AddCharacteristic(0x0051, 0x0052, kVolumeAudioInputStateUuid,
GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
builder.AddDescriptor(0x0053, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
if (!aics_broken) {
builder.AddCharacteristic(0x0054, 0x0055, kVolumeAudioInputGainSettingPropertiesUuid,
GATT_CHAR_PROP_BIT_READ);
}
builder.AddCharacteristic(0x0056, 0x0057, kVolumeAudioInputTypeUuid,
GATT_CHAR_PROP_BIT_READ);
builder.AddCharacteristic(0x0058, 0x0059, kVolumeAudioInputStatusUuid,
GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
builder.AddDescriptor(0x005a, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
builder.AddCharacteristic(0x005b, 0x005c, kVolumeAudioInputControlPointUuid,
GATT_CHAR_PROP_BIT_WRITE);
builder.AddCharacteristic(
0x005d, 0x005e, kVolumeAudioInputDescriptionUuid,
GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_WRITE_NR | GATT_CHAR_PROP_BIT_NOTIFY);
builder.AddDescriptor(0x005f, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
/* 0x0060-0x006f RFU */
}
if (vocs) {
/* VOCS 1st instance */
builder.AddService(0x0070, 0x0079, kVolumeOffsetUuid, false);
builder.AddCharacteristic(0x0071, 0x0072, kVolumeOffsetStateUuid,
GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
builder.AddDescriptor(0x0073, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
builder.AddCharacteristic(0x0074, 0x0075, kVolumeOffsetLocationUuid,
GATT_CHAR_PROP_BIT_READ);
builder.AddCharacteristic(0x0076, 0x0077, kVolumeOffsetControlPointUuid,
GATT_CHAR_PROP_BIT_WRITE);
builder.AddCharacteristic(0x0078, 0x0079, kVolumeOffsetOutputDescriptionUuid,
GATT_CHAR_PROP_BIT_READ);
/* 0x007a-0x007f RFU */
/* VOCS 2nd instance */
builder.AddService(0x0080, 0x008b, kVolumeOffsetUuid, false);
builder.AddCharacteristic(0x0081, 0x0082, kVolumeOffsetStateUuid,
GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
builder.AddDescriptor(0x0083, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
if (!vocs_broken) {
builder.AddCharacteristic(0x0084, 0x0085, kVolumeOffsetLocationUuid,
GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_WRITE_NR |
GATT_CHAR_PROP_BIT_NOTIFY);
builder.AddDescriptor(0x0086, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
}
builder.AddCharacteristic(0x0087, 0x0088, kVolumeOffsetControlPointUuid,
GATT_CHAR_PROP_BIT_WRITE);
builder.AddCharacteristic(
0x0089, 0x008a, kVolumeOffsetOutputDescriptionUuid,
GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_WRITE_NR | GATT_CHAR_PROP_BIT_NOTIFY);
builder.AddDescriptor(0x008b, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
}
}
/* 0x008c-0x008f RFU */
/* GATTS */
builder.AddService(0x0090, 0x0093, Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER), true);
builder.AddCharacteristic(0x0091, 0x0092, Uuid::From16Bit(GATT_UUID_GATT_SRV_CHGD),
GATT_CHAR_PROP_BIT_NOTIFY);
builder.AddDescriptor(0x0093, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
services_map[conn_id] = builder.Build().Services();
ON_CALL(gatt_queue, ReadCharacteristic(conn_id, _, _, _))
.WillByDefault(Invoke([&](uint16_t conn_id, uint16_t handle, GATT_READ_OP_CB cb,
void* cb_data) -> void {
std::vector<uint8_t> value;
switch (handle) {
case 0x0003:
/* device name */
value.resize(20);
break;
case 0x0021:
/* volume state */
value.resize(3);
break;
case 0x0026:
/* volume flags */
value.resize(1);
break;
case 0x0032: // 1st AICS instance
case 0x0052: // 2nd AICS instance
/* audio input state */
value.resize(4);
break;
case 0x0035: // 1st AICS instance
case 0x0055: // 2nd AICS instance
/* audio input gain settings */
value.resize(3);
break;
case 0x0037: // 1st AICS instance
case 0x0057: // 2nd AICS instance
/* audio input type */
value.resize(1);
break;
case 0x0039: // 1st AICS instance
case 0x0059: // 2nd AICS instance
/* audio input status */
value.resize(1);
break;
case 0x003e: // 1st AICS instance
case 0x005e: // 2nd AICS instance
/* audio input description */
value.resize(14);
break;
case 0x0072: // 1st VOCS instance
case 0x0082: // 2nd VOCS instance
/* offset state */
value.resize(3);
break;
case 0x0075: // 1st VOCS instance
case 0x0085: // 2nd VOCS instance
/* offset location */
value.resize(4);
break;
case 0x0079: // 1st VOCS instance
case 0x008a: // 2nd VOCS instance
/* offset output description */
value.resize(10);
break;
default:
FAIL();
return;
}
if (do_not_respond_to_reads) {
return;
}
cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(), cb_data);
}));
ON_CALL(gatt_queue, ReadMultiCharacteristic(conn_id, _, _, _))
.WillByDefault(Invoke([&](uint16_t conn_id, tBTA_GATTC_MULTI& handles,
GATT_READ_MULTI_OP_CB cb, void* cb_data) -> void {
std::vector<uint8_t> value;
auto add_element = [&](uint8_t data[], uint8_t len) -> void {
// LE order, 2 octets
value.push_back(len);
value.push_back(0x00);
uint8_t* p = &data[0];
for (size_t i = 0; i < len; i++) {
value.push_back(*p++);
}
};
for (size_t i = 0; i < handles.num_attr; i++) {
switch (handles.handles[i]) {
case 0x0003: {
/* device name */
uint8_t name[] = "UnknownName";
add_element(name, sizeof(name));
break;
}
case 0x0021: {
/* state */
uint8_t state[3] = {0x00, 0x00, 0x00};
add_element(state, sizeof(state));
break;
}
case 0x0026: {
/* volume flags */
uint8_t flags[] = {0x01};
add_element(flags, sizeof(flags));
break;
}
case 0x0032: // 1st AICS instance
case 0x0052: // 2nd AICS instance
{
/* audio input state */
uint8_t state[4] = {0x01, 0x01, 0x01, 0x00};
add_element(state, sizeof(state));
break;
}
case 0x0035: // 1st AICS instance
case 0x0055: // 2nd AICS instance
{
/* audio input gain settings */
uint8_t gain_settings[3] = {0x01, 0x01, 0x01};
add_element(gain_settings, sizeof(gain_settings));
break;
}
case 0x0037: // 1st AICS instance
case 0x0057: // 2nd AICS instance
{
/* audio input type */
uint8_t type[] = {0x01};
add_element(type, sizeof(type));
break;
}
case 0x0039: // 1st AICS instance
case 0x0059: // 2nd AICS instance
{
/* audio input status */
uint8_t status[] = {0x00};
add_element(status, sizeof(status));
break;
}
case 0x003e: // 1st AICS instance
case 0x005e: // 2nd AICS instance
{
/* audio input description */
uint8_t dest[] = "input";
add_element(dest, sizeof(dest));
break;
}
case 0x0072: // 1st VOCS instance
case 0x0082: // 2nd VOCS instance
{
/* offset state */
uint8_t state[3] = {0x00, 0x20, 0x00};
add_element(state, sizeof(state));
break;
}
case 0x0075: // 1st VOCS instance
case 0x0085: // 2nd VOCS instance
{
/* offset location */
uint8_t location[4] = {0x00, 0x02, 0x00, 0x01};
add_element(location, sizeof(location));
break;
}
case 0x0079: // 1st VOCS instance
case 0x008a: // 2nd VOCS instance
{
/* offset output description */
uint8_t dest[] = "VOCS_D";
add_element(dest, sizeof(dest));
break;
}
default:
FAIL();
return;
}
}
if (do_not_respond_to_reads) {
return;
}
cb(conn_id, GATT_SUCCESS, handles, value.size(), value.data(), cb_data);
}));
}
protected:
bool do_not_respond_to_reads = false;
int group_id = 5;
void SetUp(void) override {
__android_log_set_minimum_priority(ANDROID_LOG_VERBOSE);
com::android::bluetooth::flags::provider_->reset_flags();
com::android::bluetooth::flags::provider_->leaudio_add_aics_support(true);
com::android::bluetooth::flags::provider_->vcp_handle_group_id_internally(true);
bluetooth::manager::SetMockBtmInterface(&btm_interface);
MockCsisClient::SetMockInstanceForTesting(&mock_csis_client_module_);
MockDeviceGroups::SetMockInstanceForTesting(&mock_groups_module_);
gatt::SetMockBtaGattInterface(&gatt_interface);
gatt::SetMockBtaGattQueue(&gatt_queue);
reset_mock_function_count_map();
ON_CALL(btm_interface, IsDeviceBonded(_, _)).WillByDefault(DoAll(Return(true)));
// Store group callbacks so that we could inject grouping events
group_callbacks_ = nullptr;
ON_CALL(mock_groups_module_, Initialize(_)).WillByDefault(SaveArg<0>(&group_callbacks_));
ON_CALL(mock_groups_module_, Get()).WillByDefault(Return(&mock_groups_module_));
ON_CALL(mock_groups_module_, GetGroupId(_, _)).WillByDefault(Return(group_id));
// default action for GetCharacteristic function call
ON_CALL(gatt_interface, GetCharacteristic(_, _))
.WillByDefault(
Invoke([&](uint16_t conn_id, uint16_t handle) -> const gatt::Characteristic* {
std::list<gatt::Service>& services = services_map[conn_id];
for (auto const& service : services) {
for (auto const& characteristic : service.characteristics) {
if (characteristic.value_handle == handle) {
return &characteristic;
}
}
}
return nullptr;
}));
// default action for GetOwningService function call
ON_CALL(gatt_interface, GetOwningService(_, _))
.WillByDefault(Invoke([&](uint16_t conn_id, uint16_t handle) -> const gatt::Service* {
std::list<gatt::Service>& services = services_map[conn_id];
for (auto const& service : services) {
if (service.handle <= handle && service.end_handle >= handle) {
return &service;
}
}
return nullptr;
}));
// default action for GetServices function call
ON_CALL(gatt_interface, GetServices(_))
.WillByDefault(WithArg<0>(Invoke([&](uint16_t conn_id) -> std::list<gatt::Service>* {
return &services_map[conn_id];
})));
// default action for RegisterForNotifications function call
ON_CALL(gatt_interface, RegisterForNotifications(gatt_if, _, _))
.WillByDefault(Return(GATT_SUCCESS));
// default action for DeregisterForNotifications function call
ON_CALL(gatt_interface, DeregisterForNotifications(gatt_if, _, _))
.WillByDefault(Return(GATT_SUCCESS));
// default action for WriteDescriptor function call
ON_CALL(gatt_queue, WriteDescriptor(_, _, _, _, _, _))
.WillByDefault(Invoke([](uint16_t conn_id, uint16_t handle, std::vector<uint8_t> value,
tGATT_WRITE_TYPE /*write_type*/, GATT_WRITE_OP_CB cb,
void* cb_data) -> void {
if (cb) {
cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(), cb_data);
}
}));
auto mock_alarm = AlarmMock::Get();
ON_CALL(*mock_alarm, AlarmNew(_)).WillByDefault(Invoke([](const char* /*name*/) {
return new alarm_t();
}));
ON_CALL(*mock_alarm, AlarmFree(_)).WillByDefault(Invoke([](alarm_t* alarm) {
if (alarm) {
free(alarm);
}
}));
ON_CALL(*mock_alarm, AlarmCancel(_)).WillByDefault(Invoke([](alarm_t* alarm) {
if (alarm) {
alarm->cb = nullptr;
alarm->data = nullptr;
alarm->on_main_loop = false;
}
}));
ON_CALL(*mock_alarm, AlarmIsScheduled(_)).WillByDefault(Invoke([](const alarm_t* alarm) {
if (alarm) {
return alarm->cb != nullptr;
}
return false;
}));
ON_CALL(*mock_alarm, AlarmSet(_, _, _, _))
.WillByDefault(Invoke(
[](alarm_t* alarm, uint64_t /*interval_ms*/, alarm_callback_t cb, void* data) {
if (alarm) {
alarm->data = data;
alarm->cb = cb;
}
}));
ON_CALL(*mock_alarm, AlarmSetOnMloop(_, _, _, _))
.WillByDefault(Invoke(
[](alarm_t* alarm, uint64_t /*interval_ms*/, alarm_callback_t cb, void* data) {
if (alarm) {
alarm->on_main_loop = true;
alarm->data = data;
alarm->cb = cb;
}
}));
}
void TearDown(void) override {
services_map.clear();
gatt::SetMockBtaGattQueue(nullptr);
gatt::SetMockBtaGattInterface(nullptr);
bluetooth::manager::SetMockBtmInterface(nullptr);
AlarmMock::Reset();
}
void TestAppRegister(void) {
BtaAppRegisterCallback app_register_callback;
EXPECT_CALL(gatt_interface, AppRegister(_, _, _, _))
.WillOnce(DoAll(SaveArg<1>(&gatt_callback), SaveArg<2>(&app_register_callback)));
VolumeControl::Initialize(&callbacks, base::DoNothing());
ASSERT_TRUE(gatt_callback);
ASSERT_TRUE(app_register_callback);
app_register_callback.Run(gatt_if, GATT_SUCCESS);
ASSERT_TRUE(VolumeControl::IsVolumeControlRunning());
}
void TestAppUnregister(void) {
EXPECT_CALL(gatt_interface, AppDeregister(gatt_if));
VolumeControl::CleanUp();
ASSERT_FALSE(VolumeControl::IsVolumeControlRunning());
gatt_callback = nullptr;
}
void TestConnect(const RawAddress& address) {
// by default indicate link as encrypted
ON_CALL(btm_interface, BTM_IsEncrypted(address, _)).WillByDefault(DoAll(Return(true)));
EXPECT_CALL(gatt_interface, Open(gatt_if, address, BTM_BLE_DIRECT_CONNECTION, true));
VolumeControl::Get()->Connect(address);
Mock::VerifyAndClearExpectations(&gatt_interface);
}
void TestRemove(const RawAddress& address, uint16_t conn_id) {
EXPECT_CALL(gatt_interface, CancelOpen(gatt_if, address, true));
if (conn_id) {
EXPECT_CALL(gatt_interface, Close(conn_id));
} else {
EXPECT_CALL(gatt_interface, Close(conn_id)).Times(0);
}
VolumeControl::Get()->Remove(address);
Mock::VerifyAndClearExpectations(&gatt_interface);
}
void TestDisconnect(const RawAddress& address, uint16_t conn_id) {
if (conn_id) {
EXPECT_CALL(gatt_interface, Close(conn_id));
} else {
EXPECT_CALL(gatt_interface, Close(conn_id)).Times(0);
}
VolumeControl::Get()->Disconnect(address);
Mock::VerifyAndClearExpectations(&gatt_interface);
}
void TestAddFromStorage(const RawAddress& address) {
// by default indicate link as encrypted
ON_CALL(btm_interface, BTM_IsEncrypted(address, _)).WillByDefault(DoAll(Return(true)));
EXPECT_CALL(gatt_interface, Open(gatt_if, address, BTM_BLE_DIRECT_CONNECTION, true));
VolumeControl::Get()->AddFromStorage(address);
}
void TestSubscribeNotifications(const RawAddress& address, uint16_t conn_id,
const std::map<uint16_t, uint16_t>& handle_pairs) {
SetSampleDatabase(conn_id);
TestAppRegister();
TestConnect(address);
GetConnectedEvent(address, conn_id);
EXPECT_CALL(gatt_queue, WriteDescriptor(_, _, _, _, _, _)).WillRepeatedly(DoDefault());
EXPECT_CALL(gatt_interface, RegisterForNotifications(_, _, _)).WillRepeatedly(DoDefault());
std::vector<uint8_t> notify_value({0x01, 0x00});
for (auto const& handles : handle_pairs) {
EXPECT_CALL(gatt_queue,
WriteDescriptor(conn_id, handles.second, notify_value, GATT_WRITE, _, _))
.WillOnce(DoDefault());
EXPECT_CALL(gatt_interface, RegisterForNotifications(gatt_if, address, handles.first))
.WillOnce(DoDefault());
}
GetSearchCompleteEvent(conn_id);
TestAppUnregister();
}
void TestReadCharacteristic(const RawAddress& address, uint16_t conn_id,
std::vector<uint16_t> handles) {
SetSampleDatabase(conn_id);
TestAppRegister();
TestConnect(address);
GetConnectedEvent(address, conn_id);
tBTA_GATTC_MULTI received_to_read_1{};
tBTA_GATTC_MULTI received_to_read_2{};
if (!com::android::bluetooth::flags::le_ase_read_multiple_variable()) {
EXPECT_CALL(gatt_queue, ReadCharacteristic(conn_id, _, _, _)).WillRepeatedly(DoDefault());
for (auto const& handle : handles) {
EXPECT_CALL(gatt_queue, ReadCharacteristic(conn_id, handle, _, _)).WillOnce(DoDefault());
}
} else {
EXPECT_CALL(gatt_queue, ReadMultiCharacteristic(_, _, _, _)).Times(testing::AtLeast(1));
}
GetSearchCompleteEvent(conn_id);
TestAppUnregister();
}
void GetConnectedEvent(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,
};
gatt_callback(BTA_GATTC_OPEN_EVT, reinterpret_cast<tBTA_GATTC*>(&event_data));
}
void GetDisconnectedEvent(const RawAddress& address, uint16_t conn_id) {
tBTA_GATTC_CLOSE event_data = {
.conn_id = conn_id,
.status = GATT_SUCCESS,
.client_if = gatt_if,
.remote_bda = address,
.reason = GATT_CONN_TERMINATE_PEER_USER,
};
gatt_callback(BTA_GATTC_CLOSE_EVT, reinterpret_cast<tBTA_GATTC*>(&event_data));
}
void GetSearchCompleteEvent(uint16_t conn_id) {
tBTA_GATTC_SEARCH_CMPL event_data = {
.conn_id = conn_id,
.status = GATT_SUCCESS,
};
gatt_callback(BTA_GATTC_SEARCH_CMPL_EVT, reinterpret_cast<tBTA_GATTC*>(&event_data));
}
void GetEncryptionCompleteEvt(const RawAddress& bda) {
tBTA_GATTC cb_data{};
cb_data.enc_cmpl.client_if = gatt_if;
cb_data.enc_cmpl.remote_bda = bda;
gatt_callback(BTA_GATTC_ENC_CMPL_CB_EVT, &cb_data);
}
void SetEncryptionResult(const RawAddress& address, bool success) {
ON_CALL(btm_interface, BTM_IsEncrypted(address, _)).WillByDefault(DoAll(Return(false)));
ON_CALL(btm_interface, IsDeviceBonded(address, _)).WillByDefault(DoAll(Return(true)));
ON_CALL(btm_interface, SetEncryption(address, _, _, _, BTM_BLE_SEC_ENCRYPT))
.WillByDefault(
Invoke([&success, 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 {
if (p_callback) {
p_callback(bd_addr, transport, p_ref_data,
success ? tBTM_STATUS::BTM_SUCCESS
: tBTM_STATUS::BTM_FAILED_ON_SECURITY);
}
GetEncryptionCompleteEvt(bd_addr);
return tBTM_STATUS::BTM_SUCCESS;
}));
EXPECT_CALL(btm_interface, SetEncryption(address, _, _, _, BTM_BLE_SEC_ENCRYPT)).Times(1);
}
void SetSampleDatabaseVCS(uint16_t conn_id) {
set_sample_database(conn_id, true, false, false, false, false, false);
}
void SetSampleDatabaseAICS(uint16_t conn_id) {
set_sample_database(conn_id, true, false, true, false, false, false);
}
void SetSampleDatabaseAICSBroken(uint16_t conn_id) {
set_sample_database(conn_id, true, false, true, true, true, false);
}
void SetSampleDatabaseNoVCS(uint16_t conn_id) {
set_sample_database(conn_id, false, false, true, false, true, false);
}
void SetSampleDatabaseVCSBroken(uint16_t conn_id) {
set_sample_database(conn_id, true, true, true, false, true, false);
}
void SetSampleDatabaseVOCS(uint16_t conn_id) {
set_sample_database(conn_id, true, false, false, false, true, false);
}
void SetSampleDatabaseVOCSBroken(uint16_t conn_id) {
set_sample_database(conn_id, true, false, true, false, true, true);
}
void SetSampleDatabase(uint16_t conn_id) {
set_sample_database(conn_id, true, false, true, false, true, false);
}
NiceMock<MockVolumeControlCallbacks> callbacks;
NiceMock<bluetooth::manager::MockBtmInterface> btm_interface;
MockCsisClient mock_csis_client_module_;
NiceMock<MockDeviceGroups> mock_groups_module_;
NiceMock<gatt::MockBtaGattInterface> gatt_interface;
NiceMock<gatt::MockBtaGattQueue> gatt_queue;
tBTA_GATTC_CBACK* gatt_callback;
const uint8_t gatt_if = 0xff;
std::map<uint16_t, std::list<gatt::Service>> services_map;
bluetooth::groups::DeviceGroupsCallbacks* group_callbacks_ = nullptr;
};
TEST_F(VolumeControlTest, test_get_uninitialized) { ASSERT_DEATH(VolumeControl::Get(), ""); }
TEST_F(VolumeControlTest, test_initialize) {
bool init_cb_called = false;
BtaAppRegisterCallback app_register_callback;
EXPECT_CALL(gatt_interface, AppRegister(_, _, _, _))
.WillOnce(DoAll(SaveArg<1>(&gatt_callback), SaveArg<2>(&app_register_callback)));
VolumeControl::Initialize(
&callbacks,
base::Bind([](bool* init_cb_called) { *init_cb_called = true; }, &init_cb_called));
ASSERT_TRUE(gatt_callback);
ASSERT_TRUE(app_register_callback);
app_register_callback.Run(gatt_if, GATT_SUCCESS);
ASSERT_TRUE(init_cb_called);
ASSERT_TRUE(VolumeControl::IsVolumeControlRunning());
VolumeControl::CleanUp();
}
TEST_F(VolumeControlTest, test_initialize_twice) {
VolumeControl::Initialize(&callbacks, base::DoNothing());
VolumeControl* volume_control_p = VolumeControl::Get();
VolumeControl::Initialize(&callbacks, base::DoNothing());
ASSERT_EQ(volume_control_p, VolumeControl::Get());
VolumeControl::CleanUp();
}
TEST_F(VolumeControlTest, test_cleanup_initialized) {
VolumeControl::Initialize(&callbacks, base::DoNothing());
VolumeControl::CleanUp();
ASSERT_FALSE(VolumeControl::IsVolumeControlRunning());
}
TEST_F(VolumeControlTest, test_cleanup_uninitialized) {
VolumeControl::CleanUp();
ASSERT_FALSE(VolumeControl::IsVolumeControlRunning());
}
TEST_F(VolumeControlTest, test_app_registration) {
TestAppRegister();
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_connect) {
TestAppRegister();
TestConnect(GetTestAddress(0));
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_connect_after_remove) {
TestAppRegister();
const RawAddress test_address = GetTestAddress(0);
uint16_t conn_id = 1;
TestConnect(test_address);
GetConnectedEvent(test_address, conn_id);
Mock::VerifyAndClearExpectations(&callbacks);
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address)).Times(1);
TestRemove(test_address, conn_id);
Mock::VerifyAndClearExpectations(&callbacks);
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address)).Times(1);
ON_CALL(btm_interface, IsDeviceBonded(_, _)).WillByDefault(DoAll(Return(false)));
VolumeControl::Get()->Connect(test_address);
Mock::VerifyAndClearExpectations(&callbacks);
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_reconnect_after_interrupted_discovery) {
const RawAddress test_address = GetTestAddress(0);
// Initial connection - no callback calls yet as we want to disconnect in the
// middle
SetSampleDatabaseVOCS(1);
TestAppRegister();
TestConnect(test_address);
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address)).Times(0);
EXPECT_CALL(callbacks, OnDeviceAvailable(test_address, _, _, _)).Times(0);
GetConnectedEvent(test_address, 1);
Mock::VerifyAndClearExpectations(&callbacks);
// Remote disconnects in the middle of the service discovery
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address));
GetDisconnectedEvent(test_address, 1);
Mock::VerifyAndClearExpectations(&callbacks);
// This time let the service discovery pass
ON_CALL(gatt_interface, ServiceSearchRequest(_, _))
.WillByDefault(Invoke([&](uint16_t conn_id, const bluetooth::Uuid* p_srvc_uuid) -> void {
if (*p_srvc_uuid == kVolumeControlUuid) {
GetSearchCompleteEvent(conn_id);
}
}));
// Remote is being connected by another GATT client
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(callbacks, OnDeviceAvailable(test_address, group_id, 2, _));
GetConnectedEvent(test_address, 1);
Mock::VerifyAndClearExpectations(&callbacks);
// Request connect when the remote was already connected by another service
EXPECT_CALL(callbacks, OnDeviceAvailable(test_address, _, _, _)).Times(0);
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address));
VolumeControl::Get()->Connect(test_address);
// The GetConnectedEvent(test_address, 1); should not be triggered here, since
// GATT implementation will not send this event for the already connected
// device
Mock::VerifyAndClearExpectations(&callbacks);
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_verify_opportunistic_connect_active_after_connect_timeout) {
const RawAddress address = GetTestAddress(0);
TestAppRegister();
TestAddFromStorage(address);
Mock::VerifyAndClearExpectations(&gatt_interface);
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::DISCONNECTED, address)).Times(1);
TestConnect(address);
EXPECT_CALL(gatt_interface, CancelOpen(gatt_if, address, _)).Times(0);
EXPECT_CALL(gatt_interface, Open(gatt_if, address, BTM_BLE_DIRECT_CONNECTION, true)).Times(1);
GetConnectedEvent(address, 1, GATT_ERROR);
Mock::VerifyAndClearExpectations(&callbacks);
Mock::VerifyAndClearExpectations(&gatt_interface);
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_reconnect_after_timeout) {
const RawAddress address = GetTestAddress(0);
// Initial connection
SetSampleDatabaseVOCS(1);
TestAppRegister();
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::CONNECTED, address)).Times(0);
TestConnect(address);
// Disconnect not connected device - upper layer times out and needs a
// disconnection event to leave the transient Connecting state
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::DISCONNECTED, address));
EXPECT_CALL(gatt_interface, CancelOpen(gatt_if, address, _)).Times(0);
TestDisconnect(address, 0);
// Above the device was not connected and we got Disconnect request from the
// upper layer - it means it has timed-out but still wants to connect, thus
// native is still doing background or opportunistic connect. Let the remote
// device reconnect now.
ON_CALL(gatt_interface, ServiceSearchRequest(_, _))
.WillByDefault(Invoke([&](uint16_t conn_id, const bluetooth::Uuid* p_srvc_uuid) -> void {
if (*p_srvc_uuid == kVolumeControlUuid) {
GetSearchCompleteEvent(conn_id);
}
}));
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::CONNECTED, address));
EXPECT_CALL(callbacks, OnDeviceAvailable(address, group_id, 2, _));
GetConnectedEvent(address, 1);
Mock::VerifyAndClearExpectations(&callbacks);
// Make sure that the upper layer gets the disconnection event even if not
// connecting actively anymore due to the mentioned time-out mechanism.
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::DISCONNECTED, address));
GetDisconnectedEvent(address, 1);
Mock::VerifyAndClearExpectations(&callbacks);
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_add_from_storage) {
TestAppRegister();
TestAddFromStorage(GetTestAddress(0));
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_remove_non_connected) {
const RawAddress test_address = GetTestAddress(0);
TestAppRegister();
TestConnect(test_address);
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address));
TestRemove(test_address, 0);
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_remove_connected) {
const RawAddress test_address = GetTestAddress(0);
TestAppRegister();
TestConnect(test_address);
GetConnectedEvent(test_address, 1);
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address));
TestDisconnect(test_address, 1);
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_disconnect_non_connected) {
const RawAddress test_address = GetTestAddress(0);
TestAppRegister();
TestConnect(test_address);
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address));
TestDisconnect(test_address, 0);
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_disconnect_connected) {
const RawAddress test_address = GetTestAddress(0);
TestAppRegister();
TestConnect(test_address);
GetConnectedEvent(test_address, 1);
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address));
TestDisconnect(test_address, 1);
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_disconnected) {
const RawAddress test_address = GetTestAddress(0);
TestAppRegister();
TestConnect(test_address);
GetConnectedEvent(test_address, 1);
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address));
GetDisconnectedEvent(test_address, 1);
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_disconnected_while_autoconnect) {
const RawAddress test_address = GetTestAddress(0);
TestAppRegister();
TestAddFromStorage(test_address);
GetConnectedEvent(test_address, 1);
Mock::VerifyAndClearExpectations(&gatt_interface);
// autoconnect - don't indicate disconnection
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address)).Times(0);
GetDisconnectedEvent(test_address, 1);
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_disconnect_when_link_key_gone) {
const RawAddress test_address = GetTestAddress(0);
TestAppRegister();
TestAddFromStorage(test_address);
ON_CALL(btm_interface, BTM_IsEncrypted(test_address, _)).WillByDefault(DoAll(Return(false)));
ON_CALL(btm_interface, SetEncryption(test_address, _, _, _, BTM_BLE_SEC_ENCRYPT))
.WillByDefault(Return(tBTM_STATUS::BTM_ERR_KEY_MISSING));
// autoconnect - don't indicate disconnection
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address)).Times(0);
EXPECT_CALL(gatt_interface, Close(1));
GetConnectedEvent(test_address, 1);
Mock::VerifyAndClearExpectations(&btm_interface);
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_reconnect_after_encryption_failed) {
const RawAddress test_address = GetTestAddress(0);
TestAppRegister();
TestAddFromStorage(test_address);
SetEncryptionResult(test_address, false);
// autoconnect - don't indicate disconnection
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address)).Times(0);
GetConnectedEvent(test_address, 1);
Mock::VerifyAndClearExpectations(&btm_interface);
SetEncryptionResult(test_address, true);
GetConnectedEvent(test_address, 1);
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_service_discovery_completed_before_encryption) {
const RawAddress test_address = GetTestAddress(0);
SetSampleDatabaseVCS(1);
TestAppRegister();
TestConnect(test_address);
ON_CALL(btm_interface, BTM_IsEncrypted(test_address, _)).WillByDefault(DoAll(Return(false)));
ON_CALL(btm_interface, IsDeviceBonded(test_address, _)).WillByDefault(DoAll(Return(true)));
ON_CALL(btm_interface, SetEncryption(test_address, _, _, _, _))
.WillByDefault(Return(tBTM_STATUS::BTM_SUCCESS));
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address)).Times(0);
uint16_t conn_id = 1;
GetConnectedEvent(test_address, conn_id);
GetSearchCompleteEvent(conn_id);
Mock::VerifyAndClearExpectations(&btm_interface);
Mock::VerifyAndClearExpectations(&callbacks);
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address)).Times(1);
ON_CALL(btm_interface, BTM_IsEncrypted(test_address, _)).WillByDefault(DoAll(Return(true)));
EXPECT_CALL(gatt_interface, ServiceSearchRequest(_, _));
GetEncryptionCompleteEvt(test_address);
GetSearchCompleteEvent(conn_id);
Mock::VerifyAndClearExpectations(&callbacks);
Mock::VerifyAndClearExpectations(&gatt_interface);
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_discovery_vcs_found) {
const RawAddress test_address = GetTestAddress(0);
SetSampleDatabaseVCS(1);
TestAppRegister();
TestConnect(test_address);
EXPECT_CALL(callbacks, OnDeviceAvailable(test_address, group_id, _, _));
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address));
GetConnectedEvent(test_address, 1);
GetSearchCompleteEvent(1);
Mock::VerifyAndClearExpectations(&callbacks);
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_discovery_vcs_not_found) {
const RawAddress test_address = GetTestAddress(0);
SetSampleDatabaseNoVCS(1);
TestAppRegister();
TestConnect(test_address);
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address));
GetConnectedEvent(test_address, 1);
GetSearchCompleteEvent(1);
Mock::VerifyAndClearExpectations(&callbacks);
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_discovery_vcs_broken) {
const RawAddress test_address = GetTestAddress(0);
SetSampleDatabaseVCSBroken(1);
TestAppRegister();
TestConnect(test_address);
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address));
GetConnectedEvent(test_address, 1);
GetSearchCompleteEvent(1);
Mock::VerifyAndClearExpectations(&callbacks);
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_subscribe_vcs_volume_state) {
std::map<uint16_t, uint16_t> handles({{0x0021, 0x0022}});
TestSubscribeNotifications(GetTestAddress(0), 1, handles);
}
TEST_F(VolumeControlTest, test_subscribe_vocs_offset_state) {
std::map<uint16_t, uint16_t> handles({{0x0072, 0x0073}, {0x0082, 0x0083}});
TestSubscribeNotifications(GetTestAddress(0), 1, handles);
}
TEST_F(VolumeControlTest, test_subscribe_vocs_offset_location) {
std::map<uint16_t, uint16_t> handles({{0x0085, 0x0086}});
TestSubscribeNotifications(GetTestAddress(0), 1, handles);
}
TEST_F(VolumeControlTest, test_subscribe_vocs_output_description) {
std::map<uint16_t, uint16_t> handles({{0x008a, 0x008b}});
TestSubscribeNotifications(GetTestAddress(0), 1, handles);
}
TEST_F(VolumeControlTest, test_read_vcs_volume_state) {
const RawAddress test_address = GetTestAddress(0);
EXPECT_CALL(callbacks, OnVolumeStateChanged(test_address, _, _, _, true)).Times(1);
std::vector<uint16_t> handles({0x0021});
TestReadCharacteristic(test_address, 1, handles);
}
TEST_F(VolumeControlTest, test_read_vcs_volume_flags) {
std::vector<uint16_t> handles({0x0026});
TestReadCharacteristic(GetTestAddress(0), 1, handles);
}
TEST_F(VolumeControlTest, test_read_vocs_volume_offset) {
com::android::bluetooth::flags::provider_->le_ase_read_multiple_variable(false);
const RawAddress test_address = GetTestAddress(0);
EXPECT_CALL(callbacks, OnExtAudioOutVolumeOffsetChanged(test_address, 1, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutVolumeOffsetChanged(test_address, 2, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutLocationChanged(test_address, 1, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutLocationChanged(test_address, 2, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutDescriptionChanged(test_address, 1, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutDescriptionChanged(test_address, 2, _)).Times(1);
std::vector<uint16_t> handles({0x0072, 0x0082});
TestReadCharacteristic(test_address, 1, handles);
Mock::VerifyAndClearExpectations(&callbacks);
}
TEST_F(VolumeControlTest, test_read_vocs_volume_offset_multi) {
com::android::bluetooth::flags::provider_->le_ase_read_multiple_variable(true);
const RawAddress test_address = GetTestAddress(0);
EXPECT_CALL(callbacks, OnExtAudioOutVolumeOffsetChanged(test_address, 1, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutVolumeOffsetChanged(test_address, 2, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutLocationChanged(test_address, 1, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutLocationChanged(test_address, 2, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutDescriptionChanged(test_address, 1, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutDescriptionChanged(test_address, 2, _)).Times(1);
std::vector<uint16_t> handles({0x0072, 0x0082});
TestReadCharacteristic(test_address, 1, handles);
Mock::VerifyAndClearExpectations(&callbacks);
}
TEST_F(VolumeControlTest, test_read_vocs_offset_location) {
com::android::bluetooth::flags::provider_->le_ase_read_multiple_variable(false);
const RawAddress test_address = GetTestAddress(0);
// It is called twice because after connect read is done once and second read is coming from the
// test.
EXPECT_CALL(callbacks, OnExtAudioOutVolumeOffsetChanged(test_address, 1, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutVolumeOffsetChanged(test_address, 2, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutLocationChanged(test_address, 1, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutLocationChanged(test_address, 2, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutDescriptionChanged(test_address, 1, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutDescriptionChanged(test_address, 2, _)).Times(1);
std::vector<uint16_t> handles({0x0075, 0x0085});
TestReadCharacteristic(test_address, 1, handles);
Mock::VerifyAndClearExpectations(&callbacks);
}
TEST_F(VolumeControlTest, test_read_vocs_offset_location_multi) {
com::android::bluetooth::flags::provider_->le_ase_read_multiple_variable(true);
const RawAddress test_address = GetTestAddress(0);
// It is called twice because after connect read is done once and second read is coming from the
// test.
EXPECT_CALL(callbacks, OnExtAudioOutVolumeOffsetChanged(test_address, 1, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutVolumeOffsetChanged(test_address, 2, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutLocationChanged(test_address, 1, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutLocationChanged(test_address, 2, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutDescriptionChanged(test_address, 1, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutDescriptionChanged(test_address, 2, _)).Times(1);
std::vector<uint16_t> handles({0x0075, 0x0085});
TestReadCharacteristic(test_address, 1, handles);
Mock::VerifyAndClearExpectations(&callbacks);
}
TEST_F(VolumeControlTest, test_read_vocs_output_description) {
com::android::bluetooth::flags::provider_->le_ase_read_multiple_variable(false);
const RawAddress test_address = GetTestAddress(0);
EXPECT_CALL(callbacks, OnExtAudioOutVolumeOffsetChanged(test_address, 1, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutVolumeOffsetChanged(test_address, 2, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutLocationChanged(test_address, 1, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutLocationChanged(test_address, 2, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutDescriptionChanged(test_address, 1, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutDescriptionChanged(test_address, 2, _)).Times(1);
std::vector<uint16_t> handles({0x0079, 0x008a});
TestReadCharacteristic(test_address, 1, handles);
}
TEST_F(VolumeControlTest, test_read_vocs_output_description_multi) {
com::android::bluetooth::flags::provider_->le_ase_read_multiple_variable(true);
const RawAddress test_address = GetTestAddress(0);
EXPECT_CALL(callbacks, OnExtAudioOutVolumeOffsetChanged(test_address, 1, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutVolumeOffsetChanged(test_address, 2, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutLocationChanged(test_address, 1, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutLocationChanged(test_address, 2, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutDescriptionChanged(test_address, 1, _)).Times(1);
EXPECT_CALL(callbacks, OnExtAudioOutDescriptionChanged(test_address, 2, _)).Times(1);
std::vector<uint16_t> handles({0x0079, 0x008a});
TestReadCharacteristic(test_address, 1, handles);
}
TEST_F(VolumeControlTest, test_discovery_vocs_found) {
const RawAddress test_address = GetTestAddress(0);
SetSampleDatabaseVOCS(1);
TestAppRegister();
TestConnect(test_address);
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(callbacks, OnDeviceAvailable(test_address, group_id, 2, _));
GetConnectedEvent(test_address, 1);
GetSearchCompleteEvent(1);
Mock::VerifyAndClearExpectations(&callbacks);
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_discovery_vocs_not_found) {
const RawAddress test_address = GetTestAddress(0);
SetSampleDatabaseVCS(1);
TestAppRegister();
TestConnect(test_address);
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(callbacks, OnDeviceAvailable(test_address, group_id, 0, _));
GetConnectedEvent(test_address, 1);
GetSearchCompleteEvent(1);
Mock::VerifyAndClearExpectations(&callbacks);
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_discovery_vocs_broken) {
const RawAddress test_address = GetTestAddress(0);
SetSampleDatabaseVOCSBroken(1);
TestAppRegister();
TestConnect(test_address);
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address));
EXPECT_CALL(callbacks, OnDeviceAvailable(test_address, group_id, 1, _));
GetConnectedEvent(test_address, 1);
GetSearchCompleteEvent(1);
Mock::VerifyAndClearExpectations(&callbacks);
TestAppUnregister();
}
TEST_F(VolumeControlTest, test_read_vcs_database_out_of_sync) {
const RawAddress test_address = GetTestAddress(0);
EXPECT_CALL(callbacks, OnVolumeStateChanged(test_address, _, _, _, true));
std::vector<uint16_t> handles({0x0021});
uint16_t conn_id = 1;
SetSampleDatabase(conn_id);
TestAppRegister();
TestConnect(test_address);
GetConnectedEvent(test_address, conn_id);
EXPECT_CALL(gatt_queue, ReadCharacteristic(conn_id, _, _, _)).WillRepeatedly(DoDefault());
for (auto const& handle : handles) {
EXPECT_CALL(gatt_queue, ReadCharacteristic(conn_id, handle, _, _)).WillOnce(DoDefault());
}
GetSearchCompleteEvent(conn_id);
/* Simulate database change on the remote side. */
ON_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _))
.WillByDefault(Invoke([this](uint16_t conn_id, uint16_t handle,
std::vector<uint8_t> value, tGATT_WRITE_TYPE /*write_type*/,
GATT_WRITE_OP_CB cb, void* cb_data) {
auto* svc = gatt::FindService(services_map[conn_id], handle);
if (svc == nullptr) {
return;
}
tGATT_STATUS status = GATT_DATABASE_OUT_OF_SYNC;
if (cb) {
cb(conn_id, status, handle, value.size(), value.data(), cb_data);
}
}));
ON_CALL(gatt_interface, ServiceSearchRequest(_, _)).WillByDefault(Return());
EXPECT_CALL(gatt_interface, ServiceSearchRequest(_, _));
VolumeControl::Get()->SetVolume(test_address, 15);
Mock::VerifyAndClearExpectations(&gatt_interface);
TestAppUnregister();
}
class VolumeControlCallbackTest : public VolumeControlTest {
protected:
const RawAddress test_address = GetTestAddress(0);
uint16_t conn_id = 22;
void SetUp(void) override {
VolumeControlTest::SetUp();
SetSampleDatabase(conn_id);
TestAppRegister();
TestConnect(test_address);
GetConnectedEvent(test_address, conn_id);
GetSearchCompleteEvent(conn_id);
}
void TearDown(void) override {
TestAppUnregister();
VolumeControlTest::TearDown();
}
void GetNotificationEvent(uint16_t handle, const std::vector<uint8_t>& value) {
tBTA_GATTC_NOTIFY event_data = {
.conn_id = conn_id,
.bda = test_address,
.handle = handle,
.len = (uint8_t)value.size(),
.is_notify = true,
};
std::copy(value.begin(), value.end(), event_data.value);
gatt_callback(BTA_GATTC_NOTIF_EVT, reinterpret_cast<tBTA_GATTC*>(&event_data));
}
};
TEST_F(VolumeControlCallbackTest, test_volume_state_changed_stress) {
std::vector<uint8_t> value({0x03, 0x01, 0x02});
if (!com::android::bluetooth::flags::vcp_handle_group_id_internally()) {
EXPECT_CALL(callbacks, OnVolumeStateChanged(test_address, 0x03, true, _, true));
} else {
EXPECT_CALL(callbacks, OnGroupVolumeStateChanged(group_id, 0x03, true, true));
}
GetNotificationEvent(0x0021, value);
}
TEST_F(VolumeControlCallbackTest, test_volume_state_changed_malformed) {
EXPECT_CALL(callbacks, OnVolumeStateChanged(test_address, _, _, _, _)).Times(0);
EXPECT_CALL(callbacks, OnGroupVolumeStateChanged(group_id, _, _, _)).Times(0);
std::vector<uint8_t> too_short({0x03, 0x01});
GetNotificationEvent(0x0021, too_short);
std::vector<uint8_t> too_long({0x03, 0x01, 0x02, 0x03});
GetNotificationEvent(0x0021, too_long);
}
TEST_F(VolumeControlCallbackTest, audio_input_state_changed__invalid_mute__is_rejected) {
uint8_t invalid_mute = 0x03;
std::vector<uint8_t> value({0x03, invalid_mute, (uint8_t)GainMode::MANUAL, 0x04});
EXPECT_CALL(callbacks, OnExtAudioInStateChanged(_, _, _, _, _)).Times(0);
GetNotificationEvent(0x0032, value);
}
TEST_F(VolumeControlCallbackTest, audio_input_state_changed__invalid_gain_mode__is_rejected) {
uint8_t invalid_gain_mode = 0x06;
std::vector<uint8_t> value({0x03, (uint8_t)Mute::MUTED, invalid_gain_mode, 0x04});
EXPECT_CALL(callbacks, OnExtAudioInStateChanged(_, _, _, _, _)).Times(0);
GetNotificationEvent(0x0032, value);
}
TEST_F(VolumeControlCallbackTest, test_audio_input_state_changed__muted) {
std::vector<uint8_t> value({0x03, (uint8_t)Mute::MUTED, (uint8_t)GainMode::MANUAL, 0x04});
EXPECT_CALL(callbacks,
OnExtAudioInStateChanged(test_address, _, 0x03, Mute::MUTED, GainMode::MANUAL));
GetNotificationEvent(0x0032, value);
}
TEST_F(VolumeControlCallbackTest, test_audio_input_state_changed__disabled) {
std::vector<uint8_t> value({0x03, (uint8_t)Mute::DISABLED, (uint8_t)GainMode::MANUAL, 0x04});
EXPECT_CALL(callbacks,
OnExtAudioInStateChanged(test_address, _, 0x03, Mute::DISABLED, GainMode::MANUAL));
GetNotificationEvent(0x0032, value);
}
TEST_F(VolumeControlCallbackTest, test_audio_input_state_changed_malformed) {
EXPECT_CALL(callbacks, OnExtAudioInStateChanged(test_address, _, _, _, _)).Times(0);
std::vector<uint8_t> too_short({0x03, 0x01, 0x02});
GetNotificationEvent(0x0032, too_short);
std::vector<uint8_t> too_long({0x03, 0x01, 0x02, 0x04, 0x05});
GetNotificationEvent(0x0032, too_long);
}
TEST_F(VolumeControlCallbackTest, test_audio_gain_props_changed) {
std::vector<uint8_t> value({0x03, 0x01, 0x02});
EXPECT_CALL(callbacks,
OnExtAudioInGainSettingPropertiesChanged(test_address, _, 0x03, 0x01, 0x02));
GetNotificationEvent(0x0055, value);
}
TEST_F(VolumeControlCallbackTest, test_audio_gain_props_changed_malformed) {
EXPECT_CALL(callbacks, OnExtAudioInGainSettingPropertiesChanged(test_address, _, _, _, _))
.Times(0);
std::vector<uint8_t> too_short({0x03, 0x01});
GetNotificationEvent(0x0055, too_short);
std::vector<uint8_t> too_long({0x03, 0x01, 0x02, 0x03});
GetNotificationEvent(0x0055, too_long);
}
TEST_F(VolumeControlCallbackTest, test_audio_input_status_changed) {
std::vector<uint8_t> value({static_cast<uint8_t>(bluetooth::vc::VolumeInputStatus::Inactive)});
EXPECT_CALL(callbacks, OnExtAudioInStatusChanged(test_address, _,
bluetooth::vc::VolumeInputStatus::Inactive));
GetNotificationEvent(0x0039, value);
}
TEST_F(VolumeControlCallbackTest, test_audio_input_status_changed_malformed) {
EXPECT_CALL(callbacks, OnExtAudioInStatusChanged(test_address, _, _)).Times(0);
std::vector<uint8_t> too_short(0);
GetNotificationEvent(0x0039, too_short);
std::vector<uint8_t> too_long({0x03, 0x01});
GetNotificationEvent(0x0039, too_long);
}
TEST_F(VolumeControlCallbackTest, test_audio_input_description_changed) {
std::string description = "SPDIF";
std::vector<uint8_t> value(description.begin(), description.end());
EXPECT_CALL(callbacks, OnExtAudioInDescriptionChanged(test_address, _, description, _));
GetNotificationEvent(0x005e, value);
}
TEST_F(VolumeControlCallbackTest, test_volume_offset_changed) {
std::vector<uint8_t> value({0x04, 0x05, 0x06});
EXPECT_CALL(callbacks, OnExtAudioOutVolumeOffsetChanged(test_address, 2, 0x0504));
GetNotificationEvent(0x0082, value);
}
TEST_F(VolumeControlCallbackTest, test_volume_offset_changed_malformed) {
EXPECT_CALL(callbacks, OnExtAudioOutVolumeOffsetChanged(test_address, 2, _)).Times(0);
std::vector<uint8_t> too_short({0x04});
GetNotificationEvent(0x0082, too_short);
std::vector<uint8_t> too_long({0x04, 0x05, 0x06, 0x07});
GetNotificationEvent(0x0082, too_long);
}
TEST_F(VolumeControlCallbackTest, test_offset_location_changed) {
std::vector<uint8_t> value({0x01, 0x02, 0x03, 0x04});
EXPECT_CALL(callbacks, OnExtAudioOutLocationChanged(test_address, 2, 0x04030201));
GetNotificationEvent(0x0085, value);
}
TEST_F(VolumeControlCallbackTest, test_offset_location_changed_malformed) {
EXPECT_CALL(callbacks, OnExtAudioOutLocationChanged(test_address, 2, _)).Times(0);
std::vector<uint8_t> too_short({0x04});
GetNotificationEvent(0x0085, too_short);
std::vector<uint8_t> too_long({0x04, 0x05, 0x06});
GetNotificationEvent(0x0085, too_long);
}
TEST_F(VolumeControlCallbackTest, test_audio_output_description_changed) {
std::string descr = "left";
std::vector<uint8_t> value(descr.begin(), descr.end());
EXPECT_CALL(callbacks, OnExtAudioOutDescriptionChanged(test_address, 2, descr));
GetNotificationEvent(0x008a, value);
}
class VolumeControlValueGetTest : public VolumeControlTest {
protected:
const RawAddress test_address = GetTestAddress(0);
uint16_t conn_id = 22;
GATT_READ_OP_CB cb;
void* cb_data;
uint16_t handle;
void SetUp(void) override {
VolumeControlTest::SetUp();
SetSampleDatabase(conn_id);
TestAppRegister();
TestConnect(test_address);
GetConnectedEvent(test_address, conn_id);
GetSearchCompleteEvent(conn_id);
EXPECT_CALL(gatt_queue, ReadCharacteristic(conn_id, _, _, _))
.WillOnce(DoAll(SaveArg<1>(&handle), SaveArg<2>(&cb), SaveArg<3>(&cb_data)));
}
void TearDown(void) override {
TestAppUnregister();
cb = nullptr;
cb_data = nullptr;
handle = 0;
VolumeControlTest::TearDown();
}
};
TEST_F(VolumeControlValueGetTest, test_get_ext_audio_out_volume_offset) {
VolumeControl::Get()->GetExtAudioOutVolumeOffset(test_address, 1);
EXPECT_TRUE(cb);
std::vector<uint8_t> value({0x01, 0x02, 0x03});
EXPECT_CALL(callbacks, OnExtAudioOutVolumeOffsetChanged(test_address, 1, 0x0201));
cb(conn_id, GATT_SUCCESS, handle, (uint16_t)value.size(), value.data(), cb_data);
}
TEST_F(VolumeControlValueGetTest, test_get_ext_audio_out_location) {
VolumeControl::Get()->GetExtAudioOutLocation(test_address, 2);
EXPECT_TRUE(cb);
std::vector<uint8_t> value({0x01, 0x02, 0x03, 0x04});
EXPECT_CALL(callbacks, OnExtAudioOutLocationChanged(test_address, 2, 0x04030201));
cb(conn_id, GATT_SUCCESS, handle, (uint16_t)value.size(), value.data(), cb_data);
}
TEST_F(VolumeControlValueGetTest, test_get_ext_audio_out_description) {
VolumeControl::Get()->GetExtAudioOutDescription(test_address, 2);
EXPECT_TRUE(cb);
std::string descr = "right";
std::vector<uint8_t> value(descr.begin(), descr.end());
EXPECT_CALL(callbacks, OnExtAudioOutDescriptionChanged(test_address, 2, descr));
cb(conn_id, GATT_SUCCESS, handle, (uint16_t)value.size(), value.data(), cb_data);
}
TEST_F(VolumeControlValueGetTest, test_get_ext_audio_in_state) {
VolumeControl::Get()->GetExtAudioInState(test_address, 1);
EXPECT_TRUE(cb);
std::vector<uint8_t> value({0x01, (uint8_t)Mute::NOT_MUTED, (uint8_t)GainMode::MANUAL, 0x03});
EXPECT_CALL(callbacks,
OnExtAudioInStateChanged(test_address, 1, 0x01, Mute::NOT_MUTED, GainMode::MANUAL));
cb(conn_id, GATT_SUCCESS, handle, (uint16_t)value.size(), value.data(), cb_data);
}
TEST_F(VolumeControlValueGetTest, test_get_ext_audio_in_status) {
VolumeControl::Get()->GetExtAudioInStatus(test_address, 0);
EXPECT_TRUE(cb);
std::vector<uint8_t> value({static_cast<uint8_t>(bluetooth::vc::VolumeInputStatus::Active)});
EXPECT_CALL(callbacks,
OnExtAudioInStatusChanged(test_address, 0, bluetooth::vc::VolumeInputStatus::Active));
cb(conn_id, GATT_SUCCESS, handle, (uint16_t)value.size(), value.data(), cb_data);
}
TEST_F(VolumeControlValueGetTest, test_get_ext_audio_in_gain_props) {
VolumeControl::Get()->GetExtAudioInGainProps(test_address, 0);
EXPECT_TRUE(cb);
std::vector<uint8_t> value({0x01, 0x02, 0x03});
EXPECT_CALL(callbacks,
OnExtAudioInGainSettingPropertiesChanged(test_address, 0, 0x01, 0x02, 0x03));
cb(conn_id, GATT_SUCCESS, handle, (uint16_t)value.size(), value.data(), cb_data);
}
TEST_F(VolumeControlValueGetTest, test_get_ext_audio_in_description) {
VolumeControl::Get()->GetExtAudioInDescription(test_address, 1);
EXPECT_TRUE(cb);
std::string description = "AUX-IN";
std::vector<uint8_t> value(description.begin(), description.end());
EXPECT_CALL(callbacks, OnExtAudioInDescriptionChanged(test_address, 1, description, _));
cb(conn_id, GATT_SUCCESS, handle, (uint16_t)value.size(), value.data(), cb_data);
}
TEST_F(VolumeControlValueGetTest, test_get_ext_audio_in_type) {
VolumeControl::Get()->GetExtAudioInType(test_address, 1);
EXPECT_TRUE(cb);
std::vector<uint8_t> value({static_cast<uint8_t>(bluetooth::vc::VolumeInputType::Ambient)});
EXPECT_CALL(callbacks,
OnExtAudioInTypeChanged(test_address, 1, bluetooth::vc::VolumeInputType::Ambient));
cb(conn_id, GATT_SUCCESS, handle, (uint16_t)value.size(), value.data(), cb_data);
}
class VolumeControlValueSetTest : public VolumeControlTest {
protected:
const RawAddress test_address = GetTestAddress(0);
uint16_t conn_id = 22;
void SetUp(void) override {
VolumeControlTest::SetUp();
SetSampleDatabase(conn_id);
TestAppRegister();
TestConnect(test_address);
GetConnectedEvent(test_address, conn_id);
GetSearchCompleteEvent(conn_id);
ON_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, _, GATT_WRITE, _, _))
.WillByDefault([this](uint16_t conn_id, uint16_t /*handle*/, std::vector<uint8_t> value,
tGATT_WRITE_TYPE /*write_type*/, GATT_WRITE_OP_CB cb,
void* cb_data) {
uint8_t write_rsp;
std::vector<uint8_t> ntf_value({value[0], 0, static_cast<uint8_t>(value[1] + 1)});
switch (value[0]) {
case 0x06: // mute
ntf_value[1] = 1;
break;
case 0x05: // unmute
break;
case 0x04: // set abs. volume
ntf_value[0] = value[2];
ntf_value[1] = (value[2] ? 0 : 1);
break;
case 0x03: // unmute rel. up
break;
case 0x02: // unmute rel. down
break;
case 0x01: // rel. up
break;
case 0x00: // rel. down
break;
default:
break;
}
GetNotificationEvent(0x0021, ntf_value);
cb(conn_id, GATT_SUCCESS, 0x0024, 0, &write_rsp, cb_data);
});
}
void GetNotificationEvent(uint16_t handle, const std::vector<uint8_t>& value) {
tBTA_GATTC_NOTIFY event_data = {
.conn_id = conn_id,
.bda = test_address,
.handle = handle,
.len = (uint8_t)value.size(),
.is_notify = true,
};
std::copy(value.begin(), value.end(), event_data.value);
gatt_callback(BTA_GATTC_NOTIF_EVT, reinterpret_cast<tBTA_GATTC*>(&event_data));
}
void TearDown(void) override {
TestAppUnregister();
VolumeControlTest::TearDown();
}
};
TEST_F(VolumeControlValueSetTest, test_volume_operation_failed) {
const std::vector<uint8_t> vol_x10({0x04, 0x00, 0x10});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x10, GATT_WRITE, _, _)).Times(1);
ON_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _))
.WillByDefault(Invoke([this](uint16_t conn_id, uint16_t handle,
std::vector<uint8_t> value, tGATT_WRITE_TYPE /*write_type*/,
GATT_WRITE_OP_CB cb, void* cb_data) {
auto* svc = gatt::FindService(services_map[conn_id], handle);
if (svc == nullptr) {
return;
}
tGATT_STATUS status = GATT_ERROR;
if (cb) {
cb(conn_id, status, handle, value.size(), value.data(), cb_data);
}
}));
EXPECT_CALL(*AlarmMock::Get(), AlarmSetOnMloop(_, _, _, _)).Times(1);
EXPECT_CALL(*AlarmMock::Get(), AlarmCancel(_)).Times(1);
VolumeControl::Get()->SetVolume(test_address, 0x10);
Mock::VerifyAndClearExpectations(&gatt_queue);
Mock::VerifyAndClearExpectations(AlarmMock::Get());
}
TEST_F(VolumeControlValueSetTest, test_volume_operation_failed_due_to_device_disconnection) {
const std::vector<uint8_t> vol_x10({0x04, 0x00, 0x10});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x10, GATT_WRITE, _, _)).Times(1);
ON_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _))
.WillByDefault(Invoke([](uint16_t /*conn_id*/, uint16_t /*handle*/,
std::vector<uint8_t> /*value*/, tGATT_WRITE_TYPE /*write_type*/,
GATT_WRITE_OP_CB /*cb*/, void* /*cb_data*/) {
/* Do nothing */
}));
EXPECT_CALL(*AlarmMock::Get(), AlarmSetOnMloop(_, _, _, _)).Times(0);
alarm_callback_t active_alarm_cb = nullptr;
EXPECT_CALL(*AlarmMock::Get(), AlarmSetOnMloop(_, _, _, _))
.WillOnce(Invoke([&](alarm_t* alarm, uint64_t /*interval_ms*/, alarm_callback_t cb,
void* /*data*/) {
if (alarm) {
alarm->on_main_loop = true;
alarm->cb = cb;
active_alarm_cb = cb;
}
}));
ON_CALL(*AlarmMock::Get(), AlarmCancel(_)).WillByDefault(Invoke([&](alarm_t* alarm) {
if (alarm) {
alarm->cb = nullptr;
alarm->on_main_loop = false;
active_alarm_cb = nullptr;
}
}));
VolumeControl::Get()->SetVolume(test_address, 0x10);
Mock::VerifyAndClearExpectations(&gatt_queue);
Mock::VerifyAndClearExpectations(AlarmMock::Get());
ASSERT_NE(active_alarm_cb, nullptr);
EXPECT_CALL(*AlarmMock::Get(), AlarmCancel(_)).Times(1);
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address));
GetDisconnectedEvent(test_address, conn_id);
ASSERT_EQ(active_alarm_cb, nullptr);
Mock::VerifyAndClearExpectations(&callbacks);
}
TEST_F(VolumeControlValueSetTest, test_set_volume) {
const std::vector<uint8_t> vol_x10({0x04, 0x00, 0x10});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x10, GATT_WRITE, _, _)).Times(1);
VolumeControl::Get()->SetVolume(test_address, 0x10);
// Same volume level should not be applied twice
const std::vector<uint8_t> vol_x10_2({0x04, 0x01, 0x10});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x10_2, GATT_WRITE, _, _))
.Times(0);
VolumeControl::Get()->SetVolume(test_address, 0x10);
const std::vector<uint8_t> vol_x20({0x04, 0x01, 0x20});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x20, GATT_WRITE, _, _)).Times(1);
VolumeControl::Get()->SetVolume(test_address, 0x20);
}
TEST_F(VolumeControlValueSetTest, test_set_volume_to_previous_during_pending) {
// In this test we simulate notification coming later and operations will be queued
ON_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, _, GATT_WRITE, _, _))
.WillByDefault([](uint16_t conn_id, uint16_t handle, std::vector<uint8_t> value,
tGATT_WRITE_TYPE /*write_type*/, GATT_WRITE_OP_CB cb, void* cb_data) {
uint8_t write_rsp;
switch (value[0]) {
case 0x04: // set abs. volume
break;
default:
break;
}
cb(conn_id, GATT_SUCCESS, handle, 0, &write_rsp, cb_data);
});
const std::vector<uint8_t> vol_x10({0x04, /*change_cnt*/ 0, 0x10});
std::vector<uint8_t> ntf_value_x10({0x10, 0, 1});
const std::vector<uint8_t> vol_x11({0x04, /*change_cnt*/ 1, 0x11});
std::vector<uint8_t> ntf_value_x11({0x11, 0, 2});
const std::vector<uint8_t> vol_x10_2({0x04, /*change_cnt*/ 2, 0x10});
std::vector<uint8_t> ntf_value_x10_2({0x10, 0, 3});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x10, GATT_WRITE, _, _)).Times(1);
VolumeControl::Get()->SetVolume(test_address, 0x10);
GetNotificationEvent(0x0021, ntf_value_x10);
// Started
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x11, GATT_WRITE, _, _)).Times(1);
// Allow queuing the same as on the device but different from the started one
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x10_2, GATT_WRITE, _, _))
.Times(1);
VolumeControl::Get()->SetVolume(test_address, 0x11);
VolumeControl::Get()->SetVolume(test_address, 0x10);
GetNotificationEvent(0x0021, ntf_value_x11);
GetNotificationEvent(0x0021, ntf_value_x10_2);
Mock::VerifyAndClearExpectations(&gatt_queue);
}
TEST_F(VolumeControlValueSetTest, test_set_volume_to_same_during_other_pending) {
// In this test we simulate notification coming later and operations will be queued but some will
// be removed from the queue
ON_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, _, GATT_WRITE, _, _))
.WillByDefault([](uint16_t conn_id, uint16_t handle, std::vector<uint8_t> value,
tGATT_WRITE_TYPE /*write_type*/, GATT_WRITE_OP_CB cb, void* cb_data) {
uint8_t write_rsp;
switch (value[0]) {
case 0x06: // mute
break;
case 0x05: // unmute
break;
case 0x04: // set abs. volume
break;
default:
break;
}
cb(conn_id, GATT_SUCCESS, handle, 0, &write_rsp, cb_data);
});
const std::vector<uint8_t> vol_x10({0x04, /*change_cnt*/ 0, 0x10});
std::vector<uint8_t> ntf_value_x10({0x10, 0, 1});
const std::vector<uint8_t> vol_x00({0x04, /*change_cnt*/ 1, 0x00});
std::vector<uint8_t> ntf_value_x00({0x00, 0, 2});
const std::vector<uint8_t> mute({0x06, /*change_cnt*/ 2});
std::vector<uint8_t> ntf_value_mute({0x00, 1, 3});
const std::vector<uint8_t> vol_x00_2({0x04, /*change_cnt*/ 3, 0x00});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x10, GATT_WRITE, _, _)).Times(1);
VolumeControl::Get()->SetVolume(test_address, 0x10);
GetNotificationEvent(0x0021, ntf_value_x10);
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x00, GATT_WRITE, _, _)).Times(1);
VolumeControl::Get()->SetVolume(test_address, 0x00);
GetNotificationEvent(0x0021, ntf_value_x00);
// Started
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, mute, GATT_WRITE, _, _)).Times(1);
// Not queued because the volume is the same as on the device and no volume operation started
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x00_2, GATT_WRITE, _, _))
.Times(0);
VolumeControl::Get()->Mute(test_address);
VolumeControl::Get()->SetVolume(test_address, 0x00);
GetNotificationEvent(0x0021, ntf_value_mute);
Mock::VerifyAndClearExpectations(&gatt_queue);
}
TEST_F(VolumeControlValueSetTest, test_set_volume_to_same_pending) {
// In this test we simulate notification coming later and operations will be queued but some will
// be removed from the queue
ON_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, _, GATT_WRITE, _, _))
.WillByDefault([](uint16_t conn_id, uint16_t handle, std::vector<uint8_t> value,
tGATT_WRITE_TYPE /*write_type*/, GATT_WRITE_OP_CB cb, void* cb_data) {
uint8_t write_rsp;
switch (value[0]) {
case 0x06: // mute
break;
case 0x05: // unmute
break;
case 0x04: // set abs. volume
break;
default:
break;
}
cb(conn_id, GATT_SUCCESS, handle, 0, &write_rsp, cb_data);
});
const std::vector<uint8_t> vol_x10({0x04, /*change_cnt*/ 0, 0x10});
std::vector<uint8_t> ntf_value_x10({0x10, 0, 1});
const std::vector<uint8_t> vol_x00({0x04, /*change_cnt*/ 1, 0x00});
std::vector<uint8_t> ntf_value_x00({0x00, 0, 2});
const std::vector<uint8_t> vol_x00_2({0x04, /*change_cnt*/ 2, 0x00});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x10, GATT_WRITE, _, _)).Times(1);
VolumeControl::Get()->SetVolume(test_address, 0x10);
GetNotificationEvent(0x0021, ntf_value_x10);
// Started
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x00, GATT_WRITE, _, _)).Times(1);
// Not queued because the volume is the same as on started volume operation
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x00_2, GATT_WRITE, _, _))
.Times(0);
VolumeControl::Get()->SetVolume(test_address, 0x00);
VolumeControl::Get()->SetVolume(test_address, 0x00);
GetNotificationEvent(0x0021, ntf_value_x00);
Mock::VerifyAndClearExpectations(&gatt_queue);
}
TEST_F(VolumeControlValueSetTest, test_unmute_to_previous_during_pending) {
// In this test we simulate notification coming later and operations will be queued
ON_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, _, GATT_WRITE, _, _))
.WillByDefault([](uint16_t conn_id, uint16_t handle, std::vector<uint8_t> value,
tGATT_WRITE_TYPE /*write_type*/, GATT_WRITE_OP_CB cb, void* cb_data) {
uint8_t write_rsp;
switch (value[0]) {
case 0x06: // mute
break;
case 0x05: // unmute
break;
case 0x04: // set abs. volume
break;
default:
break;
}
cb(conn_id, GATT_SUCCESS, handle, 0, &write_rsp, cb_data);
});
const std::vector<uint8_t> vol_x10({0x04, /*change_cnt*/ 0, 0x10});
std::vector<uint8_t> ntf_value_x10({0x10, 0, 1});
const std::vector<uint8_t> mute({0x06, /*change_cnt*/ 1});
std::vector<uint8_t> ntf_value_mute({0x10, 1, 2});
const std::vector<uint8_t> unmute({0x05, /*change_cnt*/ 2});
std::vector<uint8_t> ntf_value_unmute({0x10, 0, 3});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x10, GATT_WRITE, _, _)).Times(1);
VolumeControl::Get()->SetVolume(test_address, 0x10);
GetNotificationEvent(0x0021, ntf_value_x10);
// Started
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, mute, GATT_WRITE, _, _)).Times(1);
// Allow queuing the same as on the device but different from the started one
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, unmute, GATT_WRITE, _, _)).Times(1);
VolumeControl::Get()->Mute(test_address);
VolumeControl::Get()->UnMute(test_address);
GetNotificationEvent(0x0021, ntf_value_mute);
GetNotificationEvent(0x0021, ntf_value_unmute);
Mock::VerifyAndClearExpectations(&gatt_queue);
}
TEST_F(VolumeControlValueSetTest, test_mute_to_previous_during_pending) {
// In this test we simulate notification coming later and operations will be queued
ON_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, _, GATT_WRITE, _, _))
.WillByDefault([](uint16_t conn_id, uint16_t handle, std::vector<uint8_t> value,
tGATT_WRITE_TYPE /*write_type*/, GATT_WRITE_OP_CB cb, void* cb_data) {
uint8_t write_rsp;
switch (value[0]) {
case 0x06: // mute
break;
case 0x05: // unmute
break;
case 0x04: // set abs. volume
break;
default:
break;
}
cb(conn_id, GATT_SUCCESS, handle, 0, &write_rsp, cb_data);
});
const std::vector<uint8_t> vol_x10({0x04, /*change_cnt*/ 0, 0x10});
std::vector<uint8_t> ntf_value_x10({0x10, 0, 1});
const std::vector<uint8_t> mute({0x06, /*change_cnt*/ 1});
std::vector<uint8_t> ntf_value_mute({0x10, 1, 2});
const std::vector<uint8_t> unmute({0x05, /*change_cnt*/ 2});
std::vector<uint8_t> ntf_value_unmute({0x10, 0, 3});
const std::vector<uint8_t> mute_2({0x06, /*change_cnt*/ 3});
std::vector<uint8_t> ntf_value_mute_2({0x10, 1, 4});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x10, GATT_WRITE, _, _)).Times(1);
VolumeControl::Get()->SetVolume(test_address, 0x10);
GetNotificationEvent(0x0021, ntf_value_x10);
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, mute, GATT_WRITE, _, _)).Times(1);
VolumeControl::Get()->Mute(test_address);
GetNotificationEvent(0x0021, ntf_value_mute);
// Started
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, unmute, GATT_WRITE, _, _)).Times(1);
// Allow queuing the same as on the device but different from the started one
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, mute_2, GATT_WRITE, _, _)).Times(1);
VolumeControl::Get()->UnMute(test_address);
VolumeControl::Get()->Mute(test_address);
GetNotificationEvent(0x0021, ntf_value_unmute);
GetNotificationEvent(0x0021, ntf_value_mute_2);
Mock::VerifyAndClearExpectations(&gatt_queue);
}
TEST_F(VolumeControlValueSetTest, test_unmute_to_same_during_other_pending) {
// In this test we simulate notification coming later and operations will be queued but some will
// be removed from the queue
ON_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, _, GATT_WRITE, _, _))
.WillByDefault([](uint16_t conn_id, uint16_t handle, std::vector<uint8_t> value,
tGATT_WRITE_TYPE /*write_type*/, GATT_WRITE_OP_CB cb, void* cb_data) {
uint8_t write_rsp;
switch (value[0]) {
case 0x06: // mute
break;
case 0x05: // unmute
break;
case 0x04: // set abs. volume
break;
default:
break;
}
cb(conn_id, GATT_SUCCESS, handle, 0, &write_rsp, cb_data);
});
const std::vector<uint8_t> vol_x10({0x04, /*change_cnt*/ 0, 0x10});
std::vector<uint8_t> ntf_value_x10({0x10, 0, 1});
const std::vector<uint8_t> vol_x11({0x04, /*change_cnt*/ 1, 0x11});
std::vector<uint8_t> ntf_value_x11({0x11, 0, 2});
const std::vector<uint8_t> unmute({0x05, /*change_cnt*/ 2});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x10, GATT_WRITE, _, _)).Times(1);
VolumeControl::Get()->SetVolume(test_address, 0x10);
GetNotificationEvent(0x0021, ntf_value_x10);
// Started
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x11, GATT_WRITE, _, _)).Times(1);
// Not queued because the mute state is the same as on the device and no mute operation started
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, unmute, GATT_WRITE, _, _)).Times(0);
VolumeControl::Get()->SetVolume(test_address, 0x11);
VolumeControl::Get()->UnMute(test_address);
GetNotificationEvent(0x0021, ntf_value_x11);
Mock::VerifyAndClearExpectations(&gatt_queue);
}
TEST_F(VolumeControlValueSetTest, test_mute_to_same_during_other_pending) {
// In this test we simulate notification coming later and operations will be queued but some will
// be removed from the queue
ON_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, _, GATT_WRITE, _, _))
.WillByDefault([](uint16_t conn_id, uint16_t handle, std::vector<uint8_t> value,
tGATT_WRITE_TYPE /*write_type*/, GATT_WRITE_OP_CB cb, void* cb_data) {
uint8_t write_rsp;
switch (value[0]) {
case 0x06: // mute
break;
case 0x05: // unmute
break;
case 0x04: // set abs. volume
break;
default:
break;
}
cb(conn_id, GATT_SUCCESS, handle, 0, &write_rsp, cb_data);
});
const std::vector<uint8_t> vol_x10({0x04, /*change_cnt*/ 0, 0x10});
std::vector<uint8_t> ntf_value_x10({0x10, 0, 1});
const std::vector<uint8_t> mute({0x06, /*change_cnt*/ 1});
std::vector<uint8_t> ntf_value_mute({0x10, 1, 2});
const std::vector<uint8_t> vol_x11({0x04, /*change_cnt*/ 2, 0x11});
std::vector<uint8_t> ntf_value_x11({0x11, 1, 3});
const std::vector<uint8_t> mute_2({0x06, /*change_cnt*/ 3});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x10, GATT_WRITE, _, _)).Times(1);
VolumeControl::Get()->SetVolume(test_address, 0x10);
GetNotificationEvent(0x0021, ntf_value_x10);
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, mute, GATT_WRITE, _, _)).Times(1);
VolumeControl::Get()->Mute(test_address);
GetNotificationEvent(0x0021, ntf_value_mute);
// Started
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x11, GATT_WRITE, _, _)).Times(1);
// Not queued because the mute state is the same as on the device and no unmute operation started
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, mute_2, GATT_WRITE, _, _)).Times(0);
VolumeControl::Get()->SetVolume(test_address, 0x11);
VolumeControl::Get()->Mute(test_address);
GetNotificationEvent(0x0021, ntf_value_x11);
Mock::VerifyAndClearExpectations(&gatt_queue);
}
TEST_F(VolumeControlValueSetTest, test_remove_pending_mute_operation) {
// In this test we simulate notification coming later and operations will be queued but some will
// be removed from the queue
ON_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, _, GATT_WRITE, _, _))
.WillByDefault([](uint16_t conn_id, uint16_t handle, std::vector<uint8_t> value,
tGATT_WRITE_TYPE /*write_type*/, GATT_WRITE_OP_CB cb, void* cb_data) {
uint8_t write_rsp;
switch (value[0]) {
case 0x06: // mute
break;
case 0x05: // unmute
break;
case 0x04: // set abs. volume
break;
default:
break;
}
cb(conn_id, GATT_SUCCESS, handle, 0, &write_rsp, cb_data);
});
const std::vector<uint8_t> vol_x10({0x04, /*change_cnt*/ 0, 0x10});
std::vector<uint8_t> ntf_value_x10({0x10, 0, 1});
const std::vector<uint8_t> mute({0x06, /*change_cnt*/ 1});
const std::vector<uint8_t> unmute({0x05, /*change_cnt*/ 1});
// Started
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x10, GATT_WRITE, _, _)).Times(1);
// All (un)mute operations was removed and not queued at the end as last has same value as remote
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, mute, GATT_WRITE, _, _)).Times(0);
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, unmute, GATT_WRITE, _, _)).Times(0);
VolumeControl::Get()->SetVolume(test_address, 0x10);
VolumeControl::Get()->Mute(test_address);
VolumeControl::Get()->UnMute(test_address);
VolumeControl::Get()->Mute(test_address);
VolumeControl::Get()->UnMute(test_address);
GetNotificationEvent(0x0021, ntf_value_x10);
Mock::VerifyAndClearExpectations(&gatt_queue);
}
TEST_F(VolumeControlValueSetTest, test_set_volume_stress) {
uint8_t n = 100;
uint8_t change_cnt = 0;
uint8_t vol = 1;
for (uint8_t i = 1; i < n; i++) {
const std::vector<uint8_t> vol_x10({0x04, change_cnt, vol});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x10, GATT_WRITE, _, _))
.Times(1);
VolumeControl::Get()->SetVolume(test_address, vol);
Mock::VerifyAndClearExpectations(&gatt_queue);
change_cnt++;
vol++;
}
}
TEST_F(VolumeControlValueSetTest, test_set_volume_stress_2) {
uint8_t change_cnt = 0;
uint8_t vol = 1;
// In this test we simulate notification coming later and operations will be queued
ON_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, _, GATT_WRITE, _, _))
.WillByDefault([](uint16_t conn_id, uint16_t handle, std::vector<uint8_t> value,
tGATT_WRITE_TYPE /*write_type*/, GATT_WRITE_OP_CB cb, void* cb_data) {
uint8_t write_rsp;
switch (value[0]) {
case 0x04: // set abs. volume
break;
default:
break;
}
cb(conn_id, GATT_SUCCESS, handle, 0, &write_rsp, cb_data);
});
const std::vector<uint8_t> vol_x10({0x04, /*change_cnt*/ 0, 0x10});
std::vector<uint8_t> ntf_value_x10({0x10, 0, 1});
const std::vector<uint8_t> vol_x11({0x04, /*change_cnt*/ 1, 0x11});
std::vector<uint8_t> ntf_value_x11({0x11, 0, 2});
const std::vector<uint8_t> vol_x12({0x04, /*change_cnt*/ 2, 0x12});
std::vector<uint8_t> ntf_value_x12({0x12, 0, 3});
const std::vector<uint8_t> vol_x13({0x04, /*change_cnt*/ 3, 0x13});
std::vector<uint8_t> ntf_value_x13({0x13, 0, 4});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x10, GATT_WRITE, _, _)).Times(1);
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x11, GATT_WRITE, _, _)).Times(1);
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x12, GATT_WRITE, _, _)).Times(1);
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x13, GATT_WRITE, _, _)).Times(1);
VolumeControl::Get()->SetVolume(test_address, 0x10);
VolumeControl::Get()->SetVolume(test_address, 0x11);
GetNotificationEvent(0x0021, ntf_value_x10);
GetNotificationEvent(0x0021, ntf_value_x11);
VolumeControl::Get()->SetVolume(test_address, 0x12);
VolumeControl::Get()->SetVolume(test_address, 0x13);
GetNotificationEvent(0x0021, ntf_value_x12);
GetNotificationEvent(0x0021, ntf_value_x13);
Mock::VerifyAndClearExpectations(&gatt_queue);
}
TEST_F(VolumeControlValueSetTest, test_set_volume_stress_3) {
uint8_t change_cnt = 0;
uint8_t vol = 1;
// In this test we simulate notification coming later and operations will be queued but some will
// be removed from the queue
ON_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, _, GATT_WRITE, _, _))
.WillByDefault([](uint16_t conn_id, uint16_t handle, std::vector<uint8_t> value,
tGATT_WRITE_TYPE /*write_type*/, GATT_WRITE_OP_CB cb, void* cb_data) {
uint8_t write_rsp;
switch (value[0]) {
case 0x04: // set abs. volume
break;
default:
break;
}
cb(conn_id, GATT_SUCCESS, handle, 0, &write_rsp, cb_data);
});
const std::vector<uint8_t> vol_x10({0x04, /*change_cnt*/ 0, 0x10});
std::vector<uint8_t> ntf_value_x10({0x10, 0, 1});
const std::vector<uint8_t> vol_x11({0x04, /*change_cnt*/ 1, 0x11});
std::vector<uint8_t> ntf_value_x11({0x11, 0, 2});
const std::vector<uint8_t> vol_x12({0x04, /*change_cnt*/ 1, 0x12});
std::vector<uint8_t> ntf_value_x12({0x12, 0, 3});
const std::vector<uint8_t> vol_x13({0x04, /*change_cnt*/ 1, 0x13});
std::vector<uint8_t> ntf_value_x13({0x13, 0, 4});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x10, GATT_WRITE, _, _)).Times(1);
// Those two belowe will be removed from the queue
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x11, GATT_WRITE, _, _)).Times(0);
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x12, GATT_WRITE, _, _)).Times(0);
// This one shall be sent with a change count 1.
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, vol_x13, GATT_WRITE, _, _)).Times(1);
VolumeControl::Get()->SetVolume(test_address, 0x10);
VolumeControl::Get()->SetVolume(test_address, 0x11);
VolumeControl::Get()->SetVolume(test_address, 0x12);
VolumeControl::Get()->SetVolume(test_address, 0x13);
GetNotificationEvent(0x0021, ntf_value_x10);
GetNotificationEvent(0x0021, ntf_value_x11);
GetNotificationEvent(0x0021, ntf_value_x12);
GetNotificationEvent(0x0021, ntf_value_x13);
Mock::VerifyAndClearExpectations(&gatt_queue);
}
TEST_F(VolumeControlValueSetTest, test_mute_unmute) {
std::vector<uint8_t> mute_x0({0x06, 0x00});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, mute_x0, GATT_WRITE, _, _)).Times(1);
// Don't mute when already muted
std::vector<uint8_t> mute_x1({0x06, 0x01});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, mute_x1, GATT_WRITE, _, _)).Times(0);
VolumeControl::Get()->Mute(test_address);
VolumeControl::Get()->Mute(test_address);
// Needs to be muted to unmute
std::vector<uint8_t> unmute_x1({0x05, 0x01});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, unmute_x1, GATT_WRITE, _, _))
.Times(1);
// Don't unmute when already unmuted
std::vector<uint8_t> unmute_x2({0x05, 0x02});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, unmute_x2, GATT_WRITE, _, _))
.Times(0);
VolumeControl::Get()->UnMute(test_address);
VolumeControl::Get()->UnMute(test_address);
}
TEST_F(VolumeControlValueSetTest, test_set_ext_audio_out_volume_offset) {
std::vector<uint8_t> expected_data({0x01, 0x00, 0x34, 0x12});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0088, expected_data, GATT_WRITE, _, _));
VolumeControl::Get()->SetExtAudioOutVolumeOffset(test_address, 2, 0x1234);
}
TEST_F(VolumeControlValueSetTest, test_set_ext_audio_out_location) {
std::vector<uint8_t> expected_data({0x44, 0x33, 0x22, 0x11});
EXPECT_CALL(gatt_queue,
WriteCharacteristic(conn_id, 0x0085, expected_data, GATT_WRITE_NO_RSP, _, _));
VolumeControl::Get()->SetExtAudioOutLocation(test_address, 2, 0x11223344);
}
TEST_F(VolumeControlValueSetTest, test_set_ext_audio_out_location_non_writable) {
EXPECT_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _)).Times(0);
VolumeControl::Get()->SetExtAudioOutLocation(test_address, 1, 0x11223344);
}
TEST_F(VolumeControlValueSetTest, test_set_ext_audio_out_description) {
std::string descr = "right front";
std::vector<uint8_t> expected_data(descr.begin(), descr.end());
EXPECT_CALL(gatt_queue,
WriteCharacteristic(conn_id, 0x008a, expected_data, GATT_WRITE_NO_RSP, _, _));
VolumeControl::Get()->SetExtAudioOutDescription(test_address, 2, descr);
}
TEST_F(VolumeControlValueSetTest, test_set_ext_audio_out_description_non_writable) {
std::string descr = "left front";
EXPECT_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _)).Times(0);
VolumeControl::Get()->SetExtAudioOutDescription(test_address, 1, descr);
}
TEST_F(VolumeControlValueSetTest, test_set_ext_audio_in_description) {
std::string descr = "HDMI";
std::vector<uint8_t> expected_data(descr.begin(), descr.end());
EXPECT_CALL(gatt_queue,
WriteCharacteristic(conn_id, 0x005e, expected_data, GATT_WRITE_NO_RSP, _, _));
VolumeControl::Get()->SetExtAudioInDescription(test_address, 1, descr);
}
TEST_F(VolumeControlValueSetTest, test_set_ext_audio_in_description_non_writable) {
std::string descr = "AUX";
std::vector<uint8_t> expected_data(descr.begin(), descr.end());
EXPECT_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _)).Times(0);
VolumeControl::Get()->SetExtAudioInDescription(test_address, 0, descr);
}
TEST_F(VolumeControlValueSetTest, test_set_ext_audio_in_gain_setting) {
std::vector<uint8_t> expected_data({0x01, 0x00, 0x34});
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x005c, expected_data, GATT_WRITE, _, _));
VolumeControl::Get()->SetExtAudioInGainSetting(test_address, 1, 0x34);
}
TEST_F(VolumeControlValueSetTest, test_set_ext_audio_in_gain_mode) {
std::vector<uint8_t> mode_manual({0x04, 0x00}); // 0x04 is the opcode for Manual
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x005c, mode_manual, GATT_WRITE, _, _));
VolumeControl::Get()->SetExtAudioInGainMode(test_address, 1, GainMode::MANUAL);
std::vector<uint8_t> mode_automatic({0x05, 0x00}); // 0x05 is the opcode for Automatic
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x005c, mode_automatic, GATT_WRITE, _, _));
VolumeControl::Get()->SetExtAudioInGainMode(test_address, 1, GainMode::AUTOMATIC);
}
TEST_F(VolumeControlValueSetTest, test_set_ext_audio_in_gain_mute) {
std::vector<uint8_t> mute({0x03, 0x00}); // 0x03 is the opcode for Mute
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x005c, mute, GATT_WRITE, _, _));
VolumeControl::Get()->SetExtAudioInMute(test_address, 1, Mute::MUTED);
std::vector<uint8_t> unmute({0x02, 0x00}); // 0x02 is the opcode for UnMute
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x005c, unmute, GATT_WRITE, _, _));
VolumeControl::Get()->SetExtAudioInMute(test_address, 1, Mute::NOT_MUTED);
}
class VolumeControlGroupId : public VolumeControlTest {
protected:
const RawAddress test_address_1 = GetTestAddress(0);
const RawAddress test_address_2 = GetTestAddress(1);
std::vector<RawAddress> csis_group = {test_address_1, test_address_2};
uint16_t conn_id_1 = 22;
uint16_t conn_id_2 = 33;
void SetUp(void) override {
VolumeControlTest::SetUp();
if (!com::android::bluetooth::flags::vcp_handle_group_id_internally()) {
ON_CALL(mock_csis_client_module_, Get()).WillByDefault(Return(&mock_csis_client_module_));
// Report working CSIS
ON_CALL(mock_csis_client_module_, IsCsisClientRunning()).WillByDefault(Return(true));
ON_CALL(mock_csis_client_module_, GetDeviceList(_)).WillByDefault(Return(csis_group));
ON_CALL(mock_csis_client_module_, GetGroupId(_, _)).WillByDefault(Return(group_id));
}
SetSampleDatabase(conn_id_1);
SetSampleDatabase(conn_id_2);
TestAppRegister();
}
void TearDown(void) override {
TestAppUnregister();
VolumeControlTest::TearDown();
}
void GetNotificationEvent(uint16_t conn_id, const RawAddress& test_address, uint16_t handle,
const std::vector<uint8_t>& value) {
tBTA_GATTC_NOTIFY event_data = {
.conn_id = conn_id,
.bda = test_address,
.handle = handle,
.len = (uint8_t)value.size(),
.is_notify = true,
};
std::copy(value.begin(), value.end(), event_data.value);
gatt_callback(BTA_GATTC_NOTIF_EVT, reinterpret_cast<tBTA_GATTC*>(&event_data));
}
};
TEST_F(VolumeControlGroupId, test_set_volume) {
TestConnect(test_address_1);
GetConnectedEvent(test_address_1, conn_id_1);
GetSearchCompleteEvent(conn_id_1);
TestConnect(test_address_2);
GetConnectedEvent(test_address_2, conn_id_2);
GetSearchCompleteEvent(conn_id_2);
/* Set value for the group */
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id_1, 0x0024, _, GATT_WRITE, _, _));
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id_2, 0x0024, _, GATT_WRITE, _, _));
VolumeControl::Get()->SetVolume(group_id, 10);
/* Now inject notification and make sure callback is sent up to Java layer */
EXPECT_CALL(callbacks, OnGroupVolumeStateChanged(group_id, 0x03, true, false));
std::vector<uint8_t> value({0x03, 0x01, 0x02});
GetNotificationEvent(conn_id_1, test_address_1, 0x0021, value);
GetNotificationEvent(conn_id_2, test_address_2, 0x0021, value);
/* Verify exactly one operation with this exact value is queued for each
* device */
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id_1, 0x0024, _, GATT_WRITE, _, _)).Times(1);
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id_2, 0x0024, _, GATT_WRITE, _, _)).Times(1);
VolumeControl::Get()->SetVolume(test_address_1, 20);
VolumeControl::Get()->SetVolume(test_address_2, 20);
VolumeControl::Get()->SetVolume(test_address_1, 20);
VolumeControl::Get()->SetVolume(test_address_2, 20);
EXPECT_CALL(callbacks, OnVolumeStateChanged(test_address_1, 20, false, _, false));
EXPECT_CALL(callbacks, OnVolumeStateChanged(test_address_2, 20, false, _, false));
std::vector<uint8_t> value2({20, 0x00, 0x03});
GetNotificationEvent(conn_id_1, test_address_1, 0x0021, value2);
GetNotificationEvent(conn_id_2, test_address_2, 0x0021, value2);
}
TEST_F(VolumeControlGroupId, test_set_volume_device_not_ready_no_respond) {
/* Make sure we did not get responds to the initial reads,
* so that the device was not marked as ready yet.
*/
do_not_respond_to_reads = true;
TestConnect(test_address_1);
GetConnectedEvent(test_address_1, conn_id_1);
GetSearchCompleteEvent(conn_id_1);
TestConnect(test_address_2);
GetConnectedEvent(test_address_2, conn_id_2);
GetSearchCompleteEvent(conn_id_2);
/* Set value for the group */
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id_1, 0x0024, _, GATT_WRITE, _, _)).Times(0);
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id_2, 0x0024, _, GATT_WRITE, _, _)).Times(0);
VolumeControl::Get()->SetVolume(group_id, 10);
}
TEST_F(VolumeControlGroupId, test_set_volume_device_not_ready_no_group) {
com::android::bluetooth::flags::provider_->vcp_handle_group_id_internally(true);
// Simulate late group adding
ON_CALL(mock_groups_module_, GetGroupId(_, _))
.WillByDefault(Return(bluetooth::groups::kGroupUnknown));
TestConnect(test_address_1);
GetConnectedEvent(test_address_1, conn_id_1);
GetSearchCompleteEvent(conn_id_1);
TestConnect(test_address_2);
GetConnectedEvent(test_address_2, conn_id_2);
GetSearchCompleteEvent(conn_id_2);
const std::vector<uint8_t> vol_x10({0x04, /*change_cnt*/ 0, 0x10});
std::vector<uint8_t> ntf_value_x10({0x10, 0, 1});
const std::vector<uint8_t> vol_x11({0x04, /*change_cnt*/ 1, 0x11});
std::vector<uint8_t> ntf_value_x11({0x11, 0, 2});
const std::vector<uint8_t> vol_x12({0x04, /*change_cnt*/ 2, 0x12});
std::vector<uint8_t> ntf_value_x12({0x12, 0, 3});
// Devices without group
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id_1, 0x0024, vol_x10, GATT_WRITE, _, _))
.Times(0);
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id_2, 0x0024, vol_x10, GATT_WRITE, _, _))
.Times(0);
VolumeControl::Get()->SetVolume(group_id, 0x10);
GetNotificationEvent(conn_id_1, test_address_1, 0x0021, ntf_value_x10);
GetNotificationEvent(conn_id_2, test_address_2, 0x0021, ntf_value_x10);
// First device added to group
group_callbacks_->OnGroupAdded(test_address_1, ::bluetooth::le_audio::uuid::kCapServiceUuid,
group_id);
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id_1, 0x0024, vol_x11, GATT_WRITE, _, _))
.Times(1);
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id_2, 0x0024, vol_x11, GATT_WRITE, _, _))
.Times(0);
VolumeControl::Get()->SetVolume(group_id, 0x11);
GetNotificationEvent(conn_id_1, test_address_1, 0x0021, ntf_value_x11);
GetNotificationEvent(conn_id_2, test_address_2, 0x0021, ntf_value_x11);
// Second device added to group
group_callbacks_->OnGroupMemberAdded(test_address_2, group_id);
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id_1, 0x0024, vol_x12, GATT_WRITE, _, _))
.Times(1);
EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id_2, 0x0024, vol_x12, GATT_WRITE, _, _))
.Times(1);
VolumeControl::Get()->SetVolume(group_id, 0x12);
GetNotificationEvent(conn_id_1, test_address_1, 0x0021, ntf_value_x12);
GetNotificationEvent(conn_id_2, test_address_2, 0x0021, ntf_value_x12);
Mock::VerifyAndClearExpectations(&gatt_queue);
}
TEST_F(VolumeControlGroupId, autonomus_test_set_volume) {
TestConnect(test_address_1);
GetConnectedEvent(test_address_1, conn_id_1);
GetSearchCompleteEvent(conn_id_1);
TestConnect(test_address_2);
GetConnectedEvent(test_address_2, conn_id_2);
GetSearchCompleteEvent(conn_id_2);
/* Now inject notification and make sure callback is sent up to Java layer */
EXPECT_CALL(callbacks, OnGroupVolumeStateChanged(group_id, 0x03, false, true));
std::vector<uint8_t> value({0x03, 0x00, 0x02});
GetNotificationEvent(conn_id_1, test_address_1, 0x0021, value);
GetNotificationEvent(conn_id_2, test_address_2, 0x0021, value);
}
TEST_F(VolumeControlGroupId, autonomus_single_device_test_set_volume) {
TestConnect(test_address_1);
GetConnectedEvent(test_address_1, conn_id_1);
GetSearchCompleteEvent(conn_id_1);
TestConnect(test_address_2);
GetConnectedEvent(test_address_2, conn_id_2);
GetSearchCompleteEvent(conn_id_2);
/* Disconnect one device. */
EXPECT_CALL(callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address_1));
GetDisconnectedEvent(test_address_1, conn_id_1);
/* Now inject notification and make sure callback is sent up to Java layer */
EXPECT_CALL(callbacks, OnGroupVolumeStateChanged(group_id, 0x03, false, true));
std::vector<uint8_t> value({0x03, 0x00, 0x02});
GetNotificationEvent(conn_id_2, test_address_2, 0x0021, value);
}
} // namespace
} // namespace internal
} // namespace vc
} // namespace bluetooth