/* * 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 #include #include #include #include #include #include #include #include #include #include #include "bta/include/bta_vc_api.h" #include "btif/include/btif_common.h" #include "btif/include/btif_profile_storage.h" #include "stack/include/main_thread.h" #include "types/raw_address.h" // TODO(b/369381361) Enfore -Wmissing-prototypes #pragma GCC diagnostic ignored "-Wmissing-prototypes" using base::Bind; using base::Unretained; using bluetooth::aics::GainMode; using bluetooth::aics::Mute; using bluetooth::vc::ConnectionState; using bluetooth::vc::VolumeControlCallbacks; using bluetooth::vc::VolumeControlInterface; namespace { std::unique_ptr vc_instance; std::atomic_bool initialized = false; class VolumeControlInterfaceImpl : public VolumeControlInterface, public VolumeControlCallbacks { ~VolumeControlInterfaceImpl() override = default; void Init(VolumeControlCallbacks* callbacks) override { this->callbacks_ = callbacks; do_in_main_thread( Bind(&VolumeControl::Initialize, this, jni_thread_wrapper(Bind(&btif_storage_load_bonded_volume_control_devices)))); /* It might be not yet initialized, but setting this flag here is safe, * because other calls will check this and the native instance */ initialized = true; } void OnConnectionState(ConnectionState state, const RawAddress& address) override { do_in_jni_thread(Bind(&VolumeControlCallbacks::OnConnectionState, Unretained(callbacks_), state, address)); } void OnVolumeStateChanged(const RawAddress& address, uint8_t volume, bool mute, uint8_t flags, bool isAutonomous) override { do_in_jni_thread(Bind(&VolumeControlCallbacks::OnVolumeStateChanged, Unretained(callbacks_), address, volume, mute, flags, isAutonomous)); } void OnGroupVolumeStateChanged(int group_id, uint8_t volume, bool mute, bool isAutonomous) override { do_in_jni_thread(Bind(&VolumeControlCallbacks::OnGroupVolumeStateChanged, Unretained(callbacks_), group_id, volume, mute, isAutonomous)); } void OnDeviceAvailable(const RawAddress& address, uint8_t num_offset, uint8_t num_inputs) override { do_in_jni_thread(Bind(&VolumeControlCallbacks::OnDeviceAvailable, Unretained(callbacks_), address, num_offset, num_inputs)); } /* Callbacks for Volume Offset Control Service (VOCS) - Extended Audio Outputs */ void OnExtAudioOutVolumeOffsetChanged(const RawAddress& address, uint8_t ext_output_id, int16_t offset) override { do_in_jni_thread(Bind(&VolumeControlCallbacks::OnExtAudioOutVolumeOffsetChanged, Unretained(callbacks_), address, ext_output_id, offset)); } void OnExtAudioOutLocationChanged(const RawAddress& address, uint8_t ext_output_id, uint32_t location) override { do_in_jni_thread(Bind(&VolumeControlCallbacks::OnExtAudioOutLocationChanged, Unretained(callbacks_), address, ext_output_id, location)); } void OnExtAudioOutDescriptionChanged(const RawAddress& address, uint8_t ext_output_id, std::string descr) override { do_in_jni_thread(Bind(&VolumeControlCallbacks::OnExtAudioOutDescriptionChanged, Unretained(callbacks_), address, ext_output_id, descr)); } /* Callbacks for Audio Input Stream (AIS) - Extended Audio Inputs */ void OnExtAudioInStateChanged(const RawAddress& address, uint8_t ext_input_id, int8_t gain_setting, ::Mute mute, ::GainMode gain_mode) override { do_in_jni_thread(Bind(&VolumeControlCallbacks::OnExtAudioInStateChanged, Unretained(callbacks_), address, ext_input_id, gain_setting, mute, gain_mode)); } void OnExtAudioInSetGainSettingFailed(const RawAddress& address, uint8_t ext_input_id) override { do_in_jni_thread(Bind(&VolumeControlCallbacks::OnExtAudioInSetGainSettingFailed, Unretained(callbacks_), address, ext_input_id)); } void OnExtAudioInSetMuteFailed(const RawAddress& address, uint8_t ext_input_id) override { do_in_jni_thread(Bind(&VolumeControlCallbacks::OnExtAudioInSetMuteFailed, Unretained(callbacks_), address, ext_input_id)); } void OnExtAudioInSetGainModeFailed(const RawAddress& address, uint8_t ext_input_id) override { do_in_jni_thread(Bind(&VolumeControlCallbacks::OnExtAudioInSetGainModeFailed, Unretained(callbacks_), address, ext_input_id)); } void OnExtAudioInStatusChanged(const RawAddress& address, uint8_t ext_input_id, bluetooth::vc::VolumeInputStatus status) override { do_in_jni_thread(Bind(&VolumeControlCallbacks::OnExtAudioInStatusChanged, Unretained(callbacks_), address, ext_input_id, status)); } void OnExtAudioInTypeChanged(const RawAddress& address, uint8_t ext_input_id, bluetooth::vc::VolumeInputType type) override { do_in_jni_thread(Bind(&VolumeControlCallbacks::OnExtAudioInTypeChanged, Unretained(callbacks_), address, ext_input_id, type)); } void OnExtAudioInGainSettingPropertiesChanged(const RawAddress& address, uint8_t ext_input_id, uint8_t unit, int8_t min, int8_t max) override { do_in_jni_thread(Bind(&VolumeControlCallbacks::OnExtAudioInGainSettingPropertiesChanged, Unretained(callbacks_), address, ext_input_id, unit, min, max)); } void OnExtAudioInDescriptionChanged(const RawAddress& address, uint8_t ext_input_id, std::string description, bool is_writable) override { do_in_jni_thread(Bind(&VolumeControlCallbacks::OnExtAudioInDescriptionChanged, Unretained(callbacks_), address, ext_input_id, description, is_writable)); } void Connect(const RawAddress& address) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service " "being not read"); return; } do_in_main_thread(Bind(&VolumeControl::Connect, Unretained(VolumeControl::Get()), address)); } void Disconnect(const RawAddress& address) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service " "being not read"); return; } do_in_main_thread(Bind(&VolumeControl::Disconnect, Unretained(VolumeControl::Get()), address)); } void SetVolume(std::variant addr_or_group_id, uint8_t volume) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service " "being not read"); return; } do_in_main_thread(Bind(&VolumeControl::SetVolume, Unretained(VolumeControl::Get()), std::move(addr_or_group_id), volume)); } void Mute(std::variant addr_or_group_id) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service " "being not read"); return; } do_in_main_thread(Bind(&VolumeControl::Mute, Unretained(VolumeControl::Get()), std::move(addr_or_group_id))); } void Unmute(std::variant addr_or_group_id) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service " "being not read"); return; } do_in_main_thread(Bind(&VolumeControl::UnMute, Unretained(VolumeControl::Get()), std::move(addr_or_group_id))); } void RemoveDevice(const RawAddress& address) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service " "being not read"); return; } /* RemoveDevice can be called on devices that don't have HA enabled */ if (VolumeControl::IsVolumeControlRunning()) { do_in_main_thread(Bind(&VolumeControl::Remove, Unretained(VolumeControl::Get()), address)); } } void GetExtAudioOutVolumeOffset(const RawAddress& address, uint8_t ext_output_id) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service " "being not read"); return; } do_in_main_thread(Bind(&VolumeControl::GetExtAudioOutVolumeOffset, Unretained(VolumeControl::Get()), address, ext_output_id)); } void SetExtAudioOutVolumeOffset(const RawAddress& address, uint8_t ext_output_id, int16_t offset_val) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service " "being not read"); return; } do_in_main_thread(Bind(&VolumeControl::SetExtAudioOutVolumeOffset, Unretained(VolumeControl::Get()), address, ext_output_id, offset_val)); } void GetExtAudioOutLocation(const RawAddress& address, uint8_t ext_output_id) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service " "being not read"); return; } do_in_main_thread(Bind(&VolumeControl::GetExtAudioOutLocation, Unretained(VolumeControl::Get()), address, ext_output_id)); } void SetExtAudioOutLocation(const RawAddress& address, uint8_t ext_output_id, uint32_t location) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service " "being not read"); return; } do_in_main_thread(Bind(&VolumeControl::SetExtAudioOutLocation, Unretained(VolumeControl::Get()), address, ext_output_id, location)); } void GetExtAudioOutDescription(const RawAddress& address, uint8_t ext_output_id) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service " "being not read"); return; } do_in_main_thread(Bind(&VolumeControl::GetExtAudioOutDescription, Unretained(VolumeControl::Get()), address, ext_output_id)); } void SetExtAudioOutDescription(const RawAddress& address, uint8_t ext_output_id, std::string descr) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service " "being not read"); return; } do_in_main_thread(Bind(&VolumeControl::SetExtAudioOutDescription, Unretained(VolumeControl::Get()), address, ext_output_id, descr)); } void GetExtAudioInState(const RawAddress& address, uint8_t ext_input_id) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service " "being not read"); return; } do_in_main_thread(Bind(&VolumeControl::GetExtAudioInState, Unretained(VolumeControl::Get()), address, ext_input_id)); } void GetExtAudioInStatus(const RawAddress& address, uint8_t ext_input_id) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service " "being not read"); return; } do_in_main_thread(Bind(&VolumeControl::GetExtAudioInStatus, Unretained(VolumeControl::Get()), address, ext_input_id)); } void GetExtAudioInType(const RawAddress& address, uint8_t ext_input_id) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service " "being not read"); return; } do_in_main_thread(Bind(&VolumeControl::GetExtAudioInType, Unretained(VolumeControl::Get()), address, ext_input_id)); } void GetExtAudioInGainProps(const RawAddress& address, uint8_t ext_input_id) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service " "being not read"); return; } do_in_main_thread(Bind(&VolumeControl::GetExtAudioInGainProps, Unretained(VolumeControl::Get()), address, ext_input_id)); } void GetExtAudioInDescription(const RawAddress& address, uint8_t ext_input_id) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service " "being not read"); return; } do_in_main_thread(Bind(&VolumeControl::GetExtAudioInDescription, Unretained(VolumeControl::Get()), address, ext_input_id)); } bool SetExtAudioInDescription(const RawAddress& address, uint8_t ext_input_id, std::string descr) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service " "being not read"); return false; } do_in_main_thread(Bind(&VolumeControl::SetExtAudioInDescription, Unretained(VolumeControl::Get()), address, ext_input_id, descr)); return true; } bool SetExtAudioInGainSetting(const RawAddress& address, uint8_t ext_input_id, int8_t gain_setting) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service being not read"); return false; } do_in_main_thread(Bind(&VolumeControl::SetExtAudioInGainSetting, Unretained(VolumeControl::Get()), address, ext_input_id, gain_setting)); return true; } bool SetExtAudioInGainMode(const RawAddress& address, uint8_t ext_input_id, ::GainMode gain_mode) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service being not read"); return false; } do_in_main_thread(Bind(&VolumeControl::SetExtAudioInGainMode, Unretained(VolumeControl::Get()), address, ext_input_id, gain_mode)); return true; } bool SetExtAudioInMute(const RawAddress& address, uint8_t ext_input_id, ::Mute mute) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service being not read"); return false; } do_in_main_thread(Bind(&VolumeControl::SetExtAudioInMute, Unretained(VolumeControl::Get()), address, ext_input_id, mute)); return true; } void Cleanup(void) override { if (!initialized || !VolumeControl::IsVolumeControlRunning()) { bluetooth::log::verbose( "call ignored, due to already started cleanup procedure or service " "being not read"); return; } initialized = false; do_in_main_thread(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(); }