diff options
23 files changed, 2884 insertions, 1 deletions
diff --git a/system/binder/Android.bp b/system/binder/Android.bp index 2534bdecdd..ca53ff3540 100644 --- a/system/binder/Android.bp +++ b/system/binder/Android.bp @@ -28,6 +28,7 @@ cc_library_shared { "android/bluetooth/IBluetoothProfileServiceConnection.aidl", "android/bluetooth/IBluetoothHeadset.aidl", "android/bluetooth/IBluetoothHearingAid.aidl", + "android/bluetooth/IBluetoothVolumeControl.aidl", "android/bluetooth/IBluetoothHidHost.aidl", "android/bluetooth/IBluetoothPan.aidl", "android/bluetooth/IBluetoothManager.aidl", @@ -102,6 +103,7 @@ filegroup { "android/bluetooth/IBluetoothProfileServiceConnection.aidl", "android/bluetooth/IBluetoothHeadset.aidl", "android/bluetooth/IBluetoothHearingAid.aidl", + "android/bluetooth/IBluetoothVolumeControl.aidl", "android/bluetooth/IBluetoothHidHost.aidl", "android/bluetooth/IBluetoothLeAudio.aidl", "android/bluetooth/IBluetoothPan.aidl", diff --git a/system/binder/android/bluetooth/IBluetoothVolumeControl.aidl b/system/binder/android/bluetooth/IBluetoothVolumeControl.aidl new file mode 100644 index 0000000000..66d3396563 --- /dev/null +++ b/system/binder/android/bluetooth/IBluetoothVolumeControl.aidl @@ -0,0 +1,39 @@ +/* + * 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. + */ + +package android.bluetooth; + +import android.bluetooth.BluetoothDevice; + +/** + * APIs for Bluetooth Volume Control service + * + * @hide + */ +interface IBluetoothVolumeControl { + /* Public API */ + boolean connect(in BluetoothDevice device); + boolean disconnect(in BluetoothDevice device); + List<BluetoothDevice> getConnectedDevices(); + List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states); + int getConnectionState(in BluetoothDevice device); + boolean setConnectionPolicy(in BluetoothDevice device, int connectionPolicy); + int getConnectionPolicy(in BluetoothDevice device); + + void setVolume(in BluetoothDevice device, int volume); + void setVolumeGroup(int group_id, int volume); +} diff --git a/system/bta/Android.bp b/system/bta/Android.bp index bb06c7c37b..a11e63b296 100644 --- a/system/bta/Android.bp +++ b/system/bta/Android.bp @@ -80,6 +80,8 @@ cc_library_static { "gatt/bta_gatts_utils.cc", "gatt/database.cc", "gatt/database_builder.cc", + "vc/device.cc", + "vc/vc.cc", "hearing_aid/hearing_aid.cc", "hearing_aid/hearing_aid_audio_source.cc", "hf_client/bta_hf_client_act.cc", @@ -255,3 +257,46 @@ cc_test { ], cflags: ["-DBUILDCFG"], } + + +// bta unit tests for host +// ======================================================== +cc_test { + name: "bluetooth_vc_test", + test_suites: ["device-tests"], + defaults: [ + "fluoride_bta_defaults", + "clang_coverage_bin", + ], + host_supported: true, + include_dirs: [ + "packages/modules/Bluetooth/system", + "packages/modules/Bluetooth/system/bta/include", + "packages/modules/Bluetooth/system/bta/test/common", + "packages/modules/Bluetooth/system/stack/include", + ], + srcs : [ + "gatt/database.cc", + "gatt/database_builder.cc", + "test/common/bta_gatt_api_mock.cc", + "test/common/bta_gatt_queue_mock.cc", + "test/common/btm_api_mock.cc", + "vc/devices_test.cc", + "vc/device.cc", + "vc/vc.cc", + "vc/vc_test.cc", + ], + shared_libs: [ + "libprotobuf-cpp-lite", + "libcrypto", + ], + static_libs : [ + "crypto_toolbox_for_tests", + "libgmock", + "libbt-common", + "libbt-protos-lite", + ], + sanitize: { + cfi: false, + }, +} diff --git a/system/bta/include/bta_vc_api.h b/system/bta/include/bta_vc_api.h new file mode 100644 index 0000000000..66a47cd113 --- /dev/null +++ b/system/bta/include/bta_vc_api.h @@ -0,0 +1,40 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <hardware/bt_vc.h> + +class VolumeControl { + public: + virtual ~VolumeControl() = default; + + static void Initialize(bluetooth::vc::VolumeControlCallbacks* callbacks); + static void CleanUp(); + static VolumeControl* Get(); + static void DebugDump(int fd); + + static void AddFromStorage(const RawAddress& address, bool auto_connect); + + static bool IsVolumeControlRunning(); + + /* Volume Control Server (VCS) */ + virtual void Connect(const RawAddress& address) = 0; + virtual void Disconnect(const RawAddress& address) = 0; + virtual void SetVolume(std::variant<RawAddress, int> addr_or_group_id, + uint8_t volume) = 0; +}; diff --git a/system/bta/test/common/bta_gatt_api_mock.cc b/system/bta/test/common/bta_gatt_api_mock.cc new file mode 100644 index 0000000000..628c4d51c9 --- /dev/null +++ b/system/bta/test/common/bta_gatt_api_mock.cc @@ -0,0 +1,86 @@ +/* + * 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 "bta_gatt_api_mock.h" + +static gatt::MockBtaGattInterface* gatt_interface = nullptr; + +void gatt::SetMockBtaGattInterface( + MockBtaGattInterface* mock_bta_gatt_interface) { + gatt_interface = mock_bta_gatt_interface; +} + +void BTA_GATTC_AppRegister(tBTA_GATTC_CBACK* p_client_cb, + BtaAppRegisterCallback cb, bool eatt_support) { + gatt_interface->AppRegister(p_client_cb, cb, eatt_support); +} + +void BTA_GATTC_AppDeregister(tGATT_IF client_if) { + gatt_interface->AppDeregister(client_if); +} + +void BTA_GATTC_Open(tGATT_IF client_if, const RawAddress& remote_bda, + bool is_direct, tBT_TRANSPORT transport, + bool opportunistic) { + gatt_interface->Open(client_if, remote_bda, is_direct, transport, + opportunistic); +} + +void BTA_GATTC_Open(tGATT_IF client_if, const RawAddress& remote_bda, + bool is_direct, bool opportunistic) { + gatt_interface->Open(client_if, remote_bda, is_direct, opportunistic); +} + +void BTA_GATTC_CancelOpen(tGATT_IF client_if, const RawAddress& remote_bda, + bool is_direct) { + gatt_interface->CancelOpen(client_if, remote_bda, is_direct); +} + +void BTA_GATTC_Close(uint16_t conn_id) { gatt_interface->Close(conn_id); } + +void BTA_GATTC_ServiceSearchRequest(uint16_t conn_id, + const bluetooth::Uuid* p_srvc_uuid) { + gatt_interface->ServiceSearchRequest(conn_id, p_srvc_uuid); +} + +const std::list<gatt::Service>* BTA_GATTC_GetServices(uint16_t conn_id) { + return gatt_interface->GetServices(conn_id); +} + +const gatt::Characteristic* BTA_GATTC_GetCharacteristic(uint16_t conn_id, + uint16_t handle) { + return gatt_interface->GetCharacteristic(conn_id, handle); +} + +const gatt::Service* BTA_GATTC_GetOwningService(uint16_t conn_id, + uint16_t handle) { + return gatt_interface->GetOwningService(conn_id, handle); +} + +tGATT_STATUS BTA_GATTC_RegisterForNotifications(tGATT_IF client_if, + const RawAddress& remote_bda, + uint16_t handle) { + return gatt_interface->RegisterForNotifications(client_if, remote_bda, + handle); +} + +tGATT_STATUS BTA_GATTC_DeregisterForNotifications(tGATT_IF client_if, + const RawAddress& remote_bda, + uint16_t handle) { + return gatt_interface->DeregisterForNotifications(client_if, remote_bda, + handle); +} diff --git a/system/bta/test/common/bta_gatt_api_mock.h b/system/bta/test/common/bta_gatt_api_mock.h new file mode 100644 index 0000000000..3ddd6da76c --- /dev/null +++ b/system/bta/test/common/bta_gatt_api_mock.h @@ -0,0 +1,95 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include <base/callback.h> +#include <gmock/gmock.h> + +#include "bta_gatt_api.h" + +namespace gatt { + +class BtaGattInterface { + public: + virtual void AppRegister(tBTA_GATTC_CBACK* p_client_cb, + BtaAppRegisterCallback cb, bool eatt_support) = 0; + virtual void AppDeregister(tGATT_IF client_if) = 0; + virtual void Open(tGATT_IF client_if, const RawAddress& remote_bda, + bool is_direct, tBT_TRANSPORT transport, + bool opportunistic) = 0; + virtual void Open(tGATT_IF client_if, const RawAddress& remote_bda, + bool is_direct, bool opportunistic) = 0; + virtual void CancelOpen(tGATT_IF client_if, const RawAddress& remote_bda, + bool is_direct) = 0; + virtual void Close(uint16_t conn_id) = 0; + virtual void ServiceSearchRequest(uint16_t conn_id, + const bluetooth::Uuid* p_srvc_uuid) = 0; + virtual const std::list<Service>* GetServices(uint16_t conn_id) = 0; + virtual const Characteristic* GetCharacteristic(uint16_t conn_id, + uint16_t handle) = 0; + virtual const Service* GetOwningService(uint16_t conn_id, + uint16_t handle) = 0; + virtual tGATT_STATUS RegisterForNotifications(tGATT_IF client_if, + const RawAddress& remote_bda, + uint16_t handle) = 0; + virtual tGATT_STATUS DeregisterForNotifications(tGATT_IF client_if, + const RawAddress& remote_bda, + uint16_t handle) = 0; + virtual ~BtaGattInterface() = default; +}; + +class MockBtaGattInterface : public BtaGattInterface { + public: + MOCK_METHOD((void), AppRegister, + (tBTA_GATTC_CBACK * p_client_cb, BtaAppRegisterCallback cb, + bool eatt_support), + (override)); + MOCK_METHOD((void), AppDeregister, (tGATT_IF client_if), (override)); + MOCK_METHOD((void), Open, + (tGATT_IF client_if, const RawAddress& remote_bda, bool is_direct, + tBT_TRANSPORT transport, bool opportunistic)); + MOCK_METHOD((void), Open, + (tGATT_IF client_if, const RawAddress& remote_bda, bool is_direct, + bool opportunistic)); + MOCK_METHOD((void), CancelOpen, + (tGATT_IF client_if, const RawAddress& remote_bda, + bool is_direct)); + MOCK_METHOD((void), Close, (uint16_t conn_id)); + MOCK_METHOD((void), ServiceSearchRequest, + (uint16_t conn_id, const bluetooth::Uuid* p_srvc_uuid)); + MOCK_METHOD((std::list<Service>*), GetServices, (uint16_t conn_id)); + MOCK_METHOD((const Characteristic*), GetCharacteristic, + (uint16_t conn_id, uint16_t handle)); + MOCK_METHOD((const Service*), GetOwningService, + (uint16_t conn_id, uint16_t handle)); + MOCK_METHOD((tGATT_STATUS), RegisterForNotifications, + (tGATT_IF client_if, const RawAddress& remote_bda, + uint16_t handle)); + MOCK_METHOD((tGATT_STATUS), DeregisterForNotifications, + (tGATT_IF client_if, const RawAddress& remote_bda, + uint16_t handle)); +}; + +/** + * Set the {@link MockBtaGattInterface} for testing + * + * @param mock_bta_gatt_interface pointer to mock bta gatt interface, + * could be null + */ +void SetMockBtaGattInterface(MockBtaGattInterface* mock_bta_gatt_interface); + +} // namespace gatt diff --git a/system/bta/test/common/bta_gatt_queue_mock.cc b/system/bta/test/common/bta_gatt_queue_mock.cc new file mode 100644 index 0000000000..737b341c3b --- /dev/null +++ b/system/bta/test/common/bta_gatt_queue_mock.cc @@ -0,0 +1,46 @@ +/* + * 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 "bta_gatt_queue_mock.h" + +static gatt::MockBtaGattQueue* gatt_queue = nullptr; + +void gatt::SetMockBtaGattQueue(MockBtaGattQueue* mock_bta_gatt_queue) { + gatt_queue = mock_bta_gatt_queue; +} + +void BtaGattQueue::Clean(uint16_t conn_id) { gatt_queue->Clean(conn_id); } + +void BtaGattQueue::ReadCharacteristic(uint16_t conn_id, uint16_t handle, + GATT_READ_OP_CB cb, void* cb_data) { + gatt_queue->ReadCharacteristic(conn_id, handle, cb, cb_data); +} + +void BtaGattQueue::WriteCharacteristic(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) { + gatt_queue->WriteCharacteristic(conn_id, handle, value, write_type, cb, + cb_data); +} + +void BtaGattQueue::WriteDescriptor(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) { + gatt_queue->WriteDescriptor(conn_id, handle, value, write_type, cb, cb_data); +} diff --git a/system/bta/test/common/bta_gatt_queue_mock.h b/system/bta/test/common/bta_gatt_queue_mock.h new file mode 100644 index 0000000000..127bdc8b73 --- /dev/null +++ b/system/bta/test/common/bta_gatt_queue_mock.h @@ -0,0 +1,49 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <gmock/gmock.h> + +#include "bta_gatt_queue.h" + +namespace gatt { + +class MockBtaGattQueue { + public: + MOCK_METHOD((void), Clean, (uint16_t conn_id)); + MOCK_METHOD((void), ReadCharacteristic, + (uint16_t conn_id, uint16_t handle, GATT_READ_OP_CB cb, + void* cb_data)); + MOCK_METHOD((void), WriteCharacteristic, + (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)); + MOCK_METHOD((void), WriteDescriptor, + (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)); +}; + +/** + * Set the {@link MockBtaGattQueue} for testing + * + * @param mock_bta_gatt_queue pointer to mock bta gatt queue, could be null + */ +void SetMockBtaGattQueue(MockBtaGattQueue* mock_bta_gatt_queue); + +} // namespace gatt diff --git a/system/bta/test/common/btm_api_mock.cc b/system/bta/test/common/btm_api_mock.cc new file mode 100644 index 0000000000..88d511a2de --- /dev/null +++ b/system/bta/test/common/btm_api_mock.cc @@ -0,0 +1,40 @@ +/* + * 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 "btm_api_mock.h" + +static bluetooth::manager::MockBtmInterface* btm_interface = nullptr; + +void bluetooth::manager::SetMockBtmInterface( + MockBtmInterface* mock_btm_interface) { + btm_interface = mock_btm_interface; +} + +bool BTM_GetSecurityFlagsByTransport(const RawAddress& bd_addr, + uint8_t* p_sec_flags, + tBT_TRANSPORT transport) { + return btm_interface->GetSecurityFlagsByTransport(bd_addr, p_sec_flags, + transport); +} + +tBTM_STATUS BTM_SetEncryption(const RawAddress& bd_addr, + tBT_TRANSPORT transport, + tBTM_SEC_CALLBACK* p_callback, void* p_ref_data, + tBTM_BLE_SEC_ACT sec_act) { + return btm_interface->SetEncryption(bd_addr, transport, p_callback, + p_ref_data, sec_act); +} diff --git a/system/bta/test/common/btm_api_mock.h b/system/bta/test/common/btm_api_mock.h new file mode 100644 index 0000000000..d337b85f50 --- /dev/null +++ b/system/bta/test/common/btm_api_mock.h @@ -0,0 +1,60 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include <gmock/gmock.h> + +#include "btm_api.h" + +namespace bluetooth { +namespace manager { + +class BtmInterface { + public: + virtual bool GetSecurityFlagsByTransport(const RawAddress& bd_addr, + uint8_t* p_sec_flags, + tBT_TRANSPORT transport) = 0; + virtual tBTM_STATUS SetEncryption(const RawAddress& bd_addr, + tBT_TRANSPORT transport, + tBTM_SEC_CALLBACK* p_callback, + void* p_ref_data, + tBTM_BLE_SEC_ACT sec_act) = 0; + virtual ~BtmInterface() = default; +}; + +class MockBtmInterface : public BtmInterface { + public: + MOCK_METHOD((bool), GetSecurityFlagsByTransport, + (const RawAddress& bd_addr, uint8_t* p_sec_flags, + tBT_TRANSPORT transport), + (override)); + MOCK_METHOD((tBTM_STATUS), SetEncryption, + (const RawAddress& bd_addr, tBT_TRANSPORT transport, + tBTM_SEC_CALLBACK* p_callback, void* p_ref_data, + tBTM_BLE_SEC_ACT sec_act), + (override)); +}; + +/** + * Set the {@link MockBtmInterface} for testing + * + * @param mock_btm_interface pointer to mock btm interface, could be null + */ +void SetMockBtmInterface(MockBtmInterface* mock_btm_interface); + +} // namespace manager +} // namespace bluetooth diff --git a/system/bta/vc/device.cc b/system/bta/vc/device.cc new file mode 100644 index 0000000000..9e059e7819 --- /dev/null +++ b/system/bta/vc/device.cc @@ -0,0 +1,245 @@ +/* + * Copyright 2020 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 <map> +#include <vector> + +#include "bta_gatt_api.h" +#include "bta_gatt_queue.h" +#include "devices.h" +#include "gatt_api.h" +#include "stack/btm/btm_sec.h" + +using namespace bluetooth::vc::internal; + +void VolumeControlDevice::Disconnect(tGATT_IF gatt_if) { + LOG(INFO) << __func__ << ": " << this->ToString(); + + if (IsConnected()) { + if (volume_state_handle != 0) + BTA_GATTC_DeregisterForNotifications(gatt_if, address, + volume_state_handle); + + if (volume_flags_handle != 0) + BTA_GATTC_DeregisterForNotifications(gatt_if, address, + volume_flags_handle); + + BtaGattQueue::Clean(connection_id); + BTA_GATTC_Close(connection_id); + connection_id = GATT_INVALID_CONN_ID; + } else { + BTA_GATTC_CancelOpen(gatt_if, address, false); + } + + device_ready = false; + handles_pending.clear(); +} + +/* + * Find the handle for the client characteristics configuration of a given + * characteristics + */ +uint16_t VolumeControlDevice::find_ccc_handle(uint16_t chrc_handle) { + const gatt::Characteristic* p_char = + BTA_GATTC_GetCharacteristic(connection_id, chrc_handle); + if (!p_char) { + LOG(WARNING) << __func__ << ": no such handle=" << loghex(chrc_handle); + return 0; + } + + for (const gatt::Descriptor& desc : p_char->descriptors) { + if (desc.uuid == Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)) + return desc.handle; + } + + return 0; +} + +bool VolumeControlDevice::set_volume_control_service_handles( + const gatt::Service& service) { + uint16_t state_handle = 0, state_ccc_handle = 0, control_point_handle = 0, + flags_handle = 0, flags_ccc_handle = 0; + + for (const gatt::Characteristic& chrc : service.characteristics) { + if (chrc.uuid == kVolumeControlStateUuid) { + state_handle = chrc.value_handle; + state_ccc_handle = find_ccc_handle(chrc.value_handle); + } else if (chrc.uuid == kVolumeControlPointUuid) { + control_point_handle = chrc.value_handle; + } else if (chrc.uuid == kVolumeFlagsUuid) { + flags_handle = chrc.value_handle; + flags_ccc_handle = find_ccc_handle(chrc.value_handle); + } else { + LOG(WARNING) << __func__ << ": unknown characteristic=" << chrc.uuid; + } + } + + // Validate service handles + if (GATT_HANDLE_IS_VALID(state_handle) && + GATT_HANDLE_IS_VALID(state_ccc_handle) && + GATT_HANDLE_IS_VALID(control_point_handle) && + GATT_HANDLE_IS_VALID(flags_handle) + /* volume_flags_ccc_handle is optional */) { + volume_state_handle = state_handle; + volume_state_ccc_handle = state_ccc_handle; + volume_control_point_handle = control_point_handle; + volume_flags_handle = flags_handle; + volume_flags_ccc_handle = flags_ccc_handle; + return true; + } + + return false; +} + +bool VolumeControlDevice::UpdateHandles(void) { + ResetHandles(); + + bool vcs_found = false; + const std::list<gatt::Service>* services = + BTA_GATTC_GetServices(connection_id); + if (services == nullptr) { + LOG(ERROR) << "No services found"; + return false; + } + + for (auto const& service : *services) { + if (service.uuid == kVolumeControlUuid) { + LOG(INFO) << "Found VCS, handle=" << loghex(service.handle); + vcs_found = set_volume_control_service_handles(service); + if (!vcs_found) break; + } + } + + return vcs_found; +} + +void VolumeControlDevice::ResetHandles(void) { + device_ready = false; + + // the handles are not valid, so discard pending GATT operations + BtaGattQueue::Clean(connection_id); + + volume_state_handle = 0; + volume_state_ccc_handle = 0; + volume_control_point_handle = 0; + volume_flags_handle = 0; + volume_flags_ccc_handle = 0; +} + +void VolumeControlDevice::ControlPointOperation(uint8_t opcode, + const std::vector<uint8_t>* arg, + GATT_WRITE_OP_CB cb, + void* cb_data) { + std::vector<uint8_t> set_value({opcode, change_counter}); + if (arg != nullptr) + set_value.insert(set_value.end(), (*arg).begin(), (*arg).end()); + + BtaGattQueue::WriteCharacteristic(connection_id, volume_control_point_handle, + set_value, GATT_WRITE, cb, cb_data); +} + +bool VolumeControlDevice::subscribe_for_notifications(tGATT_IF gatt_if, + uint16_t handle, + uint16_t ccc_handle, + GATT_WRITE_OP_CB cb) { + tGATT_STATUS status = + BTA_GATTC_RegisterForNotifications(gatt_if, address, handle); + if (status != GATT_SUCCESS) { + LOG(ERROR) << __func__ << ": failed, status=" << loghex(+status); + return false; + } + + std::vector<uint8_t> value(2); + uint8_t* ptr = value.data(); + UINT16_TO_STREAM(ptr, GATT_CHAR_CLIENT_CONFIG_NOTIFICATION); + BtaGattQueue::WriteDescriptor(connection_id, ccc_handle, std::move(value), + GATT_WRITE, cb, nullptr); + + return true; +} + +/** + * Enqueue GATT requests that are required by the Volume Control to be + * functional. This includes State characteristics read and subscription. + * Those characteristics contain the change counter needed to send any request + * via Control Point. Once completed successfully, the device can be stored + * and reported as connected. In each case we subscribe first to be sure we do + * not miss any value change. + */ +bool VolumeControlDevice::EnqueueInitialRequests( + tGATT_IF gatt_if, GATT_READ_OP_CB chrc_read_cb, + GATT_WRITE_OP_CB cccd_write_cb) { + handles_pending.clear(); + handles_pending.insert(volume_state_handle); + handles_pending.insert(volume_state_ccc_handle); + if (!subscribe_for_notifications(gatt_if, volume_state_handle, + volume_state_ccc_handle, cccd_write_cb)) { + return false; + } + + BtaGattQueue::ReadCharacteristic(connection_id, volume_state_handle, + chrc_read_cb, nullptr); + + return true; +} + +/** + * Enqueue the remaining requests. Those are not so crucial and can be done + * once Volume Control instance indicates it's readiness to profile. + * This includes characteristics read and subscription. + * In each case we subscribe first to be sure we do not miss any value change. + */ +void VolumeControlDevice::EnqueueRemainingRequests( + tGATT_IF gatt_if, GATT_READ_OP_CB chrc_read_cb, + GATT_WRITE_OP_CB cccd_write_cb) { + std::map<uint16_t, uint16_t> handle_pairs{ + {volume_flags_handle, volume_flags_ccc_handle}, + }; + + for (auto const& handles : handle_pairs) { + if (GATT_HANDLE_IS_VALID(handles.second)) { + subscribe_for_notifications(gatt_if, handles.first, handles.second, + cccd_write_cb); + } + + BtaGattQueue::ReadCharacteristic(connection_id, handles.first, chrc_read_cb, + nullptr); + } +} + +bool VolumeControlDevice::VerifyReady(uint16_t handle) { + handles_pending.erase(handle); + device_ready = handles_pending.size() == 0; + return device_ready; +} + +bool VolumeControlDevice::IsEncryptionEnabled() { + uint8_t sec_flag = 0; + bool device_found = + BTM_GetSecurityFlagsByTransport(address, &sec_flag, BT_TRANSPORT_LE); + LOG(INFO) << __func__ << ": found=" << static_cast<int>(device_found) + << " sec_flag=" << loghex(sec_flag); + return device_found && (sec_flag & BTM_SEC_FLAG_ENCRYPTED); +} + +bool VolumeControlDevice::EnableEncryption(tBTM_SEC_CALLBACK* callback) { + int result = BTM_SetEncryption(address, BT_TRANSPORT_LE, callback, nullptr, + BTM_BLE_SEC_ENCRYPT); + LOG(INFO) << __func__ << ": result=" << +result; + // TODO: should we care about the result?? + return true; +} diff --git a/system/bta/vc/devices.h b/system/bta/vc/devices.h new file mode 100644 index 0000000000..7a496ad8f1 --- /dev/null +++ b/system/bta/vc/devices.h @@ -0,0 +1,190 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <hardware/bt_vc.h> + +#include <unordered_set> +#include <vector> + +#include "bta_gatt_api.h" +#include "types.h" + +namespace bluetooth { +namespace vc { +namespace internal { + +class VolumeControlDevice { + public: + RawAddress address; + /* This is true only during first connection to profile, until we store the + * device + */ + bool first_connection; + + /* we are making active attempt to connect to this device, 'direct connect'. + * This is true only during initial phase of first connection. */ + bool connecting_actively; + + bool service_changed_rcvd; + + uint8_t volume; + uint8_t change_counter; + bool mute; + uint8_t flags; + + uint16_t connection_id; + + /* Volume Control Service */ + uint16_t volume_state_handle; + uint16_t volume_state_ccc_handle; + uint16_t volume_control_point_handle; + uint16_t volume_flags_handle; + uint16_t volume_flags_ccc_handle; + + bool device_ready; /* Set when device read server status and registgered for + notifications */ + + VolumeControlDevice(const RawAddress& address, bool first_connection) + : address(address), + first_connection(first_connection), + connecting_actively(first_connection), + service_changed_rcvd(false), + volume(0), + change_counter(0), + mute(false), + connection_id(GATT_INVALID_CONN_ID), + volume_state_handle(0), + volume_state_ccc_handle(0), + volume_control_point_handle(0), + volume_flags_handle(0), + volume_flags_ccc_handle(0), + device_ready(false) {} + + ~VolumeControlDevice() = default; + + inline std::string ToString() { return address.ToString(); } + + void DebugDump(int fd) { dprintf(fd, "%s\n", this->ToString().c_str()); } + + bool IsConnected() { return connection_id != GATT_INVALID_CONN_ID; } + + void Disconnect(tGATT_IF gatt_if); + + bool UpdateHandles(void); + + void ResetHandles(void); + + bool HasHandles(void) { return GATT_HANDLE_IS_VALID(volume_state_handle); } + + void ControlPointOperation(uint8_t opcode, const std::vector<uint8_t>* arg, + GATT_WRITE_OP_CB cb, void* cb_data); + bool IsEncryptionEnabled(); + + bool EnableEncryption(tBTM_SEC_CALLBACK* callback); + + bool EnqueueInitialRequests(tGATT_IF gatt_if, GATT_READ_OP_CB chrc_read_cb, + GATT_WRITE_OP_CB cccd_write_cb); + void EnqueueRemainingRequests(tGATT_IF gatt_if, GATT_READ_OP_CB chrc_read_cb, + GATT_WRITE_OP_CB cccd_write_cb); + bool VerifyReady(uint16_t handle); + + private: + /* + * This is used to track the pending GATT operation handles. Once the list is + * empty the device is assumed ready and connected. We are doing it because we + * want to make sure all the required characteristics and descritors are + * available on server side. + */ + std::unordered_set<uint16_t> handles_pending; + + uint16_t find_ccc_handle(uint16_t chrc_handle); + bool set_volume_control_service_handles(const gatt::Service& service); + bool subscribe_for_notifications(tGATT_IF gatt_if, uint16_t handle, + uint16_t ccc_handle, GATT_WRITE_OP_CB cb); +}; + +class VolumeControlDevices { + public: + void Add(const RawAddress& address, bool first_connection) { + if (FindByAddress(address) != nullptr) return; + + devices_.emplace_back(address, first_connection); + } + + void Remove(const RawAddress& address) { + for (auto it = devices_.begin(); it != devices_.end(); it++) { + if (it->address == address) { + it = devices_.erase(it); + break; + } + } + } + + VolumeControlDevice* FindByAddress(const RawAddress& address) { + auto iter = std::find_if(devices_.begin(), devices_.end(), + [&address](const VolumeControlDevice& device) { + return device.address == address; + }); + + return (iter == devices_.end()) ? nullptr : &(*iter); + } + + VolumeControlDevice* FindByConnId(uint16_t connection_id) { + auto iter = + std::find_if(devices_.begin(), devices_.end(), + [&connection_id](const VolumeControlDevice& device) { + return device.connection_id == connection_id; + }); + + return (iter == devices_.end()) ? nullptr : &(*iter); + } + + size_t Size() { return (devices_.size()); } + + void Clear() { devices_.clear(); } + + void DebugDump(int fd) { + for (auto& device : devices_) { + device.DebugDump(fd); + } + } + + void Disconnect(tGATT_IF gatt_if) { + for (auto& device : devices_) { + device.Disconnect(gatt_if); + } + } + + void ControlPointOperation(std::vector<RawAddress>& devices, uint8_t opcode, + const std::vector<uint8_t>* arg, + GATT_WRITE_OP_CB cb, void* cb_data) { + for (auto& addr : devices) { + VolumeControlDevice* device = FindByAddress(addr); + if (device && device->IsConnected()) + device->ControlPointOperation(opcode, arg, cb, cb_data); + } + } + + private: + std::vector<VolumeControlDevice> devices_; +}; + +} // namespace internal +} // namespace vc +} // namespace bluetooth diff --git a/system/bta/vc/devices_test.cc b/system/bta/vc/devices_test.cc new file mode 100644 index 0000000000..e1fb3d4ec7 --- /dev/null +++ b/system/bta/vc/devices_test.cc @@ -0,0 +1,470 @@ +/* + * 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 "devices.h" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include <map> + +#include "bta_gatt_api_mock.h" +#include "bta_gatt_queue_mock.h" +#include "btm_api_mock.h" +#include "gatt/database_builder.h" + +namespace bluetooth { +namespace vc { +namespace internal { + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Invoke; +using ::testing::Mock; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::Test; + +RawAddress GetTestAddress(int index) { + CHECK_LT(index, UINT8_MAX); + RawAddress result = { + {0xC0, 0xDE, 0xC0, 0xDE, 0x00, static_cast<uint8_t>(index)}}; + return result; +} + +class VolumeControlDevicesTest : public ::testing::Test { + protected: + void SetUp() override { + devices_ = new VolumeControlDevices(); + gatt::SetMockBtaGattInterface(&gatt_interface); + gatt::SetMockBtaGattQueue(&gatt_queue); + } + + void TearDown() override { + gatt::SetMockBtaGattQueue(nullptr); + gatt::SetMockBtaGattInterface(nullptr); + delete devices_; + } + + VolumeControlDevices* devices_ = nullptr; + gatt::MockBtaGattInterface gatt_interface; + gatt::MockBtaGattQueue gatt_queue; +}; + +TEST_F(VolumeControlDevicesTest, test_add) { + RawAddress test_address_0 = GetTestAddress(0); + ASSERT_EQ((size_t)0, devices_->Size()); + devices_->Add(test_address_0, true); + ASSERT_EQ((size_t)1, devices_->Size()); +} + +TEST_F(VolumeControlDevicesTest, test_add_twice) { + RawAddress test_address_0 = GetTestAddress(0); + ASSERT_EQ((size_t)0, devices_->Size()); + devices_->Add(test_address_0, true); + devices_->Add(test_address_0, true); + ASSERT_EQ((size_t)1, devices_->Size()); +} + +TEST_F(VolumeControlDevicesTest, test_remove) { + RawAddress test_address_0 = GetTestAddress(0); + RawAddress test_address_1 = GetTestAddress(1); + devices_->Add(test_address_0, true); + devices_->Add(test_address_1, true); + ASSERT_EQ((size_t)2, devices_->Size()); + devices_->Remove(test_address_0); + ASSERT_EQ((size_t)1, devices_->Size()); +} + +TEST_F(VolumeControlDevicesTest, test_clear) { + RawAddress test_address_0 = GetTestAddress(0); + ASSERT_EQ((size_t)0, devices_->Size()); + devices_->Add(test_address_0, true); + ASSERT_EQ((size_t)1, devices_->Size()); + devices_->Clear(); + ASSERT_EQ((size_t)0, devices_->Size()); +} + +TEST_F(VolumeControlDevicesTest, test_find_by_address) { + RawAddress test_address_0 = GetTestAddress(0); + RawAddress test_address_1 = GetTestAddress(1); + RawAddress test_address_2 = GetTestAddress(2); + devices_->Add(test_address_0, true); + devices_->Add(test_address_1, false); + devices_->Add(test_address_2, true); + VolumeControlDevice* device = devices_->FindByAddress(test_address_1); + ASSERT_NE(nullptr, device); + ASSERT_EQ(test_address_1, device->address); +} + +TEST_F(VolumeControlDevicesTest, test_find_by_conn_id) { + RawAddress test_address_0 = GetTestAddress(0); + devices_->Add(test_address_0, true); + VolumeControlDevice* test_device = devices_->FindByAddress(test_address_0); + test_device->connection_id = 0x0005; + ASSERT_NE(nullptr, devices_->FindByConnId(test_device->connection_id)); +} + +TEST_F(VolumeControlDevicesTest, test_disconnect) { + RawAddress test_address_0 = GetTestAddress(0); + RawAddress test_address_1 = GetTestAddress(1); + devices_->Add(test_address_0, true); + devices_->Add(test_address_1, true); + VolumeControlDevice* test_device_0 = devices_->FindByAddress(test_address_0); + test_device_0->connection_id = 0x0005; + tGATT_IF gatt_if = 8; + EXPECT_CALL(gatt_interface, Close(test_device_0->connection_id)); + EXPECT_CALL(gatt_interface, CancelOpen(gatt_if, test_address_1, _)); + devices_->Disconnect(gatt_if); +} + +TEST_F(VolumeControlDevicesTest, test_control_point_operation) { + uint8_t opcode = 50; + std::vector<RawAddress> devices; + + for (int i = 5; i > 0; i--) { + RawAddress test_address = GetTestAddress(i); + devices.push_back(test_address); + uint8_t change_counter = 10 * i; + uint16_t control_point_handle = 0x0020 + i; + uint16_t connection_id = i; + devices_->Add(test_address, true); + VolumeControlDevice* device = devices_->FindByAddress(test_address); + device->connection_id = connection_id; + device->change_counter = change_counter; + device->volume_control_point_handle = control_point_handle; + std::vector<uint8_t> data_expected({opcode, change_counter}); + + EXPECT_CALL(gatt_queue, + WriteCharacteristic(connection_id, control_point_handle, + data_expected, GATT_WRITE, _, _)); + } + + const std::vector<uint8_t>* arg = nullptr; + GATT_WRITE_OP_CB cb = nullptr; + void* cb_data = nullptr; + devices_->ControlPointOperation(devices, opcode, arg, cb, cb_data); +} + +TEST_F(VolumeControlDevicesTest, test_control_point_operation_args) { + uint8_t opcode = 60; + uint8_t arg_1 = 0x02; + uint8_t arg_2 = 0x05; + std::vector<RawAddress> devices; + + for (int i = 5; i > 0; i--) { + RawAddress test_address = GetTestAddress(i); + devices.push_back(test_address); + uint8_t change_counter = 10 * i; + uint16_t control_point_handle = 0x0020 + i; + uint16_t connection_id = i; + devices_->Add(test_address, true); + VolumeControlDevice* device = devices_->FindByAddress(test_address); + device->connection_id = connection_id; + device->change_counter = change_counter; + device->volume_control_point_handle = control_point_handle; + std::vector<uint8_t> data_expected({opcode, change_counter, arg_1, arg_2}); + + EXPECT_CALL(gatt_queue, + WriteCharacteristic(connection_id, control_point_handle, + data_expected, GATT_WRITE, _, _)); + } + + std::vector<uint8_t> arg({arg_1, arg_2}); + GATT_WRITE_OP_CB cb = nullptr; + void* cb_data = nullptr; + devices_->ControlPointOperation(devices, opcode, &arg, cb, cb_data); +} + +TEST_F(VolumeControlDevicesTest, test_control_point_skip_not_connected) { + RawAddress test_address = GetTestAddress(1); + devices_->Add(test_address, true); + VolumeControlDevice* device = devices_->FindByAddress(test_address); + device->connection_id = GATT_INVALID_CONN_ID; + uint16_t control_point_handle = 0x0020; + device->volume_control_point_handle = control_point_handle; + + EXPECT_CALL(gatt_queue, + WriteCharacteristic(_, control_point_handle, _, _, _, _)) + .Times(0); + + uint8_t opcode = 5; + std::vector<RawAddress> devices = {test_address}; + const std::vector<uint8_t>* arg = nullptr; + GATT_WRITE_OP_CB cb = nullptr; + void* cb_data = nullptr; + devices_->ControlPointOperation(devices, opcode, arg, cb, cb_data); +} + +class VolumeControlDeviceTest : public ::testing::Test { + protected: + void SetUp() override { + device = new VolumeControlDevice(GetTestAddress(1), true); + gatt::SetMockBtaGattInterface(&gatt_interface); + gatt::SetMockBtaGattQueue(&gatt_queue); + bluetooth::manager::SetMockBtmInterface(&btm_interface); + + ON_CALL(gatt_interface, GetCharacteristic(_, _)) + .WillByDefault( + Invoke([&](uint16_t conn_id, + uint16_t handle) -> const gatt::Characteristic* { + for (auto const& service : services) { + for (auto const& characteristic : service.characteristics) { + if (characteristic.value_handle == handle) { + return &characteristic; + } + } + } + + return nullptr; + })); + + ON_CALL(gatt_interface, GetOwningService(_, _)) + .WillByDefault(Invoke( + [&](uint16_t conn_id, uint16_t handle) -> const gatt::Service* { + for (auto const& service : services) { + if (service.handle <= handle && service.end_handle >= handle) { + return &service; + } + } + + return nullptr; + })); + + ON_CALL(gatt_interface, GetServices(_)).WillByDefault(Return(&services)); + } + + void TearDown() override { + bluetooth::manager::SetMockBtmInterface(nullptr); + gatt::SetMockBtaGattQueue(nullptr); + gatt::SetMockBtaGattInterface(nullptr); + delete device; + } + + /* sample database 1xVCS, 2xAICS, 2xVOCS */ + void SetSampleDatabase1(void) { + gatt::DatabaseBuilder builder; + builder.AddService(0x0001, 0x0016, kVolumeControlUuid, true); + builder.AddCharacteristic( + 0x0010, 0x0011, kVolumeControlStateUuid, + GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY); + builder.AddDescriptor(0x0012, + Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); + builder.AddCharacteristic(0x0013, 0x0014, kVolumeControlPointUuid, + GATT_CHAR_PROP_BIT_WRITE); + builder.AddCharacteristic(0x0015, 0x0016, kVolumeFlagsUuid, + GATT_CHAR_PROP_BIT_READ); + builder.AddService(0x00a0, 0x00a3, + Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER), true); + builder.AddCharacteristic(0x00a1, 0x00a2, + Uuid::From16Bit(GATT_UUID_GATT_SRV_CHGD), + GATT_CHAR_PROP_BIT_NOTIFY); + builder.AddDescriptor(0x00a3, + Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); + services = builder.Build().Services(); + ASSERT_EQ(true, device->UpdateHandles()); + } + + /* sample database no VCS */ + void SetSampleDatabase2(void) { + gatt::DatabaseBuilder builder; + builder.AddService(0x0001, 0x0003, Uuid::From16Bit(0x1800), true); + builder.AddCharacteristic(0x0002, 0x0003, Uuid::From16Bit(0x2a00), + GATT_CHAR_PROP_BIT_READ); + services = builder.Build().Services(); + ASSERT_EQ(false, device->UpdateHandles()); + } + + VolumeControlDevice* device = nullptr; + gatt::MockBtaGattInterface gatt_interface; + gatt::MockBtaGattQueue gatt_queue; + bluetooth::manager::MockBtmInterface btm_interface; + std::list<gatt::Service> services; +}; + +TEST_F(VolumeControlDeviceTest, test_service_volume_control_not_found) { + SetSampleDatabase2(); + ASSERT_EQ(false, device->HasHandles()); +} + +TEST_F(VolumeControlDeviceTest, test_service_volume_control_incomplete) { + gatt::DatabaseBuilder builder; + builder.AddService(0x0001, 0x0006, kVolumeControlUuid, true); + builder.AddCharacteristic( + 0x0002, 0x0003, kVolumeControlStateUuid, + GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY); + builder.AddDescriptor(0x0004, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG)); + builder.AddCharacteristic(0x0005, 0x0006, kVolumeControlPointUuid, + GATT_CHAR_PROP_BIT_WRITE); + /* no Volume Control Flags characteristic */ + services = builder.Build().Services(); + ASSERT_EQ(false, device->UpdateHandles()); + ASSERT_EQ(0x0000, device->volume_state_handle); + ASSERT_EQ(0x0000, device->volume_state_ccc_handle); + ASSERT_EQ(0x0000, device->volume_control_point_handle); + ASSERT_EQ(0x0000, device->volume_flags_handle); + ASSERT_EQ(0x0000, device->volume_flags_ccc_handle); + ASSERT_EQ(false, device->HasHandles()); +} + +TEST_F(VolumeControlDeviceTest, test_services_changed) { + SetSampleDatabase1(); + ASSERT_NE(0, device->volume_state_handle); + ASSERT_NE(0, device->volume_control_point_handle); + ASSERT_NE(0, device->volume_flags_handle); + ASSERT_EQ(true, device->HasHandles()); + SetSampleDatabase2(); + ASSERT_EQ(0, device->volume_state_handle); + ASSERT_EQ(0, device->volume_control_point_handle); + ASSERT_EQ(0, device->volume_flags_handle); + ASSERT_EQ(false, device->HasHandles()); +} + +TEST_F(VolumeControlDeviceTest, test_enqueue_initial_requests) { + SetSampleDatabase1(); + + tGATT_IF gatt_if = 0x0001; + std::vector<uint8_t> register_for_notification_data({0x01, 0x00}); + + std::map<uint16_t, uint16_t> expected_to_read_write{ + {0x0011, 0x0012} /* volume control state */}; + + for (auto const& handle_pair : expected_to_read_write) { + EXPECT_CALL(gatt_queue, ReadCharacteristic(_, handle_pair.first, _, _)); + EXPECT_CALL(gatt_queue, WriteDescriptor(_, handle_pair.second, + register_for_notification_data, + GATT_WRITE, _, _)); + EXPECT_CALL(gatt_interface, + RegisterForNotifications(gatt_if, _, handle_pair.first)); + } + + auto chrc_read_cb = [](uint16_t conn_id, tGATT_STATUS status, uint16_t handle, + uint16_t len, uint8_t* value, void* data) {}; + auto cccd_write_cb = [](uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data) {}; + ASSERT_EQ(true, device->EnqueueInitialRequests(gatt_if, chrc_read_cb, + cccd_write_cb)); +}; + +TEST_F(VolumeControlDeviceTest, test_device_ready) { + SetSampleDatabase1(); + + // grab all the handles requested + std::vector<uint16_t> requested_handles; + ON_CALL(gatt_queue, WriteDescriptor(_, _, _, _, _, _)) + .WillByDefault(Invoke( + [&requested_handles]( + 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 { requested_handles.push_back(handle); })); + ON_CALL(gatt_queue, ReadCharacteristic(_, _, _, _)) + .WillByDefault(Invoke( + [&requested_handles](uint16_t conn_id, uint16_t handle, + GATT_READ_OP_CB cb, void* cb_data) -> void { + requested_handles.push_back(handle); + })); + + auto chrc_read_cb = [](uint16_t conn_id, tGATT_STATUS status, uint16_t handle, + uint16_t len, uint8_t* value, void* data) {}; + auto cccd_write_cb = [](uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data) {}; + ASSERT_EQ(true, device->EnqueueInitialRequests(0x0001, chrc_read_cb, + cccd_write_cb)); + ASSERT_NE((size_t)0, requested_handles.size()); + + // indicate non-pending requests + ASSERT_EQ(false, device->device_ready); + device->VerifyReady(0xffff); + + for (uint16_t handle : requested_handles) { + ASSERT_EQ(false, device->device_ready); + device->VerifyReady(handle); + } + + ASSERT_EQ(true, device->device_ready); +} + +TEST_F(VolumeControlDeviceTest, test_enqueue_remaining_requests) { + SetSampleDatabase1(); + + tGATT_IF gatt_if = 0x0001; + std::vector<uint8_t> register_for_notification_data({0x01, 0x00}); + + std::vector<uint16_t> expected_to_read{0x0016 /* volume flags */}; + + std::map<uint16_t, uint16_t> expected_to_write_value_ccc_handle_map{}; + + for (uint16_t handle : expected_to_read) { + EXPECT_CALL(gatt_queue, ReadCharacteristic(_, handle, _, _)); + } + + for (auto const& handle_pair : expected_to_write_value_ccc_handle_map) { + EXPECT_CALL(gatt_queue, WriteDescriptor(_, handle_pair.second, + register_for_notification_data, + GATT_WRITE, _, _)); + EXPECT_CALL(gatt_interface, + RegisterForNotifications(gatt_if, _, handle_pair.first)); + } + + auto chrc_read_cb = [](uint16_t conn_id, tGATT_STATUS status, uint16_t handle, + uint16_t len, uint8_t* value, void* data) {}; + auto cccd_write_cb = [](uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data) {}; + device->EnqueueRemainingRequests(gatt_if, chrc_read_cb, cccd_write_cb); +} + +TEST_F(VolumeControlDeviceTest, test_check_link_encrypted) { + ON_CALL(btm_interface, GetSecurityFlagsByTransport(_, _, _)) + .WillByDefault( + DoAll(SetArgPointee<1>(BTM_SEC_FLAG_ENCRYPTED), Return(true))); + ASSERT_EQ(true, device->IsEncryptionEnabled()); + + ON_CALL(btm_interface, GetSecurityFlagsByTransport(_, _, _)) + .WillByDefault(DoAll(SetArgPointee<1>(0), Return(false))); + ASSERT_NE(true, device->IsEncryptionEnabled()); + + ON_CALL(btm_interface, GetSecurityFlagsByTransport(_, _, _)) + .WillByDefault(DoAll(SetArgPointee<1>(0), Return(true))); + ASSERT_NE(true, device->IsEncryptionEnabled()); +} + +TEST_F(VolumeControlDeviceTest, test_control_point_operation) { + GATT_WRITE_OP_CB write_cb = [](uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data) {}; + SetSampleDatabase1(); + device->change_counter = 0x01; + std::vector<uint8_t> expected_data({0x03, 0x01}); + EXPECT_CALL(gatt_queue, WriteCharacteristic(_, 0x0014, expected_data, + GATT_WRITE, write_cb, nullptr)); + device->ControlPointOperation(0x03, nullptr, write_cb, nullptr); +} + +TEST_F(VolumeControlDeviceTest, test_control_point_operation_arg) { + GATT_WRITE_OP_CB write_cb = [](uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, void* data) {}; + SetSampleDatabase1(); + device->change_counter = 0x55; + std::vector<uint8_t> expected_data({0x01, 0x55, 0x02, 0x03}); + EXPECT_CALL(gatt_queue, WriteCharacteristic(_, 0x0014, expected_data, + GATT_WRITE, write_cb, nullptr)); + std::vector<uint8_t> arg({0x02, 0x03}); + device->ControlPointOperation(0x01, &arg, write_cb, nullptr); +} + +} // namespace internal +} // namespace vc +} // namespace bluetooth diff --git a/system/bta/vc/types.h b/system/bta/vc/types.h new file mode 100644 index 0000000000..808df9ffac --- /dev/null +++ b/system/bta/vc/types.h @@ -0,0 +1,47 @@ +/* + * Copyright 2020 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <queue> +#include <vector> + +#include "raw_address.h" + +namespace bluetooth { +namespace vc { +namespace internal { + +/* clang-format off */ +/* Volume control point opcodes */ +static constexpr uint8_t kControlPointOpcodeVolumeDown = 0x00; +static constexpr uint8_t kControlPointOpcodeVolumeUp = 0x01; +static constexpr uint8_t kControlPointOpcodeUnmuteVolumeDown = 0x02; +static constexpr uint8_t kControlPointOpcodeUnmuteVolumeUp = 0x03; +static constexpr uint8_t kControlPointOpcodeSetAbsoluteVolume = 0x04; +static constexpr uint8_t kControlPointOpcodeUnmute = 0x05; +static constexpr uint8_t kControlPointOpcodeMute = 0x06; + +static const Uuid kVolumeControlUuid = Uuid::From16Bit(0x1844); +static const Uuid kVolumeControlStateUuid = Uuid::From16Bit(0x2B7D); +static const Uuid kVolumeControlPointUuid = Uuid::From16Bit(0x2B7E); +static const Uuid kVolumeFlagsUuid = Uuid::From16Bit(0x2B7F); +/* clang-format on */ + +} // namespace internal +} // namespace vc +} // namespace bluetooth diff --git a/system/bta/vc/vc.cc b/system/bta/vc/vc.cc new file mode 100644 index 0000000000..97b8266e90 --- /dev/null +++ b/system/bta/vc/vc.cc @@ -0,0 +1,573 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <base/bind.h> +#include <base/bind_helpers.h> +#include <base/logging.h> +#include <base/strings/string_number_conversions.h> +#include <hardware/bt_vc.h> + +#include <string> +#include <vector> + +#include "bta_gatt_api.h" +#include "bta_gatt_queue.h" +#include "bta_vc_api.h" +#include "btif_storage.h" +#include "devices.h" + +using base::Closure; +using bluetooth::Uuid; +using bluetooth::vc::ConnectionState; +using namespace bluetooth::vc::internal; + +namespace { +class VolumeControlImpl; +VolumeControlImpl* instance; + +/** + * Overview: + * + * This is Volume Control Implementation class which realize Volume Control + * Profile (VCP) + * + * Each connected peer device supporting Volume Control Service (VCS) is on the + * list of devices (volume_control_devices_). + * + * Once all the mandatory characteristis for all the services are discovered, + * Fluoride calls ON_CONNECTED callback. + * + * It is assumed that whenever application changes general audio options in this + * profile e.g. Volume up/down, mute/unmute etc, profile configures all the + * devices which are active Le Audio devices. + * + * + */ +class VolumeControlImpl : public VolumeControl { + public: + ~VolumeControlImpl() override = default; + + VolumeControlImpl(bluetooth::vc::VolumeControlCallbacks* callbacks) + : gatt_if_(0), callbacks_(callbacks) { + BTA_GATTC_AppRegister( + gattc_callback_static, + base::Bind([](uint8_t client_id, uint8_t status) { + if (status != GATT_SUCCESS) { + LOG(ERROR) << "Can't start Volume Control profile - no gatt " + "clients left!"; + return; + } + instance->gatt_if_ = client_id; + }), + true); + } + + void Connect(const RawAddress& address) override { + LOG(INFO) << __func__ << " " << address; + + auto device = volume_control_devices_.FindByAddress(address); + if (!device) { + volume_control_devices_.Add(address, true); + } else { + device->connecting_actively = true; + } + + BTA_GATTC_Open(gatt_if_, address, true, false); + } + + void AddFromStorage(const RawAddress& address, bool auto_connect) { + LOG(INFO) << __func__ << " " << address + << ", auto_connect=" << auto_connect; + + if (auto_connect) { + volume_control_devices_.Add(address, false); + + /* Add device into BG connection to accept remote initiated connection */ + BTA_GATTC_Open(gatt_if_, address, false, false); + } + } + + void OnGattConnected(tGATT_STATUS status, uint16_t connection_id, + tGATT_IF /*client_if*/, RawAddress address, + tBT_TRANSPORT /*transport*/, uint16_t /*mtu*/) { + LOG(INFO) << __func__ << ": address=" << address + << ", connection_id=" << connection_id; + + VolumeControlDevice* device = + volume_control_devices_.FindByAddress(address); + if (!device) { + LOG(ERROR) << __func__ << "Skipping unknown device, address=" << address; + return; + } + + if (status != GATT_SUCCESS) { + LOG(INFO) << "Failed to connect to Volume Control device"; + device_cleanup_helper(device, device->connecting_actively); + return; + } + + device->connection_id = connection_id; + + if (device->IsEncryptionEnabled()) { + OnEncryptionComplete(address, BTM_SUCCESS); + return; + } + + if (!device->EnableEncryption(enc_callback_static)) { + device_cleanup_helper(device, device->connecting_actively); + } + } + + void OnEncryptionComplete(const RawAddress& address, uint8_t success) { + VolumeControlDevice* device = + volume_control_devices_.FindByAddress(address); + if (!device) { + LOG(ERROR) << __func__ << "Skipping unknown device" << address; + return; + } + + if (success != BTM_SUCCESS) { + LOG(ERROR) << "encryption failed " + << "status: " << int{success}; + // If the encryption failed, do not remove the device. + // Disconnect only, since the Android will try to re-enable encryption + // after disconnection + device->Disconnect(gatt_if_); + if (device->connecting_actively) + callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, + device->address); + return; + } + + LOG(INFO) << __func__ << " " << address << "status: " << success; + + if (device->HasHandles()) { + device->EnqueueInitialRequests(gatt_if_, chrc_read_callback_static, + OnGattWriteCccStatic); + + } else { + device->first_connection = true; + BTA_GATTC_ServiceSearchRequest(device->connection_id, + &kVolumeControlUuid); + } + } + + void OnServiceChangeEvent(const RawAddress& address) { + VolumeControlDevice* device = + volume_control_devices_.FindByAddress(address); + if (!device) { + LOG(ERROR) << __func__ << "Skipping unknown device" << address; + return; + } + LOG(INFO) << __func__ << ": address=" << address; + device->first_connection = true; + device->service_changed_rcvd = true; + BtaGattQueue::Clean(device->connection_id); + } + + void OnServiceDiscDoneEvent(const RawAddress& address) { + VolumeControlDevice* device = + volume_control_devices_.FindByAddress(address); + if (!device) { + LOG(ERROR) << __func__ << "Skipping unknown device" << address; + return; + } + + if (device->service_changed_rcvd) + BTA_GATTC_ServiceSearchRequest(device->connection_id, + &kVolumeControlUuid); + } + + void OnServiceSearchComplete(uint16_t connection_id, tGATT_STATUS status) { + VolumeControlDevice* device = + volume_control_devices_.FindByConnId(connection_id); + if (!device) { + LOG(ERROR) << __func__ << "Skipping unknown device, connection_id=" + << loghex(connection_id); + return; + } + + /* Known device, nothing to do */ + if (!device->first_connection) return; + + if (status != GATT_SUCCESS) { + /* close connection and report service discovery complete with error */ + LOG(ERROR) << "Service discovery failed"; + device_cleanup_helper(device, device->first_connection); + return; + } + + bool success = device->UpdateHandles(); + if (!success) { + LOG(ERROR) << "Incomplete service database"; + device_cleanup_helper(device, true); + return; + } + + device->EnqueueInitialRequests(gatt_if_, chrc_read_callback_static, + OnGattWriteCccStatic); + } + + void OnCharacteristicValueChanged(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* /* data */) { + VolumeControlDevice* device = volume_control_devices_.FindByConnId(conn_id); + if (!device) { + LOG(INFO) << __func__ << ": unknown conn_id=" << loghex(conn_id); + return; + } + + if (status != GATT_SUCCESS) { + LOG(INFO) << __func__ << ": status=" << static_cast<int>(status); + return; + } + + if (handle == device->volume_state_handle) { + OnVolumeControlStateChanged(device, len, value); + verify_device_ready(device, handle); + return; + } + if (handle == device->volume_flags_handle) { + OnVolumeControlFlagsChanged(device, len, value); + verify_device_ready(device, handle); + return; + } + + LOG(ERROR) << __func__ << ": unknown handle=" << loghex(handle); + } + + void OnNotificationEvent(uint16_t conn_id, uint16_t handle, uint16_t len, + uint8_t* value) { + LOG(INFO) << __func__ << ": handle=" << loghex(handle); + OnCharacteristicValueChanged(conn_id, GATT_SUCCESS, handle, len, value, + nullptr); + } + + void VolumeControlReadCommon(uint16_t conn_id, uint16_t handle) { + BtaGattQueue::ReadCharacteristic(conn_id, handle, chrc_read_callback_static, + nullptr); + } + + void OnVolumeControlStateChanged(VolumeControlDevice* device, uint16_t len, + uint8_t* value) { + if (len != 3) { + LOG(INFO) << __func__ << ": malformed len=" << loghex(len); + return; + } + + uint8_t* pp = value; + STREAM_TO_UINT8(device->volume, pp); + STREAM_TO_UINT8(device->mute, pp); + STREAM_TO_UINT8(device->change_counter, pp); + + LOG(INFO) << __func__ << " " << base::HexEncode(value, len); + LOG(INFO) << __func__ << "volume " << loghex(device->volume) << "mute" + << loghex(device->mute) << "change_counter" + << loghex(device->change_counter); + + if (!device->device_ready) return; + + callbacks_->OnVolumeStateChanged(device->address, device->volume, + device->mute); + } + + void OnVolumeControlFlagsChanged(VolumeControlDevice* device, uint16_t len, + uint8_t* value) { + device->flags = *value; + + LOG(INFO) << __func__ << " " << base::HexEncode(value, len); + LOG(INFO) << __func__ << "flags " << loghex(device->flags); + } + + void OnGattWriteCcc(uint16_t connection_id, tGATT_STATUS status, + uint16_t handle, void* /*data*/) { + VolumeControlDevice* device = + volume_control_devices_.FindByConnId(connection_id); + if (!device) { + LOG(INFO) << __func__ + << "unknown connection_id=" << loghex(connection_id); + BtaGattQueue::Clean(connection_id); + return; + } + + if (status != GATT_SUCCESS) { + LOG(ERROR) << __func__ + << "Failed to register for notification: " << loghex(handle) + << " status: " << status; + device_cleanup_helper(device, true); + return; + } + + LOG(INFO) << __func__ + << "Successfully register for indications: " << loghex(handle); + + verify_device_ready(device, handle); + } + + static void OnGattWriteCccStatic(uint16_t connection_id, tGATT_STATUS status, + uint16_t handle, void* data) { + if (!instance) { + LOG(ERROR) << __func__ << "No instance=" << handle; + return; + } + + instance->OnGattWriteCcc(connection_id, status, handle, data); + } + + void Dump(int fd) { volume_control_devices_.DebugDump(fd); } + + void Disconnect(const RawAddress& address) override { + VolumeControlDevice* device = + volume_control_devices_.FindByAddress(address); + if (!device) { + LOG(INFO) << "Device not connected to profile" << address; + return; + } + + LOG(INFO) << __func__ << ": " << address; + LOG(INFO) << "GAP_EVT_CONN_CLOSED: " << device->address; + device_cleanup_helper(device, true); + } + + void OnGattDisconnected(uint16_t connection_id, tGATT_IF /*client_if*/, + RawAddress remote_bda, tBTA_GATT_REASON /*reason*/) { + VolumeControlDevice* device = + volume_control_devices_.FindByConnId(connection_id); + if (!device) { + LOG(ERROR) << __func__ + << " Skipping unknown device disconnect, connection_id=" + << loghex(connection_id); + return; + } + + // If we get here, it means, device has not been exlicitly disconnected. + bool device_ready = device->device_ready; + + device_cleanup_helper(device, device->connecting_actively); + + if (device_ready) { + volume_control_devices_.Add(remote_bda, true); + + /* Add device into BG connection to accept remote initiated connection */ + BTA_GATTC_Open(gatt_if_, remote_bda, false, false); + } + } + + void OnWriteControlResponse(uint16_t connection_id, tGATT_STATUS status, + uint16_t handle, void* /*data*/) { + VolumeControlDevice* device = + volume_control_devices_.FindByConnId(connection_id); + if (!device) { + LOG(ERROR) << __func__ + << "Skipping unknown device disconnect, connection_id=" + << loghex(connection_id); + return; + } + + LOG(INFO) << "Write response handle: " << loghex(handle) + << " status: " << loghex((int)(status)); + } + + void SetVolume(std::variant<RawAddress, int> addr_or_group_id, + uint8_t volume) override { + LOG(INFO) << __func__ << "vol: " << +volume; + + if (std::holds_alternative<RawAddress>(addr_or_group_id)) { + std::vector<RawAddress> devices = { + std::get<RawAddress>(addr_or_group_id)}; + std::vector<uint8_t> arg({volume}); + devices_control_point_helper(devices, + kControlPointOpcodeSetAbsoluteVolume, &arg); + return; + } + + /* TODO implement handling group request */ + } + + void CleanUp() { + LOG(INFO) << __func__; + volume_control_devices_.Disconnect(gatt_if_); + volume_control_devices_.Clear(); + BTA_GATTC_AppDeregister(gatt_if_); + } + + private: + tGATT_IF gatt_if_; + bluetooth::vc::VolumeControlCallbacks* callbacks_; + VolumeControlDevices volume_control_devices_; + + void verify_device_ready(VolumeControlDevice* device, uint16_t handle) { + if (device->device_ready) return; + + // VerifyReady sets the device_ready flag if all remaining GATT operations + // are completed + if (device->VerifyReady(handle)) { + LOG(INFO) << __func__ << "Outstanding reads completed "; + + callbacks_->OnConnectionState(ConnectionState::CONNECTED, + device->address); + + device->connecting_actively = true; + + device->first_connection = false; + + // once profile connected we can notify current states + callbacks_->OnVolumeStateChanged(device->address, device->volume, + device->mute); + + device->EnqueueRemainingRequests(gatt_if_, chrc_read_callback_static, + OnGattWriteCccStatic); + } + } + + void device_cleanup_helper(VolumeControlDevice* device, bool notify) { + device->Disconnect(gatt_if_); + if (notify) + callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, + device->address); + volume_control_devices_.Remove(device->address); + } + + void devices_control_point_helper(std::vector<RawAddress>& devices, + uint8_t opcode, + const std::vector<uint8_t>* arg) { + volume_control_devices_.ControlPointOperation( + devices, opcode, arg, + [](uint16_t connection_id, tGATT_STATUS status, uint16_t handle, + void* data) { + if (instance) + instance->OnWriteControlResponse(connection_id, status, handle, + data); + }, + nullptr); + } + + void gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { + LOG(INFO) << __func__ << " event = " << static_cast<int>(event); + + if (p_data == nullptr) return; + + switch (event) { + case BTA_GATTC_OPEN_EVT: { + tBTA_GATTC_OPEN& o = p_data->open; + OnGattConnected(o.status, o.conn_id, o.client_if, o.remote_bda, + o.transport, o.mtu); + + } break; + + case BTA_GATTC_CLOSE_EVT: { + tBTA_GATTC_CLOSE& c = p_data->close; + OnGattDisconnected(c.conn_id, c.client_if, c.remote_bda, c.reason); + } break; + + case BTA_GATTC_SEARCH_CMPL_EVT: + OnServiceSearchComplete(p_data->search_cmpl.conn_id, + p_data->search_cmpl.status); + break; + + case BTA_GATTC_NOTIF_EVT: { + tBTA_GATTC_NOTIFY& n = p_data->notify; + if (!n.is_notify || n.len > GATT_MAX_ATTR_LEN) { + LOG(ERROR) << __func__ << ": rejected BTA_GATTC_NOTIF_EVT. is_notify=" + << n.is_notify << ", len=" << static_cast<int>(n.len); + break; + } + OnNotificationEvent(n.conn_id, n.handle, n.len, n.value); + } break; + + case BTA_GATTC_ENC_CMPL_CB_EVT: + OnEncryptionComplete(p_data->enc_cmpl.remote_bda, true); + break; + + case BTA_GATTC_SRVC_CHG_EVT: + OnServiceChangeEvent(p_data->remote_bda); + break; + + case BTA_GATTC_SRVC_DISC_DONE_EVT: + OnServiceDiscDoneEvent(p_data->remote_bda); + break; + + default: + break; + } + } + + static void gattc_callback_static(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) { + if (instance) instance->gattc_callback(event, p_data); + } + + static void enc_callback_static(const RawAddress* address, tBT_TRANSPORT, + void*, tBTM_STATUS status) { + if (instance) instance->OnEncryptionComplete(*address, status); + } + + static void chrc_read_callback_static(uint16_t conn_id, tGATT_STATUS status, + uint16_t handle, uint16_t len, + uint8_t* value, void* data) { + if (instance) + instance->OnCharacteristicValueChanged(conn_id, status, handle, len, + value, data); + } +}; +} // namespace + +void VolumeControl::Initialize( + bluetooth::vc::VolumeControlCallbacks* callbacks) { + if (instance) { + LOG(ERROR) << "Already initialized!"; + return; + } + + instance = new VolumeControlImpl(callbacks); +} + +bool VolumeControl::IsVolumeControlRunning() { return instance; } + +VolumeControl* VolumeControl::Get(void) { + CHECK(instance); + return instance; +}; + +void VolumeControl::AddFromStorage(const RawAddress& address, + bool auto_connect) { + if (!instance) { + LOG(ERROR) << "Not initialized yet"; + return; + } + + instance->AddFromStorage(address, auto_connect); +}; + +void VolumeControl::CleanUp() { + if (!instance) { + LOG(ERROR) << "not initialized!"; + return; + } + + VolumeControlImpl* ptr = instance; + instance = nullptr; + + ptr->CleanUp(); + + delete ptr; +}; + +void VolumeControl::DebugDump(int fd) { + dprintf(fd, "Volume Control Manager:\n"); + if (instance) instance->Dump(fd); + dprintf(fd, "\n"); +} diff --git a/system/bta/vc/vc_test.cc b/system/bta/vc/vc_test.cc new file mode 100644 index 0000000000..30eef87fa1 --- /dev/null +++ b/system/bta/vc/vc_test.cc @@ -0,0 +1,643 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <base/bind.h> +#include <base/bind_helpers.h> +#include <gmock/gmock.h> +#include <gtest/gtest.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 "types.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) { + CHECK_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() override = default; + + MOCK_METHOD((void), OnConnectionState, + (ConnectionState state, const RawAddress& address), (override)); + MOCK_METHOD((void), OnVolumeStateChanged, + (const RawAddress& address, uint8_t volume, bool mute), + (override)); + MOCK_METHOD((void), OnGroupVolumeStateChanged, + (int group_id, uint8_t volume, bool mute), (override)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockVolumeControlCallbacks); +}; + +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) { + /* TODO Place holder */ + } + /* 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) { + /* TODO Place holder for VOCS */ + } + } + /* 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; + + default: + ASSERT_TRUE(false); + return; + } + + cb(conn_id, GATT_SUCCESS, handle, value.size(), value.data(), + cb_data); + })); + } + + protected: + void SetUp(void) override { + bluetooth::manager::SetMockBtmInterface(&btm_interface); + gatt::SetMockBtaGattInterface(&gatt_interface); + gatt::SetMockBtaGattQueue(&gatt_queue); + callbacks.reset(new MockVolumeControlCallbacks()); + + // 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, 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()); + 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, GetSecurityFlagsByTransport(address, NotNull(), _)) + .WillByDefault( + DoAll(SetArgPointee<1>(BTM_SEC_FLAG_ENCRYPTED), Return(true))); + + EXPECT_CALL(gatt_interface, Open(gatt_if, address, true, _)); + VolumeControl::Get()->Connect(address); + } + + void TestDisconnect(const RawAddress& address, uint16_t conn_id) { + if (conn_id) { + EXPECT_CALL(gatt_interface, Close(conn_id)); + } else { + EXPECT_CALL(gatt_interface, CancelOpen(gatt_if, address, _)); + } + VolumeControl::Get()->Disconnect(address); + } + + void TestAddFromStorage(const RawAddress& address, bool auto_connect) { + // by default indicate link as encrypted + ON_CALL(btm_interface, GetSecurityFlagsByTransport(address, NotNull(), _)) + .WillByDefault( + DoAll(SetArgPointee<1>(BTM_SEC_FLAG_ENCRYPTED), Return(true))); + + if (auto_connect) { + EXPECT_CALL(gatt_interface, Open(gatt_if, address, false, _)); + } else { + EXPECT_CALL(gatt_interface, Open(gatt_if, address, _, _)).Times(0); + } + VolumeControl::Get()->AddFromStorage(address, auto_connect); + } + + void TestSubscribeNotifications(const RawAddress& address, uint16_t conn_id, + 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); + + 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 = { + .status = GATT_SUCCESS, + .conn_id = conn_id, + .client_if = gatt_if, + .remote_bda = address, + .reason = HCI_ERR_PEER_USER, + }; + + gatt_callback(BTA_GATTC_CLOSE_EVT, (tBTA_GATTC*)&event_data); + } + + void GetSearchCompleteEvent(uint16_t conn_id) { + tBTA_GATTC_SEARCH_CMPL event_data = { + .status = GATT_SUCCESS, + .conn_id = conn_id, + }; + + gatt_callback(BTA_GATTC_SEARCH_CMPL_EVT, (tBTA_GATTC*)&event_data); + } + + void SetEncryptionResult(const RawAddress& address, bool success) { + ON_CALL(btm_interface, GetSecurityFlagsByTransport(address, NotNull(), _)) + .WillByDefault(DoAll(SetArgPointee<1>(0), Return(true))); + EXPECT_CALL(btm_interface, + SetEncryption(address, _, NotNull(), _, BTM_BLE_SEC_ENCRYPT)) + .WillOnce(Invoke( + [&success](const RawAddress& bd_addr, tBT_TRANSPORT transport, + tBTM_SEC_CALLBACK* p_callback, void* p_ref_data, + tBTM_BLE_SEC_ACT sec_act) -> tBTM_STATUS { + p_callback(&bd_addr, transport, p_ref_data, + success ? BTM_SUCCESS : BTM_FAILED_ON_SECURITY); + return BTM_SUCCESS; + })); + } + + void 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 SetSampleDatabase(uint16_t conn_id) { + set_sample_database(conn_id, true, false, true, false, true, false); + } + + std::unique_ptr<MockVolumeControlCallbacks> callbacks; + bluetooth::manager::MockBtmInterface btm_interface; + gatt::MockBtaGattInterface gatt_interface; + 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; +}; + +TEST_F(VolumeControlTest, test_get_uninitialized) { + ASSERT_DEATH(VolumeControl::Get(), ""); +} + +TEST_F(VolumeControlTest, test_initialize) { + VolumeControl::Initialize(callbacks.get()); + ASSERT_TRUE(VolumeControl::IsVolumeControlRunning()); + VolumeControl::CleanUp(); +} + +TEST_F(VolumeControlTest, test_initialize_twice) { + VolumeControl::Initialize(callbacks.get()); + VolumeControl* volume_control_p = VolumeControl::Get(); + VolumeControl::Initialize(callbacks.get()); + ASSERT_EQ(volume_control_p, VolumeControl::Get()); + VolumeControl::CleanUp(); +} + +TEST_F(VolumeControlTest, test_cleanup_initialized) { + VolumeControl::Initialize(callbacks.get()); + 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_add_from_storage) { + TestAppRegister(); + TestAddFromStorage(GetTestAddress(0), true); + TestAddFromStorage(GetTestAddress(1), false); + 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, true); + 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_reconnect_after_encryption_failed) { + const RawAddress test_address = GetTestAddress(0); + TestAppRegister(); + TestAddFromStorage(test_address, true); + 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_discovery_vcs_found) { + const RawAddress test_address = GetTestAddress(0); + SetSampleDatabaseVCS(1); + TestAppRegister(); + TestConnect(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<uint16_t, uint16_t> handles({{0x0021, 0x0022}}); + 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, _, _)); + 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); +} + +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<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, (tBTA_GATTC*)&event_data); + } +}; + +TEST_F(VolumeControlCallbackTest, test_volume_state_changed) { + std::vector<uint8_t> value({0x03, 0x01, 0x02}); + EXPECT_CALL(*callbacks, OnVolumeStateChanged(test_address, 0x03, true)); + GetNotificationEvent(0x0021, value); +} + +TEST_F(VolumeControlCallbackTest, test_volume_state_changed_malformed) { + EXPECT_CALL(*callbacks, OnVolumeStateChanged(test_address, _, _)).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); +} + +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); + } + + void TearDown(void) override { + TestAppUnregister(); + VolumeControlTest::TearDown(); + } +}; + +TEST_F(VolumeControlValueSetTest, test_set_volume) { + std::vector<uint8_t> expected_data({0x04, 0x00, 0x10}); + EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, expected_data, + GATT_WRITE, _, _)); + VolumeControl::Get()->SetVolume(test_address, 0x10); +} +} // namespace +} // namespace internal +} // namespace vc +} // namespace bluetooth diff --git a/system/btif/Android.bp b/system/btif/Android.bp index e7a3986c3d..ab557277f7 100755 --- a/system/btif/Android.bp +++ b/system/btif/Android.bp @@ -76,6 +76,7 @@ cc_library_static { "src/btif_gatt_server.cc", "src/btif_gatt_test.cc", "src/btif_gatt_util.cc", + "src/btif_vc.cc", "src/btif_hearing_aid.cc", "src/btif_hf.cc", "src/btif_hf_client.cc", diff --git a/system/btif/BUILD.gn b/system/btif/BUILD.gn index 7c30b224be..df326e5d17 100644 --- a/system/btif/BUILD.gn +++ b/system/btif/BUILD.gn @@ -64,6 +64,7 @@ static_library("btif") { "src/btif_gatt_test.cc", "src/btif_gatt_util.cc", "src/btif_hd.cc", + "src/btif_vc.cc", "src/btif_hearing_aid.cc", "src/btif_hf.cc", "src/btif_hf_client.cc", diff --git a/system/btif/src/bluetooth.cc b/system/btif/src/bluetooth.cc index 92029cb41d..fc6ea3f48f 100644 --- a/system/btif/src/bluetooth.cc +++ b/system/btif/src/bluetooth.cc @@ -40,6 +40,7 @@ #include <hardware/bt_rc.h> #include <hardware/bt_sdp.h> #include <hardware/bt_sock.h> +#include <hardware/bt_vc.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -84,6 +85,7 @@ using bluetooth::hearing_aid::HearingAidInterface; using bluetooth::le_audio::LeAudioClientInterface; +using bluetooth::vc::VolumeControlInterface; /******************************************************************************* * Static variables @@ -127,6 +129,8 @@ extern const btsdp_interface_t* btif_sdp_get_interface(); extern HearingAidInterface* btif_hearing_aid_get_interface(); /* LeAudio testi client */ extern LeAudioClientInterface* btif_le_audio_get_interface(); +/* Volume Control client */ +extern VolumeControlInterface* btif_volume_control_get_interface(); /******************************************************************************* * Functions @@ -454,6 +458,9 @@ static const void* get_profile_interface(const char* profile_id) { if (is_profile(profile_id, BT_PROFILE_LE_AUDIO_ID)) return btif_le_audio_get_interface(); + if (is_profile(profile_id, BT_PROFILE_VC_ID)) + return btif_volume_control_get_interface(); + return NULL; } diff --git a/system/btif/src/btif_dm.cc b/system/btif/src/btif_dm.cc index 365aeffb6a..8d093d744f 100644 --- a/system/btif/src/btif_dm.cc +++ b/system/btif/src/btif_dm.cc @@ -81,6 +81,7 @@ using bluetooth::Uuid; *****************************************************************************/ const Uuid UUID_HEARING_AID = Uuid::FromString("FDF0"); +const Uuid UUID_VC = Uuid::FromString("1844"); #define COD_MASK 0x07FF @@ -1239,7 +1240,8 @@ static void btif_dm_search_devices_evt(tBTA_DM_SEARCH_EVT event, /* Returns true if |uuid| should be passed as device property */ static bool btif_is_interesting_le_service(bluetooth::Uuid uuid) { - return uuid.As16Bit() == UUID_SERVCLASS_LE_HID || uuid == UUID_HEARING_AID; + return (uuid.As16Bit() == UUID_SERVCLASS_LE_HID || uuid == UUID_HEARING_AID || + uuid == UUID_VC); } /******************************************************************************* diff --git a/system/btif/src/btif_vc.cc b/system/btif/src/btif_vc.cc new file mode 100644 index 0000000000..bf51eb5913 --- /dev/null +++ b/system/btif/src/btif_vc.cc @@ -0,0 +1,125 @@ +/* + * 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. + */ + +/* Volume Control Interface */ + +#include <hardware/bluetooth.h> +#include <hardware/bt_vc.h> + +#include <base/bind.h> +#include <base/location.h> +#include <base/logging.h> + +#include "bta_vc_api.h" +#include "btif_common.h" +#include "stack/include/btu.h" + +using base::Bind; +using base::Unretained; +using bluetooth::vc::ConnectionState; +using bluetooth::vc::VolumeControlCallbacks; +using bluetooth::vc::VolumeControlInterface; + +namespace { +std::unique_ptr<VolumeControlInterface> vc_instance; + +class VolumeControlInterfaceImpl : public VolumeControlInterface, + public VolumeControlCallbacks { + ~VolumeControlInterfaceImpl() override = default; + + void Init(VolumeControlCallbacks* callbacks) override { + DVLOG(2) << __func__; + this->callbacks_ = callbacks; + do_in_main_thread(FROM_HERE, Bind(&VolumeControl::Initialize, this)); + } + + void OnConnectionState(ConnectionState state, + const RawAddress& address) override { + DVLOG(2) << __func__ << " address: " << address; + do_in_jni_thread(FROM_HERE, Bind(&VolumeControlCallbacks::OnConnectionState, + Unretained(callbacks_), state, address)); + } + + void OnVolumeStateChanged(const RawAddress& address, uint8_t volume, + bool mute) override { + DVLOG(2) << __func__ << " address: " << address << "volume: " << volume + << "mute: " << mute; + do_in_jni_thread(FROM_HERE, + Bind(&VolumeControlCallbacks::OnVolumeStateChanged, + Unretained(callbacks_), address, volume, mute)); + } + + void OnGroupVolumeStateChanged(int group_id, uint8_t volume, + bool mute) override { + DVLOG(2) << __func__ << "group_id: " << group_id << "volume: " << volume + << "mute: " << mute; + do_in_jni_thread(FROM_HERE, + Bind(&VolumeControlCallbacks::OnGroupVolumeStateChanged, + Unretained(callbacks_), group_id, volume, mute)); + } + + void Connect(const RawAddress& address) override { + DVLOG(2) << __func__ << " address: " << address; + do_in_main_thread(FROM_HERE, + Bind(&VolumeControl::Connect, + Unretained(VolumeControl::Get()), address)); + } + + void Disconnect(const RawAddress& address) override { + DVLOG(2) << __func__ << " address: " << address; + do_in_main_thread(FROM_HERE, + Bind(&VolumeControl::Disconnect, + Unretained(VolumeControl::Get()), address)); + } + + void SetVolume(std::variant<RawAddress, int> addr_or_group_id, + uint8_t volume) override { + DVLOG(2) << __func__ << " volume: " << volume; + do_in_main_thread(FROM_HERE, Bind(&VolumeControl::SetVolume, + Unretained(VolumeControl::Get()), + std::move(addr_or_group_id), volume)); + } + + void RemoveDevice(const RawAddress& address) override { + DVLOG(2) << __func__ << " address: " << address; + + /* RemoveDevice can be called on devices that don't have HA enabled */ + if (VolumeControl::IsVolumeControlRunning()) { + do_in_main_thread(FROM_HERE, + Bind(&VolumeControl::Disconnect, + Unretained(VolumeControl::Get()), address)); + } + + /* Placeholder: Remove things from storage here */ + } + + void Cleanup(void) override { + DVLOG(2) << __func__; + do_in_main_thread(FROM_HERE, Bind(&VolumeControl::CleanUp)); + } + + private: + VolumeControlCallbacks* callbacks_; +}; + +} /* namespace */ + +VolumeControlInterface* btif_volume_control_get_interface(void) { + if (!vc_instance) vc_instance.reset(new VolumeControlInterfaceImpl()); + + return vc_instance.get(); +} diff --git a/system/include/hardware/bluetooth.h b/system/include/hardware/bluetooth.h index cd7259529b..e0b9b08dd5 100644 --- a/system/include/hardware/bluetooth.h +++ b/system/include/hardware/bluetooth.h @@ -51,6 +51,7 @@ #define BT_PROFILE_LE_AUDIO_ID "le_audio" #define BT_KEYSTORE_ID "bluetooth_keystore" #define BT_ACTIVITY_ATTRIBUTION_ID "activity_attribution" +#define BT_PROFILE_VC_ID "volume_control" /** Bluetooth Device Name */ typedef struct { uint8_t name[249]; } __attribute__((packed)) bt_bdname_t; diff --git a/system/include/hardware/bt_vc.h b/system/include/hardware/bt_vc.h new file mode 100644 index 0000000000..f1a63d3dca --- /dev/null +++ b/system/include/hardware/bt_vc.h @@ -0,0 +1,76 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <hardware/bluetooth.h> + +#include <variant> + +namespace bluetooth { +namespace vc { + +enum class ConnectionState { + DISCONNECTED = 0, + CONNECTING, + CONNECTED, + DISCONNECTING +}; + +class VolumeControlCallbacks { + public: + virtual ~VolumeControlCallbacks() = default; + + /** Callback for profile connection state change */ + virtual void OnConnectionState(ConnectionState state, + const RawAddress& address) = 0; + + /* Callback for the volume change changed on the device */ + virtual void OnVolumeStateChanged(const RawAddress& address, uint8_t volume, + bool mute) = 0; + + /* Callback for the volume change changed on the group*/ + virtual void OnGroupVolumeStateChanged(int group_id, uint8_t volume, + bool mute) = 0; +}; + +class VolumeControlInterface { + public: + virtual ~VolumeControlInterface() = default; + + /** Register the Volume Control callbacks */ + virtual void Init(VolumeControlCallbacks* callbacks) = 0; + + /** Closes the interface */ + virtual void Cleanup(void) = 0; + + /** Connect to Volume Control */ + virtual void Connect(const RawAddress& address) = 0; + + /** Disconnect from Volume Control */ + virtual void Disconnect(const RawAddress& address) = 0; + + /** Called when Volume control devices is unbonded */ + virtual void RemoveDevice(const RawAddress& address) = 0; + + /** Set the volume */ + virtual void SetVolume(std::variant<RawAddress, int> addr_or_group_id, + uint8_t volume) = 0; +}; + +} /* namespace vc */ +} /* namespace bluetooth */ |