1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/webui/settings/tts_handler.h"
6 
7 #include "base/bind.h"
8 #include "base/json/json_reader.h"
9 #include "base/values.h"
10 #include "chrome/browser/browser_process.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/speech/extension_api/tts_engine_extension_api.h"
13 #include "chrome/browser/speech/extension_api/tts_engine_extension_observer.h"
14 #include "chrome/browser/ui/chrome_pages.h"
15 #include "chrome/common/extensions/extension_constants.h"
16 #include "chrome/grit/generated_resources.h"
17 #include "content/public/browser/tts_controller.h"
18 #include "content/public/browser/web_ui.h"
19 #include "extensions/browser/event_router.h"
20 #include "extensions/browser/extension_registry.h"
21 #include "extensions/browser/process_manager.h"
22 #include "extensions/common/extension.h"
23 #include "extensions/common/extension_set.h"
24 #include "extensions/common/manifest_handlers/options_page_info.h"
25 #include "ui/base/l10n/l10n_util.h"
26 
27 namespace settings {
TtsHandler()28 TtsHandler::TtsHandler() {}
29 
~TtsHandler()30 TtsHandler::~TtsHandler() {
31   content::TtsController::GetInstance()->RemoveVoicesChangedDelegate(this);
32 }
33 
HandleGetAllTtsVoiceData(const base::ListValue * args)34 void TtsHandler::HandleGetAllTtsVoiceData(const base::ListValue* args) {
35   OnVoicesChanged();
36 }
37 
HandleGetTtsExtensions(const base::ListValue * args)38 void TtsHandler::HandleGetTtsExtensions(const base::ListValue* args) {
39   // Ensure the built in tts engine is loaded to be able to respond to messages.
40   WakeTtsEngine(nullptr);
41 
42   base::ListValue responses;
43   Profile* profile = Profile::FromWebUI(web_ui());
44   extensions::ExtensionRegistry* registry =
45       extensions::ExtensionRegistry::Get(profile);
46 
47   const std::set<std::string> extensions =
48       TtsEngineExtensionObserver::GetInstance(profile)->GetTtsExtensions();
49   std::set<std::string>::const_iterator iter;
50   for (iter = extensions.begin(); iter != extensions.end(); ++iter) {
51     const std::string extension_id = *iter;
52     const extensions::Extension* extension =
53         registry->GetInstalledExtension(extension_id);
54     if (!extension) {
55       // The extension is still loading from OnVoicesChange call to
56       // TtsController::GetVoices(). Don't do any work, voices will
57       // be updated again after extension load.
58       continue;
59     }
60     base::DictionaryValue response;
61     response.SetString("name", extension->name());
62     response.SetString("extensionId", extension_id);
63     if (extensions::OptionsPageInfo::HasOptionsPage(extension)) {
64       response.SetString(
65           "optionsPage",
66 
67           extensions::OptionsPageInfo::GetOptionsPage(extension).spec());
68     }
69     responses.Append(std::move(response));
70   }
71 
72   FireWebUIListener("tts-extensions-updated", responses);
73 }
74 
OnVoicesChanged()75 void TtsHandler::OnVoicesChanged() {
76   content::TtsController* tts_controller =
77       content::TtsController::GetInstance();
78   std::vector<content::VoiceData> voices;
79   tts_controller->GetVoices(Profile::FromWebUI(web_ui()), &voices);
80   const std::string& app_locale = g_browser_process->GetApplicationLocale();
81   base::ListValue responses;
82   for (const auto& voice : voices) {
83     base::DictionaryValue response;
84     int language_score = GetVoiceLangMatchScore(&voice, app_locale);
85     std::string language_code;
86     if (voice.lang.empty()) {
87       language_code = "noLanguageCode";
88       response.SetString(
89           "displayLanguage",
90           l10n_util::GetStringUTF8(IDS_TEXT_TO_SPEECH_SETTINGS_NO_LANGUAGE));
91     } else {
92       language_code = l10n_util::GetLanguage(voice.lang);
93       response.SetString(
94           "displayLanguage",
95           l10n_util::GetDisplayNameForLocale(
96               language_code, g_browser_process->GetApplicationLocale(), true));
97     }
98     response.SetString("name", voice.name);
99     response.SetString("languageCode", language_code);
100     response.SetString("fullLanguageCode", voice.lang);
101     response.SetInteger("languageScore", language_score);
102     response.SetString("extensionId", voice.engine_id);
103     responses.Append(std::move(response));
104   }
105   AllowJavascript();
106   FireWebUIListener("all-voice-data-updated", responses);
107 
108   // Also refresh the TTS extensions in case they have changed.
109   HandleGetTtsExtensions(nullptr);
110 }
111 
OnTtsEvent(content::TtsUtterance * utterance,content::TtsEventType event_type,int char_index,int length,const std::string & error_message)112 void TtsHandler::OnTtsEvent(content::TtsUtterance* utterance,
113                             content::TtsEventType event_type,
114                             int char_index,
115                             int length,
116                             const std::string& error_message) {
117   if (event_type == content::TTS_EVENT_END ||
118       event_type == content::TTS_EVENT_INTERRUPTED ||
119       event_type == content::TTS_EVENT_ERROR) {
120     base::Value result(false /* preview stopped */);
121     FireWebUIListener("tts-preview-state-changed", result);
122   }
123 }
124 
HandlePreviewTtsVoice(const base::ListValue * args)125 void TtsHandler::HandlePreviewTtsVoice(const base::ListValue* args) {
126   DCHECK_EQ(2U, args->GetSize());
127   std::string text;
128   std::string voice_id;
129   args->GetString(0, &text);
130   args->GetString(1, &voice_id);
131 
132   if (text.empty() || voice_id.empty())
133     return;
134 
135   std::unique_ptr<base::DictionaryValue> json =
136       base::DictionaryValue::From(base::JSONReader::ReadDeprecated(voice_id));
137   std::string name;
138   std::string extension_id;
139   json->GetString("name", &name);
140   json->GetString("extension", &extension_id);
141 
142   std::unique_ptr<content::TtsUtterance> utterance =
143       content::TtsUtterance::Create((Profile::FromWebUI(web_ui())));
144   utterance->SetText(text);
145   utterance->SetVoiceName(name);
146   utterance->SetEngineId(extension_id);
147   utterance->SetSrcUrl(
148       GURL(chrome::GetOSSettingsUrl("manageAccessibility/tts")));
149   utterance->SetEventDelegate(this);
150   content::TtsController::GetInstance()->Stop();
151 
152   base::Value result(true /* preview started */);
153   FireWebUIListener("tts-preview-state-changed", result);
154   content::TtsController::GetInstance()->SpeakOrEnqueue(std::move(utterance));
155 }
156 
RegisterMessages()157 void TtsHandler::RegisterMessages() {
158   web_ui()->RegisterMessageCallback(
159       "getAllTtsVoiceData",
160       base::BindRepeating(&TtsHandler::HandleGetAllTtsVoiceData,
161                           base::Unretained(this)));
162   web_ui()->RegisterMessageCallback(
163       "getTtsExtensions",
164       base::BindRepeating(&TtsHandler::HandleGetTtsExtensions,
165                           base::Unretained(this)));
166   web_ui()->RegisterMessageCallback(
167       "previewTtsVoice", base::BindRepeating(&TtsHandler::HandlePreviewTtsVoice,
168                                              base::Unretained(this)));
169   web_ui()->RegisterMessageCallback(
170       "wakeTtsEngine",
171       base::BindRepeating(&TtsHandler::WakeTtsEngine, base::Unretained(this)));
172 }
173 
OnJavascriptAllowed()174 void TtsHandler::OnJavascriptAllowed() {
175   content::TtsController::GetInstance()->AddVoicesChangedDelegate(this);
176 }
177 
OnJavascriptDisallowed()178 void TtsHandler::OnJavascriptDisallowed() {
179   content::TtsController::GetInstance()->RemoveVoicesChangedDelegate(this);
180   content::TtsController::GetInstance()->RemoveUtteranceEventDelegate(this);
181 }
182 
GetVoiceLangMatchScore(const content::VoiceData * voice,const std::string & app_locale)183 int TtsHandler::GetVoiceLangMatchScore(const content::VoiceData* voice,
184                                        const std::string& app_locale) {
185   if (voice->lang.empty() || app_locale.empty())
186     return 0;
187   if (voice->lang == app_locale)
188     return 2;
189   return l10n_util::GetLanguage(voice->lang) ==
190                  l10n_util::GetLanguage(app_locale)
191              ? 1
192              : 0;
193 }
194 
WakeTtsEngine(const base::ListValue * args)195 void TtsHandler::WakeTtsEngine(const base::ListValue* args) {
196   Profile* profile = Profile::FromWebUI(web_ui());
197   TtsExtensionEngine::GetInstance()->LoadBuiltInTtsEngine(profile);
198   extensions::ProcessManager::Get(profile)->WakeEventPage(
199       extension_misc::kGoogleSpeechSynthesisExtensionId,
200       base::BindOnce(&TtsHandler::OnTtsEngineAwake,
201                      weak_factory_.GetWeakPtr()));
202 }
203 
OnTtsEngineAwake(bool success)204 void TtsHandler::OnTtsEngineAwake(bool success) {
205   OnVoicesChanged();
206 }
207 
208 }  // namespace settings
209