start_page_service.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
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.
4
5#include "chrome/browser/ui/app_list/start_page_service.h"
6
7#include <string>
8
9#include "base/command_line.h"
10#include "base/memory/singleton.h"
11#include "base/metrics/user_metrics.h"
12#include "base/prefs/pref_service.h"
13#include "chrome/browser/chrome_notification_types.h"
14#include "chrome/browser/media/media_stream_infobar_delegate.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/browser/search/hotword_service.h"
17#include "chrome/browser/search/hotword_service_factory.h"
18#include "chrome/browser/ui/app_list/recommended_apps.h"
19#include "chrome/browser/ui/app_list/start_page_observer.h"
20#include "chrome/browser/ui/app_list/start_page_service_factory.h"
21#include "chrome/common/chrome_switches.h"
22#include "chrome/common/pref_names.h"
23#include "chrome/common/url_constants.h"
24#include "content/public/browser/notification_details.h"
25#include "content/public/browser/notification_observer.h"
26#include "content/public/browser/notification_registrar.h"
27#include "content/public/browser/notification_service.h"
28#include "content/public/browser/notification_source.h"
29#include "content/public/browser/web_contents.h"
30#include "content/public/browser/web_contents_delegate.h"
31#include "extensions/browser/extension_system_provider.h"
32#include "extensions/browser/extensions_browser_client.h"
33#include "extensions/common/extension.h"
34#include "ui/app_list/app_list_switches.h"
35
36using base::RecordAction;
37using base::UserMetricsAction;
38
39namespace app_list {
40
41namespace {
42
43bool InSpeechRecognition(SpeechRecognitionState state) {
44  return state == SPEECH_RECOGNITION_RECOGNIZING ||
45      state == SPEECH_RECOGNITION_IN_SPEECH;
46}
47
48}
49
50class StartPageService::ProfileDestroyObserver
51    : public content::NotificationObserver {
52 public:
53  explicit ProfileDestroyObserver(StartPageService* service)
54      : service_(service) {
55    registrar_.Add(this,
56                   chrome::NOTIFICATION_PROFILE_DESTROYED,
57                   content::Source<Profile>(service_->profile()));
58  }
59  virtual ~ProfileDestroyObserver() {}
60
61 private:
62  // content::NotificationObserver
63  virtual void Observe(int type,
64                       const content::NotificationSource& source,
65                       const content::NotificationDetails& details) OVERRIDE {
66    DCHECK_EQ(chrome::NOTIFICATION_PROFILE_DESTROYED, type);
67    DCHECK_EQ(service_->profile(), content::Source<Profile>(source).ptr());
68    service_->Shutdown();
69  }
70
71  StartPageService* service_;  // Owner of this class.
72  content::NotificationRegistrar registrar_;
73
74  DISALLOW_COPY_AND_ASSIGN(ProfileDestroyObserver);
75};
76
77class StartPageService::StartPageWebContentsDelegate
78    : public content::WebContentsDelegate {
79 public:
80  StartPageWebContentsDelegate() {}
81  virtual ~StartPageWebContentsDelegate() {}
82
83  virtual void RequestMediaAccessPermission(
84      content::WebContents* web_contents,
85      const content::MediaStreamRequest& request,
86      const content::MediaResponseCallback& callback) OVERRIDE {
87    if (MediaStreamInfoBarDelegate::Create(web_contents, request, callback))
88      NOTREACHED() << "Media stream not allowed for WebUI";
89  }
90
91 private:
92  DISALLOW_COPY_AND_ASSIGN(StartPageWebContentsDelegate);
93};
94
95// static
96StartPageService* StartPageService::Get(Profile* profile) {
97  return StartPageServiceFactory::GetForProfile(profile);
98}
99
100StartPageService::StartPageService(Profile* profile)
101    : profile_(profile),
102      profile_destroy_observer_(new ProfileDestroyObserver(this)),
103      recommended_apps_(new RecommendedApps(profile)),
104      state_(app_list::SPEECH_RECOGNITION_OFF),
105      speech_button_toggled_manually_(false),
106      speech_result_obtained_(false) {
107  // If experimental hotwording is enabled, then we're always "ready".
108  // Transitioning into the "hotword recognizing" state is handled by the
109  // hotword extension.
110  if (HotwordService::IsExperimentalHotwordingEnabled())
111    state_ = app_list::SPEECH_RECOGNITION_READY;
112
113  if (app_list::switches::IsExperimentalAppListEnabled())
114    LoadContents();
115}
116
117StartPageService::~StartPageService() {}
118
119void StartPageService::AddObserver(StartPageObserver* observer) {
120  observers_.AddObserver(observer);
121}
122
123void StartPageService::RemoveObserver(StartPageObserver* observer) {
124  observers_.RemoveObserver(observer);
125}
126
127void StartPageService::AppListShown() {
128  if (!contents_) {
129    LoadContents();
130  } else {
131    // If experimental hotwording is enabled, don't enable hotwording in the
132    // start page, since the hotword extension is taking care of this.
133    bool hotword_enabled = HotwordEnabled() &&
134        !HotwordService::IsExperimentalHotwordingEnabled();
135    contents_->GetWebUI()->CallJavascriptFunction(
136        "appList.startPage.onAppListShown",
137        base::FundamentalValue(hotword_enabled));
138  }
139}
140
141void StartPageService::AppListHidden() {
142  contents_->GetWebUI()->CallJavascriptFunction(
143      "appList.startPage.onAppListHidden");
144  if (!app_list::switches::IsExperimentalAppListEnabled())
145    UnloadContents();
146}
147
148void StartPageService::ToggleSpeechRecognition() {
149  speech_button_toggled_manually_ = true;
150  contents_->GetWebUI()->CallJavascriptFunction(
151      "appList.startPage.toggleSpeechRecognition");
152}
153
154bool StartPageService::HotwordEnabled() {
155  if (HotwordService::IsExperimentalHotwordingEnabled()) {
156    return HotwordServiceFactory::IsServiceAvailable(profile_) &&
157        profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled);
158  }
159#if defined(OS_CHROMEOS)
160  return HotwordServiceFactory::IsServiceAvailable(profile_) &&
161      profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled);
162#else
163  return false;
164#endif
165}
166
167content::WebContents* StartPageService::GetStartPageContents() {
168  return app_list::switches::IsExperimentalAppListEnabled() ? contents_.get()
169                                                            : NULL;
170}
171
172content::WebContents* StartPageService::GetSpeechRecognitionContents() {
173  if (app_list::switches::IsVoiceSearchEnabled()) {
174    if (!contents_)
175      LoadContents();
176    return contents_.get();
177  }
178  return NULL;
179}
180
181void StartPageService::OnSpeechResult(
182    const base::string16& query, bool is_final) {
183  if (is_final) {
184    speech_result_obtained_ = true;
185    RecordAction(UserMetricsAction("AppList_SearchedBySpeech"));
186  }
187  FOR_EACH_OBSERVER(StartPageObserver,
188                    observers_,
189                    OnSpeechResult(query, is_final));
190}
191
192void StartPageService::OnSpeechSoundLevelChanged(int16 level) {
193  FOR_EACH_OBSERVER(StartPageObserver,
194                    observers_,
195                    OnSpeechSoundLevelChanged(level));
196}
197
198void StartPageService::OnSpeechRecognitionStateChanged(
199    SpeechRecognitionState new_state) {
200  if (!InSpeechRecognition(state_) && InSpeechRecognition(new_state)) {
201    if (!speech_button_toggled_manually_ &&
202        state_ == SPEECH_RECOGNITION_HOTWORD_LISTENING) {
203      RecordAction(UserMetricsAction("AppList_HotwordRecognized"));
204    } else {
205      RecordAction(UserMetricsAction("AppList_VoiceSearchStartedManually"));
206    }
207  } else if (InSpeechRecognition(state_) && !InSpeechRecognition(new_state) &&
208             !speech_result_obtained_) {
209    RecordAction(UserMetricsAction("AppList_VoiceSearchCanceled"));
210  }
211  speech_button_toggled_manually_ = false;
212  speech_result_obtained_ = false;
213  state_ = new_state;
214  FOR_EACH_OBSERVER(StartPageObserver,
215                    observers_,
216                    OnSpeechRecognitionStateChanged(new_state));
217}
218
219void StartPageService::Shutdown() {
220  UnloadContents();
221}
222
223void StartPageService::LoadContents() {
224  contents_.reset(content::WebContents::Create(
225      content::WebContents::CreateParams(profile_)));
226  contents_delegate_.reset(new StartPageWebContentsDelegate());
227  contents_->SetDelegate(contents_delegate_.get());
228
229  GURL url(chrome::kChromeUIAppListStartPageURL);
230  CommandLine* command_line = CommandLine::ForCurrentProcess();
231  if (command_line->HasSwitch(::switches::kAppListStartPageURL)) {
232    url = GURL(
233        command_line->GetSwitchValueASCII(::switches::kAppListStartPageURL));
234  }
235
236  contents_->GetController().LoadURL(
237      url,
238      content::Referrer(),
239      content::PAGE_TRANSITION_AUTO_TOPLEVEL,
240      std::string());
241}
242
243void StartPageService::UnloadContents() {
244  contents_.reset();
245}
246
247}  // namespace app_list
248