blob: d95f81dfc76a6179676b8e0add0c2598f9e1922a [file] [log] [blame]
// Copyright 2017 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/arc/tts/arc_tts_service.h"
#include <memory>
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "ash/components/arc/test/fake_arc_session.h"
#include "base/threading/platform_thread.h"
#include "chrome/browser/speech/tts_chromeos.h"
#include "chrome/test/base/testing_profile.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
#include "content/public/browser/tts_controller.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace arc {
namespace {
class TestableTtsController : public content::TtsController {
public:
TestableTtsController() = default;
TestableTtsController(const TestableTtsController&) = delete;
TestableTtsController& operator=(const TestableTtsController&) = delete;
~TestableTtsController() override = default;
void OnTtsEvent(int utterance_id,
content::TtsEventType event_type,
int char_index,
int length,
const std::string& error_message) override {
last_utterance_id_ = utterance_id;
last_event_type_ = event_type;
last_char_index_ = char_index;
last_length_ = length;
last_error_message_ = error_message;
}
// Unimplemented.
bool IsSpeaking() override { return false; }
void SpeakOrEnqueue(
std::unique_ptr<content::TtsUtterance> utterance) override {}
void Stop() override {}
void Stop(const GURL& source_url) override {}
void Pause() override {}
void Resume() override {}
void GetVoices(content::BrowserContext* browser_context,
const GURL& source_url,
std::vector<content::VoiceData>* out_voices) override {}
void VoicesChanged() override {}
void AddVoicesChangedDelegate(
content::VoicesChangedDelegate* delegate) override {}
void RemoveVoicesChangedDelegate(
content::VoicesChangedDelegate* delegate) override {}
void RemoveUtteranceEventDelegate(
content::UtteranceEventDelegate* delegate) override {}
void SetTtsEngineDelegate(content::TtsEngineDelegate* delegate) override {}
content::TtsEngineDelegate* GetTtsEngineDelegate() override {
return nullptr;
}
void RefreshVoices() override {}
void SetRemoteTtsEngineDelegate(
content::RemoteTtsEngineDelegate* delegate) override {}
void SetTtsPlatform(content::TtsPlatform* tts_platform) override {}
int QueueSize() override { return 0; }
void StripSSML(
const std::string& utterance,
base::OnceCallback<void(const std::string&)> callback) override {}
void OnTtsUtteranceBecameInvalid(int utterance_id) override {}
int last_utterance_id_;
content::TtsEventType last_event_type_;
int last_char_index_;
int last_length_;
std::string last_error_message_;
};
class ArcTtsServiceTest : public testing::Test {
public:
ArcTtsServiceTest()
: arc_service_manager_(std::make_unique<ArcServiceManager>()),
testing_profile_(std::make_unique<TestingProfile>()),
tts_controller_(std::make_unique<TestableTtsController>()),
tts_service_(ArcTtsService::GetForBrowserContextForTesting(
testing_profile_.get())) {
tts_service_->set_tts_controller_for_testing(tts_controller_.get());
}
ArcTtsServiceTest(const ArcTtsServiceTest&) = delete;
ArcTtsServiceTest& operator=(const ArcTtsServiceTest&) = delete;
~ArcTtsServiceTest() override { tts_service_->Shutdown(); }
protected:
ArcTtsService* tts_service() const { return tts_service_; }
TestableTtsController* tts_controller() const {
return tts_controller_.get();
}
private:
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<ArcServiceManager> arc_service_manager_;
std::unique_ptr<TestingProfile> testing_profile_;
std::unique_ptr<TestableTtsController> tts_controller_;
ArcTtsService* const tts_service_;
};
// Tests that ArcTtsService can be constructed and destructed.
TEST_F(ArcTtsServiceTest, TestConstructDestruct) {}
// Tests that OnTtsEvent() properly calls into
// TtsControllerDelegateImpl::OnTtsEvent().
TEST_F(ArcTtsServiceTest, TestOnTtsEvent) {
tts_service()->OnTtsEvent(1, mojom::TtsEventType::START, 0, -1, "");
EXPECT_EQ(1, tts_controller()->last_utterance_id_);
EXPECT_EQ(content::TTS_EVENT_START, tts_controller()->last_event_type_);
EXPECT_EQ(-1, tts_controller()->last_length_);
EXPECT_EQ("", tts_controller()->last_error_message_);
tts_service()->OnTtsEvent(1, mojom::TtsEventType::END, 10, 2, "");
EXPECT_EQ(1, tts_controller()->last_utterance_id_);
EXPECT_EQ(content::TTS_EVENT_END, tts_controller()->last_event_type_);
EXPECT_EQ(2, tts_controller()->last_length_);
EXPECT_EQ("", tts_controller()->last_error_message_);
tts_service()->OnTtsEvent(2, mojom::TtsEventType::INTERRUPTED, 0, -1, "");
EXPECT_EQ(2, tts_controller()->last_utterance_id_);
EXPECT_EQ(content::TTS_EVENT_INTERRUPTED, tts_controller()->last_event_type_);
EXPECT_EQ(-1, tts_controller()->last_length_);
EXPECT_EQ("", tts_controller()->last_error_message_);
tts_service()->OnTtsEvent(3, mojom::TtsEventType::ERROR, 0, 10, "");
EXPECT_EQ(3, tts_controller()->last_utterance_id_);
EXPECT_EQ(content::TTS_EVENT_ERROR, tts_controller()->last_event_type_);
EXPECT_EQ(10, tts_controller()->last_length_);
EXPECT_EQ("", tts_controller()->last_error_message_);
}
TEST_F(ArcTtsServiceTest, GetVoices) {
std::vector<mojom::TtsVoicePtr> android_voices;
// These voices are sorted so that those where
// |is_network_connection_required| when false, come before those with that
// field set to true.
auto voice1 = mojom::TtsVoice::New();
voice1->name = "voice1";
voice1->locale = "en_US";
voice1->is_network_connection_required = true;
android_voices.push_back(std::move(voice1));
auto voice0 = mojom::TtsVoice::New();
voice0->name = "voice0";
voice0->locale = "eng-usa";
voice0->is_network_connection_required = false;
android_voices.push_back(std::move(voice0));
auto voice2 = mojom::TtsVoice::New();
voice2->name = "voice2";
voice2->locale = "FOO_bar";
voice2->is_network_connection_required = true;
android_voices.push_back(std::move(voice2));
tts_service()->OnVoicesChanged(std::move(android_voices));
TtsPlatformImplChromeOs* tts_chromeos =
TtsPlatformImplChromeOs::GetInstance();
std::vector<content::VoiceData> chrome_voices;
tts_chromeos->GetVoices(&chrome_voices);
EXPECT_EQ(3U, chrome_voices.size());
EXPECT_EQ("voice0", chrome_voices[0].name);
EXPECT_EQ("en-US", chrome_voices[0].lang);
EXPECT_FALSE(chrome_voices[0].remote);
EXPECT_EQ("voice1", chrome_voices[1].name);
EXPECT_EQ("en-US", chrome_voices[1].lang);
EXPECT_TRUE(chrome_voices[1].remote);
EXPECT_EQ("voice2", chrome_voices[2].name);
EXPECT_EQ("foo-BAR", chrome_voices[2].lang);
EXPECT_TRUE(chrome_voices[2].remote);
}
} // namespace
TEST_F(ArcTtsServiceTest, ChromeVoiceEvents) {
std::vector<mojom::TtsVoicePtr> android_voices;
auto voice0 = mojom::TtsVoice::New();
android_voices.push_back(std::move(voice0));
auto voice1 = mojom::TtsVoice::New();
android_voices.push_back(std::move(voice1));
tts_service()->OnVoicesChanged(std::move(android_voices));
TtsPlatformImplChromeOs* tts_chromeos =
TtsPlatformImplChromeOs::GetInstance();
std::vector<content::VoiceData> chrome_voices;
tts_chromeos->GetVoices(&chrome_voices);
EXPECT_EQ(2U, chrome_voices.size());
std::set<content::TtsEventType> expected_events(
{content::TTS_EVENT_START, content::TTS_EVENT_END,
content::TTS_EVENT_INTERRUPTED, content::TTS_EVENT_ERROR});
EXPECT_EQ(expected_events, chrome_voices[0].events);
EXPECT_EQ(expected_events, chrome_voices[1].events);
chrome_voices.clear();
tts_chromeos->ReceivedWordEvent();
tts_chromeos->GetVoices(&chrome_voices);
expected_events.insert(content::TTS_EVENT_WORD);
EXPECT_EQ(expected_events, chrome_voices[0].events);
EXPECT_EQ(expected_events, chrome_voices[1].events);
// All events should have been mapped at runtime.
EXPECT_EQ(static_cast<size_t>(mojom::TtsEventType::kMaxValue),
expected_events.size() - 1);
// Setting the voice again results in all Android-side mojo events besides
// word.
auto voice2 = mojom::TtsVoice::New();
std::vector<mojom::TtsVoicePtr> more_android_voices;
more_android_voices.push_back(std::move(voice2));
tts_service()->OnVoicesChanged(std::move(more_android_voices));
chrome_voices.clear();
tts_chromeos->GetVoices(&chrome_voices);
expected_events.erase(content::TTS_EVENT_WORD);
EXPECT_EQ(1U, chrome_voices.size());
EXPECT_EQ(expected_events, chrome_voices[0].events);
}
} // namespace arc