#include <string>
+#include "base/bind.h"
#include "base/command_line.h"
#include "base/memory/singleton.h"
#include "base/metrics/user_metrics.h"
+#include "base/prefs/pref_service.h"
+#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/media/media_stream_infobar_delegate.h"
#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/app_list/app_list_service.h"
+#include "chrome/browser/search/hotword_service.h"
+#include "chrome/browser/search/hotword_service_factory.h"
#include "chrome/browser/ui/app_list/recommended_apps.h"
+#include "chrome/browser/ui/app_list/speech_recognizer.h"
#include "chrome/browser/ui/app_list/start_page_observer.h"
#include "chrome/browser/ui/app_list/start_page_service_factory.h"
#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_observer.h"
chrome::NOTIFICATION_PROFILE_DESTROYED,
content::Source<Profile>(service_->profile()));
}
- virtual ~ProfileDestroyObserver() {}
+ ~ProfileDestroyObserver() override {}
private:
// content::NotificationObserver
- virtual void Observe(int type,
- const content::NotificationSource& source,
- const content::NotificationDetails& details) OVERRIDE {
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override {
DCHECK_EQ(chrome::NOTIFICATION_PROFILE_DESTROYED, type);
DCHECK_EQ(service_->profile(), content::Source<Profile>(source).ptr());
service_->Shutdown();
: public content::WebContentsDelegate {
public:
StartPageWebContentsDelegate() {}
- virtual ~StartPageWebContentsDelegate() {}
+ ~StartPageWebContentsDelegate() override {}
- virtual void RequestMediaAccessPermission(
+ void RequestMediaAccessPermission(
content::WebContents* web_contents,
const content::MediaStreamRequest& request,
- const content::MediaResponseCallback& callback) OVERRIDE {
+ const content::MediaResponseCallback& callback) override {
if (MediaStreamInfoBarDelegate::Create(web_contents, request, callback))
NOTREACHED() << "Media stream not allowed for WebUI";
}
+ bool CheckMediaAccessPermission(content::WebContents* web_contents,
+ const GURL& security_origin,
+ content::MediaStreamType type) override {
+ return MediaCaptureDevicesDispatcher::GetInstance()
+ ->CheckMediaAccessPermission(web_contents, security_origin, type);
+ }
+
private:
DISALLOW_COPY_AND_ASSIGN(StartPageWebContentsDelegate);
};
recommended_apps_(new RecommendedApps(profile)),
state_(app_list::SPEECH_RECOGNITION_OFF),
speech_button_toggled_manually_(false),
- speech_result_obtained_(false) {
-#if defined(OS_CHROMEOS)
- // Updates the default state to hotword listening, because this is
- // the default behavior. This will be updated when the page is loaded and
- // the nacl module is loaded.
- if (app_list::switches::IsVoiceSearchEnabled())
- state_ = app_list::SPEECH_RECOGNITION_HOTWORD_LISTENING;
-#endif
-
- contents_.reset(content::WebContents::Create(
- content::WebContents::CreateParams(profile_)));
- contents_delegate_.reset(new StartPageWebContentsDelegate());
- contents_->SetDelegate(contents_delegate_.get());
-
- GURL url(chrome::kChromeUIAppListStartPageURL);
- CommandLine* command_line = CommandLine::ForCurrentProcess();
- if (command_line->HasSwitch(::switches::kAppListStartPageURL)) {
- url = GURL(
- command_line->GetSwitchValueASCII(::switches::kAppListStartPageURL));
- }
-
- contents_->GetController().LoadURL(
- url,
- content::Referrer(),
- content::PAGE_TRANSITION_AUTO_TOPLEVEL,
- std::string());
+ speech_result_obtained_(false),
+ webui_finished_loading_(false),
+ weak_factory_(this) {
+ // If experimental hotwording is enabled, then we're always "ready".
+ // Transitioning into the "hotword recognizing" state is handled by the
+ // hotword extension.
+ if (HotwordService::IsExperimentalHotwordingEnabled())
+ state_ = app_list::SPEECH_RECOGNITION_READY;
+
+ if (app_list::switches::IsExperimentalAppListEnabled())
+ LoadContents();
}
StartPageService::~StartPageService() {}
observers_.RemoveObserver(observer);
}
+void StartPageService::AppListShown() {
+ if (!contents_) {
+ LoadContents();
+ } else if (contents_->GetWebUI() &&
+ !HotwordService::IsExperimentalHotwordingEnabled()) {
+ // If experimental hotwording is enabled, don't call onAppListShown.
+ // onAppListShown() initializes the web speech API, which is not used with
+ // experimental hotwording.
+ contents_->GetWebUI()->CallJavascriptFunction(
+ "appList.startPage.onAppListShown",
+ base::FundamentalValue(HotwordEnabled()));
+ }
+}
+
+void StartPageService::AppListHidden() {
+ if (contents_->GetWebUI()) {
+ contents_->GetWebUI()->CallJavascriptFunction(
+ "appList.startPage.onAppListHidden");
+ }
+ if (!app_list::switches::IsExperimentalAppListEnabled())
+ UnloadContents();
+
+ if (HotwordService::IsExperimentalHotwordingEnabled() &&
+ speech_recognizer_) {
+ speech_recognizer_->Stop();
+ }
+}
+
void StartPageService::ToggleSpeechRecognition() {
+ DCHECK(contents_);
speech_button_toggled_manually_ = true;
+ if (!contents_->GetWebUI())
+ return;
+
+ if (!webui_finished_loading_) {
+ pending_webui_callbacks_.push_back(
+ base::Bind(&StartPageService::ToggleSpeechRecognition,
+ base::Unretained(this)));
+ return;
+ }
+
+ if (HotwordService::IsExperimentalHotwordingEnabled()) {
+ if (!speech_recognizer_) {
+ std::string profile_locale;
+#if defined(OS_CHROMEOS)
+ profile_locale = profile_->GetPrefs()->GetString(
+ prefs::kApplicationLocale);
+#endif
+ if (profile_locale.empty())
+ profile_locale = g_browser_process->GetApplicationLocale();
+
+ speech_recognizer_.reset(
+ new SpeechRecognizer(weak_factory_.GetWeakPtr(),
+ profile_->GetRequestContext(),
+ profile_locale));
+ }
+
+ speech_recognizer_->Start();
+ return;
+ }
+
contents_->GetWebUI()->CallJavascriptFunction(
"appList.startPage.toggleSpeechRecognition");
}
+bool StartPageService::HotwordEnabled() {
+ if (HotwordService::IsExperimentalHotwordingEnabled()) {
+ auto prefs = profile_->GetPrefs();
+ return HotwordServiceFactory::IsServiceAvailable(profile_) &&
+ (prefs->GetBoolean(prefs::kHotwordSearchEnabled) ||
+ prefs->GetBoolean(prefs::kHotwordAlwaysOnSearchEnabled));
+ }
+#if defined(OS_CHROMEOS)
+ return HotwordServiceFactory::IsServiceAvailable(profile_) &&
+ profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled);
+#else
+ return false;
+#endif
+}
+
content::WebContents* StartPageService::GetStartPageContents() {
return app_list::switches::IsExperimentalAppListEnabled() ? contents_.get()
: NULL;
}
content::WebContents* StartPageService::GetSpeechRecognitionContents() {
- return app_list::switches::IsVoiceSearchEnabled() ? contents_.get() : NULL;
+ if (app_list::switches::IsVoiceSearchEnabled()) {
+ if (!contents_)
+ LoadContents();
+ return contents_.get();
+ }
+ return NULL;
}
void StartPageService::OnSpeechResult(
OnSpeechResult(query, is_final));
}
-void StartPageService::OnSpeechSoundLevelChanged(int16 level) {
+void StartPageService::OnSpeechSoundLevelChanged(int16_t level) {
FOR_EACH_OBSERVER(StartPageObserver,
observers_,
OnSpeechSoundLevelChanged(level));
void StartPageService::OnSpeechRecognitionStateChanged(
SpeechRecognitionState new_state) {
- SpeechRecognitionState old_state = state_;
- state_ = new_state;
- if (!InSpeechRecognition(old_state) && InSpeechRecognition(new_state)) {
+ if (HotwordService::IsExperimentalHotwordingEnabled() &&
+ new_state == SPEECH_RECOGNITION_READY &&
+ speech_recognizer_) {
+ speech_recognizer_->Stop();
+ }
+
+ if (!InSpeechRecognition(state_) && InSpeechRecognition(new_state)) {
if (!speech_button_toggled_manually_ &&
- old_state == SPEECH_RECOGNITION_HOTWORD_LISTENING) {
+ state_ == SPEECH_RECOGNITION_HOTWORD_LISTENING) {
RecordAction(UserMetricsAction("AppList_HotwordRecognized"));
- AppListService* app_list_service =
- AppListService::Get(chrome::GetActiveDesktop());
- if (!app_list_service->IsAppListVisible())
- app_list_service->Show();
} else {
RecordAction(UserMetricsAction("AppList_VoiceSearchStartedManually"));
}
- } else if (InSpeechRecognition(old_state) &&
- !InSpeechRecognition(new_state) &&
+ } else if (InSpeechRecognition(state_) && !InSpeechRecognition(new_state) &&
!speech_result_obtained_) {
RecordAction(UserMetricsAction("AppList_VoiceSearchCanceled"));
}
speech_button_toggled_manually_ = false;
speech_result_obtained_ = false;
-
+ state_ = new_state;
FOR_EACH_OBSERVER(StartPageObserver,
observers_,
OnSpeechRecognitionStateChanged(new_state));
}
+content::WebContents* StartPageService::GetSpeechContents() {
+ return GetSpeechRecognitionContents();
+}
+
void StartPageService::Shutdown() {
+ UnloadContents();
+}
+
+void StartPageService::WebUILoaded() {
+ // There's a race condition between the WebUI loading, and calling its JS
+ // functions. Specifically, calling LoadContents() doesn't mean that the page
+ // has loaded, but several code paths make this assumption. This function
+ // allows us to defer calling JS functions until after the page has finished
+ // loading.
+ webui_finished_loading_ = true;
+ for (const auto& cb : pending_webui_callbacks_)
+ cb.Run();
+ pending_webui_callbacks_.clear();
+}
+
+void StartPageService::LoadContents() {
+ contents_.reset(content::WebContents::Create(
+ content::WebContents::CreateParams(profile_)));
+ contents_delegate_.reset(new StartPageWebContentsDelegate());
+ contents_->SetDelegate(contents_delegate_.get());
+
+ GURL url(chrome::kChromeUIAppListStartPageURL);
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ if (command_line->HasSwitch(::switches::kAppListStartPageURL)) {
+ url = GURL(
+ command_line->GetSwitchValueASCII(::switches::kAppListStartPageURL));
+ }
+
+ contents_->GetController().LoadURL(
+ url,
+ content::Referrer(),
+ ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
+ std::string());
+}
+
+void StartPageService::UnloadContents() {
contents_.reset();
+ webui_finished_loading_ = false;
}
} // namespace app_list