1 // Copyright 2013 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.
5 #include "chrome/browser/ui/app_list/start_page_service.h"
10 #include "base/command_line.h"
11 #include "base/memory/singleton.h"
12 #include "base/metrics/user_metrics.h"
13 #include "base/prefs/pref_service.h"
14 #include "chrome/browser/browser_process.h"
15 #include "chrome/browser/chrome_notification_types.h"
16 #include "chrome/browser/media/media_stream_infobar_delegate.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/search/hotword_service.h"
19 #include "chrome/browser/search/hotword_service_factory.h"
20 #include "chrome/browser/ui/app_list/recommended_apps.h"
21 #include "chrome/browser/ui/app_list/speech_recognizer.h"
22 #include "chrome/browser/ui/app_list/start_page_observer.h"
23 #include "chrome/browser/ui/app_list/start_page_service_factory.h"
24 #include "chrome/common/chrome_switches.h"
25 #include "chrome/common/pref_names.h"
26 #include "chrome/common/url_constants.h"
27 #include "content/public/browser/notification_details.h"
28 #include "content/public/browser/notification_observer.h"
29 #include "content/public/browser/notification_registrar.h"
30 #include "content/public/browser/notification_service.h"
31 #include "content/public/browser/notification_source.h"
32 #include "content/public/browser/web_contents.h"
33 #include "content/public/browser/web_contents_delegate.h"
34 #include "extensions/browser/extension_system_provider.h"
35 #include "extensions/browser/extensions_browser_client.h"
36 #include "extensions/common/extension.h"
37 #include "ui/app_list/app_list_switches.h"
39 using base::RecordAction;
40 using base::UserMetricsAction;
46 bool InSpeechRecognition(SpeechRecognitionState state) {
47 return state == SPEECH_RECOGNITION_RECOGNIZING ||
48 state == SPEECH_RECOGNITION_IN_SPEECH;
53 class StartPageService::ProfileDestroyObserver
54 : public content::NotificationObserver {
56 explicit ProfileDestroyObserver(StartPageService* service)
59 chrome::NOTIFICATION_PROFILE_DESTROYED,
60 content::Source<Profile>(service_->profile()));
62 ~ProfileDestroyObserver() override {}
65 // content::NotificationObserver
66 void Observe(int type,
67 const content::NotificationSource& source,
68 const content::NotificationDetails& details) override {
69 DCHECK_EQ(chrome::NOTIFICATION_PROFILE_DESTROYED, type);
70 DCHECK_EQ(service_->profile(), content::Source<Profile>(source).ptr());
74 StartPageService* service_; // Owner of this class.
75 content::NotificationRegistrar registrar_;
77 DISALLOW_COPY_AND_ASSIGN(ProfileDestroyObserver);
80 class StartPageService::StartPageWebContentsDelegate
81 : public content::WebContentsDelegate {
83 StartPageWebContentsDelegate() {}
84 ~StartPageWebContentsDelegate() override {}
86 void RequestMediaAccessPermission(
87 content::WebContents* web_contents,
88 const content::MediaStreamRequest& request,
89 const content::MediaResponseCallback& callback) override {
90 if (MediaStreamInfoBarDelegate::Create(web_contents, request, callback))
91 NOTREACHED() << "Media stream not allowed for WebUI";
94 bool CheckMediaAccessPermission(content::WebContents* web_contents,
95 const GURL& security_origin,
96 content::MediaStreamType type) override {
97 return MediaCaptureDevicesDispatcher::GetInstance()
98 ->CheckMediaAccessPermission(web_contents, security_origin, type);
102 DISALLOW_COPY_AND_ASSIGN(StartPageWebContentsDelegate);
106 StartPageService* StartPageService::Get(Profile* profile) {
107 return StartPageServiceFactory::GetForProfile(profile);
110 StartPageService::StartPageService(Profile* profile)
112 profile_destroy_observer_(new ProfileDestroyObserver(this)),
113 recommended_apps_(new RecommendedApps(profile)),
114 state_(app_list::SPEECH_RECOGNITION_OFF),
115 speech_button_toggled_manually_(false),
116 speech_result_obtained_(false),
117 webui_finished_loading_(false),
118 weak_factory_(this) {
119 // If experimental hotwording is enabled, then we're always "ready".
120 // Transitioning into the "hotword recognizing" state is handled by the
121 // hotword extension.
122 if (HotwordService::IsExperimentalHotwordingEnabled())
123 state_ = app_list::SPEECH_RECOGNITION_READY;
125 if (app_list::switches::IsExperimentalAppListEnabled())
129 StartPageService::~StartPageService() {}
131 void StartPageService::AddObserver(StartPageObserver* observer) {
132 observers_.AddObserver(observer);
135 void StartPageService::RemoveObserver(StartPageObserver* observer) {
136 observers_.RemoveObserver(observer);
139 void StartPageService::AppListShown() {
142 } else if (contents_->GetWebUI() &&
143 !HotwordService::IsExperimentalHotwordingEnabled()) {
144 // If experimental hotwording is enabled, don't call onAppListShown.
145 // onAppListShown() initializes the web speech API, which is not used with
146 // experimental hotwording.
147 contents_->GetWebUI()->CallJavascriptFunction(
148 "appList.startPage.onAppListShown",
149 base::FundamentalValue(HotwordEnabled()));
153 void StartPageService::AppListHidden() {
154 if (contents_->GetWebUI()) {
155 contents_->GetWebUI()->CallJavascriptFunction(
156 "appList.startPage.onAppListHidden");
158 if (!app_list::switches::IsExperimentalAppListEnabled())
161 if (HotwordService::IsExperimentalHotwordingEnabled() &&
162 speech_recognizer_) {
163 speech_recognizer_->Stop();
167 void StartPageService::ToggleSpeechRecognition() {
169 speech_button_toggled_manually_ = true;
170 if (!contents_->GetWebUI())
173 if (!webui_finished_loading_) {
174 pending_webui_callbacks_.push_back(
175 base::Bind(&StartPageService::ToggleSpeechRecognition,
176 base::Unretained(this)));
180 if (HotwordService::IsExperimentalHotwordingEnabled()) {
181 if (!speech_recognizer_) {
182 std::string profile_locale;
183 #if defined(OS_CHROMEOS)
184 profile_locale = profile_->GetPrefs()->GetString(
185 prefs::kApplicationLocale);
187 if (profile_locale.empty())
188 profile_locale = g_browser_process->GetApplicationLocale();
190 speech_recognizer_.reset(
191 new SpeechRecognizer(weak_factory_.GetWeakPtr(),
192 profile_->GetRequestContext(),
196 speech_recognizer_->Start();
200 contents_->GetWebUI()->CallJavascriptFunction(
201 "appList.startPage.toggleSpeechRecognition");
204 bool StartPageService::HotwordEnabled() {
205 if (HotwordService::IsExperimentalHotwordingEnabled()) {
206 auto prefs = profile_->GetPrefs();
207 return HotwordServiceFactory::IsServiceAvailable(profile_) &&
208 (prefs->GetBoolean(prefs::kHotwordSearchEnabled) ||
209 prefs->GetBoolean(prefs::kHotwordAlwaysOnSearchEnabled));
211 #if defined(OS_CHROMEOS)
212 return HotwordServiceFactory::IsServiceAvailable(profile_) &&
213 profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled);
219 content::WebContents* StartPageService::GetStartPageContents() {
220 return app_list::switches::IsExperimentalAppListEnabled() ? contents_.get()
224 content::WebContents* StartPageService::GetSpeechRecognitionContents() {
225 if (app_list::switches::IsVoiceSearchEnabled()) {
228 return contents_.get();
233 void StartPageService::OnSpeechResult(
234 const base::string16& query, bool is_final) {
236 speech_result_obtained_ = true;
237 RecordAction(UserMetricsAction("AppList_SearchedBySpeech"));
239 FOR_EACH_OBSERVER(StartPageObserver,
241 OnSpeechResult(query, is_final));
244 void StartPageService::OnSpeechSoundLevelChanged(int16_t level) {
245 FOR_EACH_OBSERVER(StartPageObserver,
247 OnSpeechSoundLevelChanged(level));
250 void StartPageService::OnSpeechRecognitionStateChanged(
251 SpeechRecognitionState new_state) {
253 if (HotwordService::IsExperimentalHotwordingEnabled() &&
254 new_state == SPEECH_RECOGNITION_READY &&
255 speech_recognizer_) {
256 speech_recognizer_->Stop();
259 if (!InSpeechRecognition(state_) && InSpeechRecognition(new_state)) {
260 if (!speech_button_toggled_manually_ &&
261 state_ == SPEECH_RECOGNITION_HOTWORD_LISTENING) {
262 RecordAction(UserMetricsAction("AppList_HotwordRecognized"));
264 RecordAction(UserMetricsAction("AppList_VoiceSearchStartedManually"));
266 } else if (InSpeechRecognition(state_) && !InSpeechRecognition(new_state) &&
267 !speech_result_obtained_) {
268 RecordAction(UserMetricsAction("AppList_VoiceSearchCanceled"));
270 speech_button_toggled_manually_ = false;
271 speech_result_obtained_ = false;
273 FOR_EACH_OBSERVER(StartPageObserver,
275 OnSpeechRecognitionStateChanged(new_state));
278 content::WebContents* StartPageService::GetSpeechContents() {
279 return GetSpeechRecognitionContents();
282 void StartPageService::Shutdown() {
286 void StartPageService::WebUILoaded() {
287 // There's a race condition between the WebUI loading, and calling its JS
288 // functions. Specifically, calling LoadContents() doesn't mean that the page
289 // has loaded, but several code paths make this assumption. This function
290 // allows us to defer calling JS functions until after the page has finished
292 webui_finished_loading_ = true;
293 for (const auto& cb : pending_webui_callbacks_)
295 pending_webui_callbacks_.clear();
298 void StartPageService::LoadContents() {
299 contents_.reset(content::WebContents::Create(
300 content::WebContents::CreateParams(profile_)));
301 contents_delegate_.reset(new StartPageWebContentsDelegate());
302 contents_->SetDelegate(contents_delegate_.get());
304 GURL url(chrome::kChromeUIAppListStartPageURL);
305 CommandLine* command_line = CommandLine::ForCurrentProcess();
306 if (command_line->HasSwitch(::switches::kAppListStartPageURL)) {
308 command_line->GetSwitchValueASCII(::switches::kAppListStartPageURL));
311 contents_->GetController().LoadURL(
314 ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
318 void StartPageService::UnloadContents() {
320 webui_finished_loading_ = false;
323 } // namespace app_list