blob: 86e67dc5241258e46dc5b2cd93b5542f1c345317 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/accessibility/speech_monitor.h"
#include "base/strings/pattern.h"
#include "chrome/common/extensions/extension_constants.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/tts_controller.h"
namespace ash {
namespace test {
namespace {
constexpr int kPrintExpectationDelayMs = 3000;
} // namespace
SpeechMonitor::SpeechMonitor() {
content::TtsController::SkipAddNetworkChangeObserverForTests(true);
content::TtsController::GetInstance()->SetTtsPlatform(this);
}
SpeechMonitor::~SpeechMonitor() {
content::TtsController::GetInstance()->SetTtsPlatform(
content::TtsPlatform::GetInstance());
if (!replay_queue_.empty() || !replayed_queue_.empty())
CHECK(replay_called_) << "Expectation was made, but Replay() not called.";
}
bool SpeechMonitor::PlatformImplSupported() {
return true;
}
bool SpeechMonitor::PlatformImplInitialized() {
return true;
}
void SpeechMonitor::Speak(int utterance_id,
const std::string& utterance,
const std::string& lang,
const content::VoiceData& voice,
const content::UtteranceContinuousParameters& params,
base::OnceCallback<void(bool)> on_speak_finished) {
CHECK(!utterance.empty())
<< "If you're deliberately speaking the "
"empty string in a test, that's probably not the correct way to "
"achieve stopping speech. If it is unintended, it indicates a deeper "
"underlying issue.";
content::TtsController::GetInstance()->OnTtsEvent(
utterance_id, content::TTS_EVENT_START, 0,
static_cast<int>(utterance.size()), std::string());
content::TtsController::GetInstance()->OnTtsEvent(
utterance_id, content::TTS_EVENT_END, static_cast<int>(utterance.size()),
0, std::string());
std::move(on_speak_finished).Run(true);
time_of_last_utterance_ = std::chrono::steady_clock::now();
}
bool SpeechMonitor::StopSpeaking() {
++stop_count_;
return true;
}
bool SpeechMonitor::IsSpeaking() {
return false;
}
void SpeechMonitor::GetVoices(std::vector<content::VoiceData>* out_voices) {
out_voices->push_back(content::VoiceData());
content::VoiceData& voice = out_voices->back();
voice.native = true;
voice.name = "SpeechMonitor";
voice.engine_id = extension_misc::kGoogleSpeechSynthesisExtensionId;
voice.events.insert(content::TTS_EVENT_END);
}
void SpeechMonitor::WillSpeakUtteranceWithVoice(
content::TtsUtterance* utterance,
const content::VoiceData& voice_data) {
utterance_queue_.emplace_back(utterance->GetText(), utterance->GetLang());
delay_for_last_utterance_ms_ = CalculateUtteranceDelayMS();
MaybeContinueReplay();
}
void SpeechMonitor::LoadBuiltInTtsEngine(
content::BrowserContext* browser_context) {}
std::string SpeechMonitor::GetError() {
return error_;
}
void SpeechMonitor::ClearError() {
error_ = std::string();
}
void SpeechMonitor::SetError(const std::string& error) {
error_ = error;
}
void SpeechMonitor::Shutdown() {}
void SpeechMonitor::FinalizeVoiceOrdering(
std::vector<content::VoiceData>& voices) {}
void SpeechMonitor::RefreshVoices() {}
content::ExternalPlatformDelegate*
SpeechMonitor::GetExternalPlatformDelegate() {
return nullptr;
}
double SpeechMonitor::CalculateUtteranceDelayMS() {
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
std::chrono::duration<double> time_span =
std::chrono::duration_cast<std::chrono::duration<double>>(
now - time_of_last_utterance_);
return time_span.count() * 1000;
}
double SpeechMonitor::GetDelayForLastUtteranceMS() {
return delay_for_last_utterance_ms_;
}
void SpeechMonitor::ExpectSpeech(const std::string& text,
const base::Location& location) {
CHECK(!replay_loop_runner_.get());
replay_queue_.push_back(
{[this, text]() {
std::vector<std::string> all_text;
for (auto it = utterance_queue_.begin(); it != utterance_queue_.end();
it++) {
all_text.push_back(it->text);
std::string joined_all_text = base::JoinString(all_text, " ");
if (it->text == text ||
joined_all_text.find(text) != std::string::npos) {
// Erase all utterances that came before the
// match as well as the match itself.
utterance_queue_.erase(utterance_queue_.begin(), it + 1);
return true;
}
}
return false;
},
"ExpectSpeech(\"" + text + "\") " + location.ToString()});
}
void SpeechMonitor::ExpectSpeechPattern(const std::string& pattern,
const base::Location& location) {
ExpectSpeechPatternWithLocale(pattern, "", location);
}
void SpeechMonitor::ExpectSpeechPatternWithLocale(
const std::string& pattern,
const std::string& locale,
const base::Location& location) {
CHECK(!replay_loop_runner_.get());
replay_queue_.push_back(
{[this, pattern, locale]() {
std::vector<std::string> all_text;
for (auto it = utterance_queue_.begin(); it != utterance_queue_.end();
it++) {
all_text.push_back(it->text);
std::string joined_all_text = base::JoinString(all_text, " ");
if ((base::MatchPattern(it->text, pattern) &&
(locale.empty() || it->lang == locale)) ||
base::MatchPattern(joined_all_text, "*" + pattern)) {
// Erase all utterances that came before the
// match as well as the match itself.
utterance_queue_.erase(utterance_queue_.begin(), it + 1);
return true;
}
}
return false;
},
"ExpectSpeechPattern(\"" + pattern + "\") " + location.ToString()});
}
void SpeechMonitor::ExpectNextSpeechIsNot(const std::string& text,
const base::Location& location) {
CHECK(!replay_loop_runner_.get());
replay_queue_.push_back(
{[this, text]() {
if (utterance_queue_.empty())
return false;
return text != utterance_queue_.front().text;
},
"ExpectNextSpeechIsNot(\"" + text + "\") " + location.ToString()});
}
void SpeechMonitor::ExpectNextSpeechIsNotPattern(
const std::string& pattern,
const base::Location& location) {
CHECK(!replay_loop_runner_.get());
replay_queue_.push_back({[this, pattern]() {
if (utterance_queue_.empty())
return false;
return !base::MatchPattern(
utterance_queue_.front().text, pattern);
},
"ExpectNextSpeechIsNotPattern(\"" + pattern +
"\") " + location.ToString()});
}
void SpeechMonitor::Call(std::function<void()> func,
const base::Location& location) {
CHECK(!replay_loop_runner_.get());
replay_queue_.push_back({[func]() {
func();
return true;
},
"Call() " + location.ToString()});
}
void SpeechMonitor::Replay() {
replay_called_ = true;
MaybeContinueReplay();
}
void SpeechMonitor::MaybeContinueReplay() {
// This method can be called prior to Replay() being called.
if (!replay_called_)
return;
auto it = replay_queue_.begin();
while (it != replay_queue_.end()) {
ReplayArgs current = *it;
it = replay_queue_.erase(it);
if (current.first()) {
// Careful here; the above callback may have triggered more speech which
// causes |MaybeContinueReplay| to be called recursively. We have to
// ensure to check |replay_queue_| here.
if (replay_queue_.empty())
break;
replayed_queue_.push_back(current.second);
} else {
replay_queue_.insert(replay_queue_.begin(), current);
it = replay_queue_.begin();
break;
}
}
if (!replay_queue_.empty()) {
content::GetUIThreadTaskRunner({})->PostDelayedTask(
FROM_HERE,
base::BindOnce(&SpeechMonitor::MaybePrintExpectations,
weak_factory_.GetWeakPtr()),
base::Milliseconds(kPrintExpectationDelayMs));
if (!replay_loop_runner_.get()) {
replay_loop_runner_ = new content::MessageLoopRunner();
replay_loop_runner_->Run();
}
} else if (replay_queue_.empty() && replay_loop_runner_.get()) {
replay_loop_runner_->Quit();
}
}
void SpeechMonitor::MaybePrintExpectations() {
if (CalculateUtteranceDelayMS() < kPrintExpectationDelayMs ||
replay_queue_.empty())
return;
if (last_replay_queue_size_ == replay_queue_.size())
return;
last_replay_queue_size_ = replay_queue_.size();
std::vector<std::string> replay_queue_descriptions;
for (const auto& pair : replay_queue_)
replay_queue_descriptions.push_back(pair.second);
std::vector<std::string> utterance_queue_descriptions;
for (const auto& item : utterance_queue_)
utterance_queue_descriptions.push_back("\"" + item.text + "\"");
LOG(ERROR) << "Still waiting for expectation(s).\n"
<< "Unsatisfied expectations...\n"
<< base::JoinString(replay_queue_descriptions, "\n") << "\n\n"
<< "pending speech utterances...\n"
<< base::JoinString(utterance_queue_descriptions, "\n") << "\n\n"
<< "Satisfied expectations...\n"
<< base::JoinString(replayed_queue_, "\n");
}
} // namespace test
} // namespace ash