blob: cb67acda97af6949b64bc1c265e739a0fae65d23 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/services/assistant/platform/audio_input_host.h"
#include "base/check.h"
#include "base/metrics/histogram_functions.h"
#include "base/optional.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "chromeos/audio/cras_audio_handler.h"
#include "chromeos/services/assistant/platform/audio_input_impl.h"
#include "chromeos/services/assistant/public/cpp/features.h"
namespace chromeos {
namespace assistant {
namespace {
constexpr const char kDefaultLocale[] = "en_us";
AudioInputImpl::LidState ConvertLidState(
chromeos::PowerManagerClient::LidState state) {
switch (state) {
case chromeos::PowerManagerClient::LidState::CLOSED:
return AudioInputImpl::LidState::kClosed;
case chromeos::PowerManagerClient::LidState::OPEN:
return AudioInputImpl::LidState::kOpen;
case chromeos::PowerManagerClient::LidState::NOT_PRESENT:
// If there is no lid, it can't be closed.
return AudioInputImpl::LidState::kOpen;
}
}
// Hotword model is expected to have <language>_<region> format with lower
// case, while the locale in pref is stored as <language>-<region> with region
// code in capital letters. So we need to convert the pref locale to the
// correct format.
// Examples:
// "fr" -> "fr_fr"
// "nl-BE" -> "nl_be"
base::Optional<std::string> ToHotwordModel(std::string pref_locale) {
std::vector<std::string> code_strings = base::SplitString(
pref_locale, "-", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (code_strings.size() == 0) {
// Note: I am not sure this happens during real operations, but it
// definitely happens during the ChromeOS performance tests.
return base::nullopt;
}
DCHECK_LT(code_strings.size(), 3u);
// For locales with language code "en", use "en_all" hotword model.
if (code_strings[0] == "en")
return "en_all";
// If the language code and country code happen to be the same, e.g.
// France (FR) and French (fr), the locale will be stored as "fr" instead
// of "fr-FR" in the profile on Chrome OS.
if (code_strings.size() == 1)
return code_strings[0] + "_" + code_strings[0];
return code_strings[0] + "_" + base::ToLowerASCII(code_strings[1]);
}
} // namespace
chromeos::assistant::AudioInputHost::AudioInputHost(
AudioInputImpl* audio_input,
CrasAudioHandler* cras_audio_handler,
chromeos::PowerManagerClient* power_manager_client)
: audio_input_(audio_input),
cras_audio_handler_(cras_audio_handler),
power_manager_client_(power_manager_client),
power_manager_client_observer_(this) {
DCHECK(audio_input_);
DCHECK(cras_audio_handler_);
DCHECK(power_manager_client_);
power_manager_client_observer_.Observe(power_manager_client);
power_manager_client->GetSwitchStates(base::BindOnce(
&AudioInputHost::OnInitialLidStateReceived, weak_factory_.GetWeakPtr()));
}
AudioInputHost::~AudioInputHost() = default;
void AudioInputHost::SetMicState(bool mic_open) {
audio_input_->SetMicState(mic_open);
}
void AudioInputHost::SetDeviceId(const std::string& device_id) {
audio_input_->SetDeviceId(device_id);
}
void AudioInputHost::OnConversationTurnStarted() {
audio_input_->OnConversationTurnStarted();
// Inform power manager of a wake notification when Libassistant
// recognized hotword and started a conversation. We intentionally
// avoid using |NotifyUserActivity| because it is not suitable for
// this case according to the Platform team.
power_manager_client_->NotifyWakeNotification();
}
void AudioInputHost::OnConversationTurnFinished() {
audio_input_->OnConversationTurnFinished();
}
void AudioInputHost::OnHotwordEnabled(bool enable) {
audio_input_->OnHotwordEnabled(enable);
}
void AudioInputHost::SetHotwordDeviceId(const std::string& device_id) {
hotword_device_id_ = device_id;
audio_input_->SetHotwordDeviceId(device_id);
}
void AudioInputHost::SetDspHotwordLocale(std::string pref_locale) {
if (!features::IsDspHotwordEnabled())
return;
std::string hotword_model =
ToHotwordModel(pref_locale).value_or(kDefaultLocale);
cras_audio_handler_->SetHotwordModel(
GetDspNodeId(), hotword_model,
base::BindOnce(&AudioInputHost::SetDspHotwordLocaleCallback,
weak_factory_.GetWeakPtr(), hotword_model));
}
void AudioInputHost::SetDspHotwordLocaleCallback(std::string pref_locale,
bool success) {
base::UmaHistogramBoolean("Assistant.SetDspHotwordLocale", success);
if (success)
return;
LOG(ERROR) << "Set " << pref_locale
<< " hotword model failed, fallback to default locale.";
// Reset the locale to the default value if we failed to sync it to the locale
// stored in user's pref.
cras_audio_handler_->SetHotwordModel(
GetDspNodeId(), /* hotword_model */ kDefaultLocale,
base::BindOnce([](bool success) {
if (!success)
LOG(ERROR) << "Reset to default hotword model failed.";
}));
}
uint64_t AudioInputHost::GetDspNodeId() const {
DCHECK(!hotword_device_id_.empty());
uint64_t result;
bool success = base::StringToUint64(hotword_device_id_, &result);
DCHECK(success) << "Invalid hotword device id '" << hotword_device_id_ << "'";
return result;
}
void AudioInputHost::LidEventReceived(
chromeos::PowerManagerClient::LidState state,
base::TimeTicks timestamp) {
// Lid switch event still gets fired during system suspend, which enables
// us to stop DSP recording correctly when user closes lid after the device
// goes to sleep.
audio_input_->OnLidStateChanged(ConvertLidState(state));
}
void AudioInputHost::OnInitialLidStateReceived(
base::Optional<chromeos::PowerManagerClient::SwitchStates> switch_states) {
if (switch_states.has_value())
audio_input_->OnLidStateChanged(ConvertLidState(switch_states->lid_state));
}
} // namespace assistant
} // namespace chromeos