|  | // Copyright 2013 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "extensions/browser/api/audio/audio_service.h" | 
|  |  | 
|  | #include <stddef.h> | 
|  | #include <stdint.h> | 
|  |  | 
|  | #include "base/containers/contains.h" | 
|  | #include "base/functional/callback.h" | 
|  | #include "base/memory/raw_ptr.h" | 
|  | #include "base/memory/weak_ptr.h" | 
|  | #include "base/strings/string_number_conversions.h" | 
|  | #include "chromeos/ash/components/audio/audio_device.h" | 
|  | #include "chromeos/ash/components/audio/cras_audio_handler.h" | 
|  | #include "content/public/browser/browser_thread.h" | 
|  | #include "extensions/browser/api/audio/audio_device_id_calculator.h" | 
|  |  | 
|  | namespace extensions { | 
|  |  | 
|  | using api::audio::AudioDeviceInfo; | 
|  | using ::ash::AudioDevice; | 
|  | using ::ash::AudioDeviceType; | 
|  | using ::ash::CrasAudioHandler; | 
|  | using ::content::BrowserThread; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | api::audio::DeviceType GetAsAudioApiDeviceType(AudioDeviceType type) { | 
|  | switch (type) { | 
|  | case AudioDeviceType::kHeadphone: | 
|  | return api::audio::DeviceType::kHeadphone; | 
|  | case AudioDeviceType::kMic: | 
|  | return api::audio::DeviceType::kMic; | 
|  | case AudioDeviceType::kUsb: | 
|  | return api::audio::DeviceType::kUsb; | 
|  | case AudioDeviceType::kBluetooth: | 
|  | case AudioDeviceType::kBluetoothNbMic: | 
|  | return api::audio::DeviceType::kBluetooth; | 
|  | case AudioDeviceType::kHdmi: | 
|  | return api::audio::DeviceType::kHdmi; | 
|  | case AudioDeviceType::kInternalSpeaker: | 
|  | return api::audio::DeviceType::kInternalSpeaker; | 
|  | case AudioDeviceType::kInternalMic: | 
|  | return api::audio::DeviceType::kInternalMic; | 
|  | case AudioDeviceType::kFrontMic: | 
|  | return api::audio::DeviceType::kFrontMic; | 
|  | case AudioDeviceType::kRearMic: | 
|  | return api::audio::DeviceType::kRearMic; | 
|  | case AudioDeviceType::kKeyboardMic: | 
|  | return api::audio::DeviceType::kKeyboardMic; | 
|  | case AudioDeviceType::kHotword: | 
|  | return api::audio::DeviceType::kHotword; | 
|  | case AudioDeviceType::kLineout: | 
|  | return api::audio::DeviceType::kLineout; | 
|  | case AudioDeviceType::kPostMixLoopback: | 
|  | return api::audio::DeviceType::kPostMixLoopback; | 
|  | case AudioDeviceType::kPostDspLoopback: | 
|  | return api::audio::DeviceType::kPostDspLoopback; | 
|  | case AudioDeviceType::kAlsaLoopback: | 
|  | return api::audio::DeviceType::kAlsaLoopback; | 
|  | case AudioDeviceType::kOther: | 
|  | return api::audio::DeviceType::kOther; | 
|  | } | 
|  |  | 
|  | NOTREACHED(); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class AudioServiceImpl : public AudioService, | 
|  | public CrasAudioHandler::AudioObserver { | 
|  | public: | 
|  | explicit AudioServiceImpl(AudioDeviceIdCalculator* id_calculator); | 
|  |  | 
|  | AudioServiceImpl(const AudioServiceImpl&) = delete; | 
|  | AudioServiceImpl& operator=(const AudioServiceImpl&) = delete; | 
|  |  | 
|  | ~AudioServiceImpl() override; | 
|  |  | 
|  | // Called by listeners to this service to add/remove themselves as observers. | 
|  | void AddObserver(AudioService::Observer* observer) override; | 
|  | void RemoveObserver(AudioService::Observer* observer) override; | 
|  |  | 
|  | // Start to query audio device information. | 
|  | void GetDevices( | 
|  | const api::audio::DeviceFilter* filter, | 
|  | base::OnceCallback<void(bool, DeviceInfoList)> callback) override; | 
|  | void SetActiveDeviceLists(const DeviceIdList* input_devices, | 
|  | const DeviceIdList* output_devives, | 
|  | base::OnceCallback<void(bool)> callback) override; | 
|  | void SetDeviceSoundLevel(const std::string& device_id, | 
|  | int level_value, | 
|  | base::OnceCallback<void(bool)> callback) override; | 
|  | void SetMute(bool is_input, | 
|  | bool value, | 
|  | base::OnceCallback<void(bool)> callback) override; | 
|  | void GetMute(bool is_input, | 
|  | base::OnceCallback<void(bool, bool)> callback) override; | 
|  |  | 
|  | protected: | 
|  | // CrasAudioHandler::AudioObserver overrides. | 
|  | void OnOutputNodeVolumeChanged(uint64_t id, int volume) override; | 
|  | void OnInputNodeGainChanged(uint64_t id, int gain) override; | 
|  | void OnOutputMuteChanged(bool mute_on) override; | 
|  | void OnInputMuteChanged( | 
|  | bool mute_on, | 
|  | CrasAudioHandler::InputMuteChangeMethod method) override; | 
|  | void OnAudioNodesChanged() override; | 
|  | void OnActiveOutputNodeChanged() override; | 
|  | void OnActiveInputNodeChanged() override; | 
|  |  | 
|  | private: | 
|  | void NotifyLevelChanged(uint64_t id, int level); | 
|  | void NotifyMuteChanged(bool is_input, bool is_muted); | 
|  | void NotifyDevicesChanged(); | 
|  |  | 
|  | uint64_t GetIdFromStr(const std::string& id_str); | 
|  | bool GetAudioNodeIdList(const DeviceIdList& ids, | 
|  | bool is_input, | 
|  | CrasAudioHandler::NodeIdList* node_ids); | 
|  | AudioDeviceInfo ToAudioDeviceInfo(const AudioDevice& device); | 
|  |  | 
|  | // List of observers. | 
|  | base::ObserverList<AudioService::Observer>::Unchecked observer_list_; | 
|  |  | 
|  | raw_ptr<CrasAudioHandler, DanglingUntriaged> cras_audio_handler_; | 
|  |  | 
|  | raw_ptr<AudioDeviceIdCalculator> id_calculator_; | 
|  |  | 
|  | // Note: This should remain the last member so it'll be destroyed and | 
|  | // invalidate the weak pointers before any other members are destroyed. | 
|  | base::WeakPtrFactory<AudioServiceImpl> weak_ptr_factory_{this}; | 
|  | }; | 
|  |  | 
|  | AudioServiceImpl::AudioServiceImpl(AudioDeviceIdCalculator* id_calculator) | 
|  | : cras_audio_handler_(CrasAudioHandler::Get()), | 
|  | id_calculator_(id_calculator) { | 
|  | CHECK(id_calculator_); | 
|  |  | 
|  | if (cras_audio_handler_) | 
|  | cras_audio_handler_->AddAudioObserver(this); | 
|  | } | 
|  |  | 
|  | AudioServiceImpl::~AudioServiceImpl() { | 
|  | // The CrasAudioHandler global instance may have already been destroyed, so | 
|  | // do not used the cached pointer here. | 
|  | if (CrasAudioHandler::Get()) | 
|  | CrasAudioHandler::Get()->RemoveAudioObserver(this); | 
|  | } | 
|  |  | 
|  | void AudioServiceImpl::AddObserver(AudioService::Observer* observer) { | 
|  | observer_list_.AddObserver(observer); | 
|  | } | 
|  |  | 
|  | void AudioServiceImpl::RemoveObserver(AudioService::Observer* observer) { | 
|  | observer_list_.RemoveObserver(observer); | 
|  | } | 
|  |  | 
|  | void AudioServiceImpl::GetDevices( | 
|  | const api::audio::DeviceFilter* filter, | 
|  | base::OnceCallback<void(bool, DeviceInfoList)> callback) { | 
|  | DeviceInfoList devices_out; | 
|  | if (!cras_audio_handler_) { | 
|  | std::move(callback).Run(false, std::move(devices_out)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | ash::AudioDeviceList devices; | 
|  | cras_audio_handler_->GetAudioDevices(&devices); | 
|  |  | 
|  | bool accept_input = | 
|  | !(filter && filter->stream_types) || | 
|  | base::Contains(*filter->stream_types, api::audio::StreamType::kInput); | 
|  | bool accept_output = | 
|  | !(filter && filter->stream_types) || | 
|  | base::Contains(*filter->stream_types, api::audio::StreamType::kOutput); | 
|  |  | 
|  | for (const auto& device : devices) { | 
|  | if (filter && filter->is_active && *filter->is_active != device.active) | 
|  | continue; | 
|  | if (device.is_input && !accept_input) | 
|  | continue; | 
|  | if (!device.is_input && !accept_output) | 
|  | continue; | 
|  | devices_out.push_back(ToAudioDeviceInfo(device)); | 
|  | } | 
|  |  | 
|  | std::move(callback).Run(true, std::move(devices_out)); | 
|  | } | 
|  |  | 
|  | void AudioServiceImpl::SetActiveDeviceLists( | 
|  | const DeviceIdList* input_devices, | 
|  | const DeviceIdList* output_devives, | 
|  | base::OnceCallback<void(bool)> callback) { | 
|  | DCHECK(cras_audio_handler_); | 
|  | if (!cras_audio_handler_) { | 
|  | std::move(callback).Run(false); | 
|  | return; | 
|  | } | 
|  |  | 
|  | CrasAudioHandler::NodeIdList input_nodes; | 
|  | if (input_devices && | 
|  | !GetAudioNodeIdList(*input_devices, true, &input_nodes)) { | 
|  | std::move(callback).Run(false); | 
|  | return; | 
|  | } | 
|  |  | 
|  | CrasAudioHandler::NodeIdList output_nodes; | 
|  | if (output_devives && | 
|  | !GetAudioNodeIdList(*output_devives, false, &output_nodes)) { | 
|  | std::move(callback).Run(false); | 
|  | return; | 
|  | } | 
|  |  | 
|  | bool success = true; | 
|  | if (output_devives) { | 
|  | success = cras_audio_handler_->SetActiveOutputNodes(output_nodes); | 
|  | DCHECK(success); | 
|  | } | 
|  |  | 
|  | if (input_devices) { | 
|  | success = success && cras_audio_handler_->SetActiveInputNodes(input_nodes); | 
|  | DCHECK(success); | 
|  | } | 
|  | std::move(callback).Run(success); | 
|  | } | 
|  |  | 
|  | void AudioServiceImpl::SetDeviceSoundLevel( | 
|  | const std::string& device_id, | 
|  | int level_value, | 
|  | base::OnceCallback<void(bool)> callback) { | 
|  | DCHECK(cras_audio_handler_); | 
|  | if (!cras_audio_handler_) { | 
|  | std::move(callback).Run(false); | 
|  | return; | 
|  | } | 
|  |  | 
|  | const AudioDevice* device = | 
|  | cras_audio_handler_->GetDeviceFromId(GetIdFromStr(device_id)); | 
|  | if (!device) { | 
|  | std::move(callback).Run(false); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (level_value != -1) { | 
|  | cras_audio_handler_->SetVolumeGainPercentForDevice(device->id, level_value); | 
|  | std::move(callback).Run(true); | 
|  | } else { | 
|  | std::move(callback).Run(false); | 
|  | } | 
|  | } | 
|  |  | 
|  | void AudioServiceImpl::SetMute(bool is_input, | 
|  | bool value, | 
|  | base::OnceCallback<void(bool)> callback) { | 
|  | DCHECK(cras_audio_handler_); | 
|  | if (!cras_audio_handler_) { | 
|  | std::move(callback).Run(false); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (is_input) | 
|  | cras_audio_handler_->SetInputMute( | 
|  | value, CrasAudioHandler::InputMuteChangeMethod::kOther); | 
|  | else | 
|  | cras_audio_handler_->SetOutputMute(value); | 
|  |  | 
|  | std::move(callback).Run(true); | 
|  | } | 
|  |  | 
|  | void AudioServiceImpl::GetMute(bool is_input, | 
|  | base::OnceCallback<void(bool, bool)> callback) { | 
|  | DCHECK(cras_audio_handler_); | 
|  | if (!cras_audio_handler_) { | 
|  | std::move(callback).Run(false, false); | 
|  | return; | 
|  | } | 
|  |  | 
|  | const bool is_muted_result = is_input ? cras_audio_handler_->IsInputMuted() | 
|  | : cras_audio_handler_->IsOutputMuted(); | 
|  | std::move(callback).Run(true, is_muted_result); | 
|  | } | 
|  |  | 
|  | uint64_t AudioServiceImpl::GetIdFromStr(const std::string& id_str) { | 
|  | uint64_t device_id; | 
|  | if (!base::StringToUint64(id_str, &device_id)) | 
|  | return 0; | 
|  | else | 
|  | return device_id; | 
|  | } | 
|  |  | 
|  | bool AudioServiceImpl::GetAudioNodeIdList( | 
|  | const DeviceIdList& ids, | 
|  | bool is_input, | 
|  | CrasAudioHandler::NodeIdList* node_ids) { | 
|  | for (const auto& device_id : ids) { | 
|  | const AudioDevice* device = | 
|  | cras_audio_handler_->GetDeviceFromId(GetIdFromStr(device_id)); | 
|  | if (!device) | 
|  | return false; | 
|  | if (device->is_input != is_input) | 
|  | return false; | 
|  | node_ids->push_back(device->id); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | AudioDeviceInfo AudioServiceImpl::ToAudioDeviceInfo(const AudioDevice& device) { | 
|  | AudioDeviceInfo info; | 
|  | info.id = base::NumberToString(device.id); | 
|  | info.stream_type = device.is_input | 
|  | ? extensions::api::audio::StreamType::kInput | 
|  | : extensions::api::audio::StreamType::kOutput; | 
|  | info.device_type = GetAsAudioApiDeviceType(device.type); | 
|  | info.display_name = device.display_name; | 
|  | info.device_name = device.device_name; | 
|  | info.is_active = device.active; | 
|  | info.level = | 
|  | device.is_input | 
|  | ? cras_audio_handler_->GetInputGainPercentForDevice(device.id) | 
|  | : cras_audio_handler_->GetOutputVolumePercentForDevice(device.id); | 
|  | info.stable_device_id = | 
|  | id_calculator_->GetStableDeviceId(device.stable_device_id); | 
|  |  | 
|  | return info; | 
|  | } | 
|  |  | 
|  | void AudioServiceImpl::OnOutputNodeVolumeChanged(uint64_t id, int volume) { | 
|  | NotifyLevelChanged(id, volume); | 
|  | } | 
|  |  | 
|  | void AudioServiceImpl::OnOutputMuteChanged(bool mute_on) { | 
|  | NotifyMuteChanged(false, mute_on); | 
|  | } | 
|  |  | 
|  | void AudioServiceImpl::OnInputNodeGainChanged(uint64_t id, int gain) { | 
|  | NotifyLevelChanged(id, gain); | 
|  | } | 
|  |  | 
|  | void AudioServiceImpl::OnInputMuteChanged( | 
|  | bool mute_on, | 
|  | CrasAudioHandler::InputMuteChangeMethod method) { | 
|  | NotifyMuteChanged(true, mute_on); | 
|  | } | 
|  |  | 
|  | void AudioServiceImpl::OnAudioNodesChanged() { | 
|  | NotifyDevicesChanged(); | 
|  | } | 
|  |  | 
|  | void AudioServiceImpl::OnActiveOutputNodeChanged() {} | 
|  |  | 
|  | void AudioServiceImpl::OnActiveInputNodeChanged() {} | 
|  |  | 
|  | void AudioServiceImpl::NotifyLevelChanged(uint64_t id, int level) { | 
|  | for (auto& observer : observer_list_) | 
|  | observer.OnLevelChanged(base::NumberToString(id), level); | 
|  | } | 
|  |  | 
|  | void AudioServiceImpl::NotifyMuteChanged(bool is_input, bool is_muted) { | 
|  | for (auto& observer : observer_list_) | 
|  | observer.OnMuteChanged(is_input, is_muted); | 
|  | } | 
|  |  | 
|  | void AudioServiceImpl::NotifyDevicesChanged() { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
|  |  | 
|  | ash::AudioDeviceList devices; | 
|  | cras_audio_handler_->GetAudioDevices(&devices); | 
|  |  | 
|  | DeviceInfoList device_info_list; | 
|  | for (const auto& device : devices) { | 
|  | device_info_list.push_back(ToAudioDeviceInfo(device)); | 
|  | } | 
|  |  | 
|  | for (auto& observer : observer_list_) | 
|  | observer.OnDevicesChanged(device_info_list); | 
|  | } | 
|  |  | 
|  | AudioService::Ptr AudioService::CreateInstance( | 
|  | AudioDeviceIdCalculator* id_calculator) { | 
|  | return std::make_unique<AudioServiceImpl>(id_calculator); | 
|  | } | 
|  |  | 
|  | }  // namespace extensions |