/* * 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 "bind_helpers.h" #include "bta_gatt_api_mock.h" #include "bta_gatt_queue_mock.h" #include "bta_vc_api.h" #include "btm_api_mock.h" #include "gatt/database_builder.h" #include "hardware/bt_gatt_types.h" #include "mock_csis_client.h" #include "stack/include/bt_uuid16.h" #include "test/common/mock_functions.h" #include "types.h" #include "types/bluetooth/uuid.h" #include "types/raw_address.h" void btif_storage_add_volume_control(const RawAddress& addr, bool auto_conn) {} namespace bluetooth { namespace vc { namespace internal { namespace { using base::Bind; using base::Unretained; 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; RawAddress GetTestAddress(int index) { EXPECT_LT(index, UINT8_MAX); RawAddress result = { {0xC0, 0xDE, 0xC0, 0xDE, 0x00, static_cast(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, uint8_t num_offset), (override)); MOCK_METHOD((void), OnVolumeStateChanged, (const RawAddress& address, uint8_t volume, bool mute, 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)); }; 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) { /* TODO Place holder */ } 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) { /* TODO Place holder for AICS */ } 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 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 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: ASSERT_TRUE(false); return; } if (do_not_respond_to_reads) return; cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(), cb_data); })); } protected: bool do_not_respond_to_reads = false; void SetUp(void) override { bluetooth::manager::SetMockBtmInterface(&btm_interface); MockCsisClient::SetMockInstanceForTesting(&mock_csis_client_module_); gatt::SetMockBtaGattInterface(&gatt_interface); gatt::SetMockBtaGattQueue(&gatt_queue); callbacks.reset(new MockVolumeControlCallbacks()); reset_mock_function_count_map(); ON_CALL(btm_interface, IsLinkKeyKnown(_, _)) .WillByDefault(DoAll(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; })); // 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); })); } void TearDown(void) override { services_map.clear(); callbacks.reset(); gatt::SetMockBtaGattQueue(nullptr); gatt::SetMockBtaGattInterface(nullptr); bluetooth::manager::SetMockBtmInterface(nullptr); } void TestAppRegister(void) { BtaAppRegisterCallback app_register_callback; EXPECT_CALL(gatt_interface, AppRegister(_, _, _)) .WillOnce(DoAll(SaveArg<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); VolumeControl::Initialize(callbacks.get(), 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, false)); 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, std::map& 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 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 handles) { SetSampleDatabase(conn_id); TestAppRegister(); TestConnect(address); GetConnectedEvent(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); TestAppUnregister(); } void GetConnectedEvent(const RawAddress& address, uint16_t conn_id) { tBTA_GATTC_OPEN event_data = { .status = GATT_SUCCESS, .conn_id = conn_id, .client_if = gatt_if, .remote_bda = address, .transport = GATT_TRANSPORT_LE, .mtu = 240, }; gatt_callback(BTA_GATTC_OPEN_EVT, (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, (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, (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, IsLinkKeyKnown(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 ? BTM_SUCCESS : BTM_FAILED_ON_SECURITY); } GetEncryptionCompleteEvt(bd_addr); return 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 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); } std::unique_ptr callbacks; bluetooth::manager::MockBtmInterface btm_interface; MockCsisClient mock_csis_client_module_; gatt::MockBtaGattInterface gatt_interface; gatt::MockBtaGattQueue gatt_queue; tBTA_GATTC_CBACK* gatt_callback; const uint8_t gatt_if = 0xff; std::map> services_map; }; 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<0>(&gatt_callback), SaveArg<1>(&app_register_callback))); VolumeControl::Initialize( callbacks.get(), 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.get(), base::DoNothing()); VolumeControl* volume_control_p = VolumeControl::Get(); VolumeControl::Initialize(callbacks.get(), base::DoNothing()); ASSERT_EQ(volume_control_p, VolumeControl::Get()); VolumeControl::CleanUp(); } TEST_F(VolumeControlTest, test_cleanup_initialized) { VolumeControl::Initialize(callbacks.get(), 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.get()); EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address)) .Times(1); TestRemove(test_address, conn_id); Mock::VerifyAndClearExpectations(callbacks.get()); EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address)) .Times(1); ON_CALL(btm_interface, IsLinkKeyKnown(_, _)) .WillByDefault(DoAll(Return(false))); VolumeControl::Get()->Connect(test_address); Mock::VerifyAndClearExpectations(callbacks.get()); 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, 2)).Times(0); GetConnectedEvent(test_address, 1); Mock::VerifyAndClearExpectations(callbacks.get()); // Remote disconnects in the middle of the service discovery EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address)); GetDisconnectedEvent(test_address, 1); Mock::VerifyAndClearExpectations(callbacks.get()); // 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, 2)); GetConnectedEvent(test_address, 1); Mock::VerifyAndClearExpectations(callbacks.get()); // Request connect when the remote was already connected by another service EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, 2)).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.get()); 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, false)).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, 2)); GetConnectedEvent(address, 1); Mock::VerifyAndClearExpectations(callbacks.get()); // 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.get()); 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); // 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(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, IsLinkKeyKnown(test_address, _)) .WillByDefault(DoAll(Return(true))); ON_CALL(btm_interface, SetEncryption(test_address, _, _, _, _)) .WillByDefault(Return(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.get()); 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.get()); 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, _)); EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::CONNECTED, test_address)); GetConnectedEvent(test_address, 1); GetSearchCompleteEvent(1); Mock::VerifyAndClearExpectations(callbacks.get()); 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.get()); 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.get()); TestAppUnregister(); } TEST_F(VolumeControlTest, test_subscribe_vcs_volume_state) { std::map handles({{0x0021, 0x0022}}); TestSubscribeNotifications(GetTestAddress(0), 1, handles); } TEST_F(VolumeControlTest, test_subscribe_vocs_offset_state) { std::map handles({{0x0072, 0x0073}, {0x0082, 0x0083}}); TestSubscribeNotifications(GetTestAddress(0), 1, handles); } TEST_F(VolumeControlTest, test_subscribe_vocs_offset_location) { std::map handles({{0x0085, 0x0086}}); TestSubscribeNotifications(GetTestAddress(0), 1, handles); } TEST_F(VolumeControlTest, test_subscribe_vocs_output_description) { std::map 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, _, _, false)); std::vector handles({0x0021}); TestReadCharacteristic(test_address, 1, handles); } TEST_F(VolumeControlTest, test_read_vcs_volume_flags) { std::vector handles({0x0026}); TestReadCharacteristic(GetTestAddress(0), 1, handles); } TEST_F(VolumeControlTest, test_read_vocs_volume_offset) { const RawAddress test_address = GetTestAddress(0); EXPECT_CALL(*callbacks, OnExtAudioOutVolumeOffsetChanged(test_address, 1, _)); EXPECT_CALL(*callbacks, OnExtAudioOutVolumeOffsetChanged(test_address, 2, _)); std::vector handles({0x0072, 0x0082}); TestReadCharacteristic(test_address, 1, handles); } TEST_F(VolumeControlTest, test_read_vocs_offset_location) { const RawAddress test_address = GetTestAddress(0); EXPECT_CALL(*callbacks, OnExtAudioOutLocationChanged(test_address, 1, _)); EXPECT_CALL(*callbacks, OnExtAudioOutLocationChanged(test_address, 2, _)); std::vector handles({0x0075, 0x0085}); TestReadCharacteristic(test_address, 1, handles); } TEST_F(VolumeControlTest, test_read_vocs_output_description) { const RawAddress test_address = GetTestAddress(0); EXPECT_CALL(*callbacks, OnExtAudioOutDescriptionChanged(test_address, 1, _)); EXPECT_CALL(*callbacks, OnExtAudioOutDescriptionChanged(test_address, 2, _)); std::vector 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, 2)); GetConnectedEvent(test_address, 1); GetSearchCompleteEvent(1); Mock::VerifyAndClearExpectations(callbacks.get()); 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, 0)); GetConnectedEvent(test_address, 1); GetSearchCompleteEvent(1); Mock::VerifyAndClearExpectations(callbacks.get()); 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, 1)); GetConnectedEvent(test_address, 1); GetSearchCompleteEvent(1); Mock::VerifyAndClearExpectations(callbacks.get()); TestAppUnregister(); } TEST_F(VolumeControlTest, test_read_vcs_database_out_of_sync) { const RawAddress test_address = GetTestAddress(0); EXPECT_CALL(*callbacks, OnVolumeStateChanged(test_address, _, _, false)); std::vector 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 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, std::vector& 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, (tBTA_GATTC*)&event_data); } }; TEST_F(VolumeControlCallbackTest, test_volume_state_changed_stress) { std::vector value({0x03, 0x01, 0x02}); EXPECT_CALL(*callbacks, OnVolumeStateChanged(test_address, 0x03, true, true)); GetNotificationEvent(0x0021, value); } TEST_F(VolumeControlCallbackTest, test_volume_state_changed_malformed) { EXPECT_CALL(*callbacks, OnVolumeStateChanged(test_address, _, _, _)).Times(0); std::vector too_short({0x03, 0x01}); GetNotificationEvent(0x0021, too_short); std::vector too_long({0x03, 0x01, 0x02, 0x03}); GetNotificationEvent(0x0021, too_long); } TEST_F(VolumeControlCallbackTest, test_volume_offset_changed) { std::vector 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 too_short({0x04}); GetNotificationEvent(0x0082, too_short); std::vector too_long({0x04, 0x05, 0x06, 0x07}); GetNotificationEvent(0x0082, too_long); } TEST_F(VolumeControlCallbackTest, test_offset_location_changed) { std::vector 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 too_short({0x04}); GetNotificationEvent(0x0085, too_short); std::vector too_long({0x04, 0x05, 0x06}); GetNotificationEvent(0x0085, too_long); } TEST_F(VolumeControlCallbackTest, test_audio_output_description_changed) { std::string descr = "left"; std::vector 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 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 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 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); } 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 value, tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb, void* cb_data) { uint8_t write_rsp; std::vector ntf_value( {value[0], 0, static_cast(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, std::vector& 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, (tBTA_GATTC*)&event_data); } void TearDown(void) override { TestAppUnregister(); VolumeControlTest::TearDown(); } }; TEST_F(VolumeControlValueSetTest, test_volume_operation_failed) { const std::vector 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 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); })); ASSERT_EQ(0, get_func_call_count("alarm_set_on_mloop")); ASSERT_EQ(0, get_func_call_count("alarm_cancel")); VolumeControl::Get()->SetVolume(test_address, 0x10); Mock::VerifyAndClearExpectations(&gatt_queue); ASSERT_EQ(1, get_func_call_count("alarm_set_on_mloop")); ASSERT_EQ(1, get_func_call_count("alarm_cancel")); } TEST_F(VolumeControlValueSetTest, test_volume_operation_failed_due_to_device_disconnection) { const std::vector 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 value, tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb, void* cb_data) { /* Do nothing */ })); ASSERT_EQ(0, get_func_call_count("alarm_set_on_mloop")); ASSERT_EQ(0, get_func_call_count("alarm_cancel")); VolumeControl::Get()->SetVolume(test_address, 0x10); Mock::VerifyAndClearExpectations(&gatt_queue); EXPECT_CALL(*callbacks, OnConnectionState(ConnectionState::DISCONNECTED, test_address)); GetDisconnectedEvent(test_address, conn_id); Mock::VerifyAndClearExpectations(callbacks.get()); ASSERT_EQ(1, get_func_call_count("alarm_set_on_mloop")); ASSERT_EQ(1, get_func_call_count("alarm_cancel")); } TEST_F(VolumeControlValueSetTest, test_set_volume) { const std::vector 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 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 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_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 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 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 vol_x10({0x04, /*change_cnt*/ 0, 0x10}); std::vector ntf_value_x10({0x10, 0, 1}); const std::vector vol_x11({0x04, /*change_cnt*/ 1, 0x11}); std::vector ntf_value_x11({0x11, 0, 2}); const std::vector vol_x12({0x04, /*change_cnt*/ 2, 0x12}); std::vector ntf_value_x12({0x12, 0, 3}); const std::vector vol_x13({0x04, /*change_cnt*/ 3, 0x13}); std::vector 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 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 vol_x10({0x04, /*change_cnt*/ 0, 0x10}); std::vector ntf_value_x10({0x10, 0, 1}); const std::vector vol_x11({0x04, /*change_cnt*/ 1, 0x11}); std::vector ntf_value_x11({0x11, 0, 2}); const std::vector vol_x12({0x04, /*change_cnt*/ 1, 0x12}); std::vector ntf_value_x12({0x12, 0, 3}); const std::vector vol_x13({0x04, /*change_cnt*/ 1, 0x13}); std::vector 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 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 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 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 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 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 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 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); } class VolumeControlCsis : public VolumeControlTest { protected: const RawAddress test_address_1 = GetTestAddress(0); const RawAddress test_address_2 = GetTestAddress(1); std::vector csis_group = {test_address_1, test_address_2}; uint16_t conn_id_1 = 22; uint16_t conn_id_2 = 33; int group_id = 5; void SetUp(void) override { VolumeControlTest::SetUp(); 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, std::vector& 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, (tBTA_GATTC*)&event_data); } }; TEST_F(VolumeControlCsis, 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 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); std::vector value2({20, 0x00, 0x03}); GetNotificationEvent(conn_id_1, test_address_1, 0x0021, value2); GetNotificationEvent(conn_id_2, test_address_2, 0x0021, value2); } TEST_F(VolumeControlCsis, test_set_volume_device_not_ready) { /* 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(VolumeControlCsis, 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 value({0x03, 0x00, 0x02}); GetNotificationEvent(conn_id_1, test_address_1, 0x0021, value); GetNotificationEvent(conn_id_2, test_address_2, 0x0021, value); } TEST_F(VolumeControlCsis, 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 value({0x03, 0x00, 0x02}); GetNotificationEvent(conn_id_2, test_address_2, 0x0021, value); } } // namespace } // namespace internal } // namespace vc } // namespace bluetooth