start_page_service.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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  virtual bool CheckMediaAccessPermission(
92      content::WebContents* web_contents,
93      const GURL& security_origin,
94      content::MediaStreamType type) OVERRIDE {
95    return MediaCaptureDevicesDispatcher::GetInstance()
96        ->CheckMediaAccessPermission(web_contents, security_origin, type);
97  }
98
99 private:
100  DISALLOW_COPY_AND_ASSIGN(StartPageWebContentsDelegate);
101};
102
103// static
104StartPageService* StartPageService::Get(Profile* profile) {
105  return StartPageServiceFactory::GetForProfile(profile);
106}
107
108StartPageService::StartPageService(Profile* profile)
109    : profile_(profile),
110      profile_destroy_observer_(new ProfileDestroyObserver(this)),
111      recommended_apps_(new RecommendedApps(profile)),
112      state_(app_list::SPEECH_RECOGNITION_OFF),
113      speech_button_toggled_manually_(false),
114      speech_result_obtained_(false) {
115  // If experimental hotwording is enabled, then we're always "ready".
116  // Transitioning into the "hotword recognizing" state is handled by the
117  // hotword extension.
118  if (HotwordService::IsExperimentalHotwordingEnabled())
119    state_ = app_list::SPEECH_RECOGNITION_READY;
120
121  if (app_list::switches::IsExperimentalAppListEnabled())
122    LoadContents();
123}
124
125StartPageService::~StartPageService() {}
126
127void StartPageService::AddObserver(StartPageObserver* observer) {
128  observers_.AddObserver(observer);
129}
130
131void StartPageService::RemoveObserver(StartPageObserver* observer) {
132  observers_.RemoveObserver(observer);
133}
134
135void StartPageService::AppListShown() {
136  if (!contents_) {
137    LoadContents();
138  } else {
139    // If experimental hotwording is enabled, don't enable hotwording in the
140    // start page, since the hotword extension is taking care of this.
141    bool hotword_enabled = HotwordEnabled() &&
142        !HotwordService::IsExperimentalHotwordingEnabled();
143    contents_->GetWebUI()->CallJavascriptFunction(
144        "appList.startPage.onAppListShown",
145        base::FundamentalValue(hotword_enabled));
146  }
147}
148
149void StartPageService::AppListHidden() {
150  contents_->GetWebUI()->CallJavascriptFunction(
151      "appList.startPage.onAppListHidden");
152  if (!app_list::switches::IsExperimentalAppListEnabled())
153    UnloadContents();
154}
155
156void StartPageService::ToggleSpeechRecognition() {
157  speech_button_toggled_manually_ = true;
158  contents_->GetWebUI()->CallJavascriptFunction(
159      "appList.startPage.toggleSpeechRecognition");
160}
161
162bool StartPageService::HotwordEnabled() {
163  if (HotwordService::IsExperimentalHotwordingEnabled()) {
164    return HotwordServiceFactory::IsServiceAvailable(profile_) &&
165        profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled);
166  }
167#if defined(OS_CHROMEOS)
168  return HotwordServiceFactory::IsServiceAvailable(profile_) &&
169      profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled);
170#else
171  return false;
172#endif
173}
174
175content::WebContents* StartPageService::GetStartPageContents() {
176  return app_list::switches::IsExperimentalAppListEnabled() ? contents_.get()
177                                                            : NULL;
178}
179
180content::WebContents* StartPageService::GetSpeechRecognitionContents() {
181  if (app_list::switches::IsVoiceSearchEnabled()) {
182    if (!contents_)
183      LoadContents();
184    return contents_.get();
185  }
186  return NULL;
187}
188
189void StartPageService::OnSpeechResult(
190    const base::string16& query, bool is_final) {
191  if (is_final) {
192    speech_result_obtained_ = true;
193    RecordAction(UserMetricsAction("AppList_SearchedBySpeech"));
194  }
195  FOR_EACH_OBSERVER(StartPageObserver,
196                    observers_,
197                    OnSpeechResult(query, is_final));
198}
199
200void StartPageService::OnSpeechSoundLevelChanged(int16 level) {
201  FOR_EACH_OBSERVER(StartPageObserver,
202                    observers_,
203                    OnSpeechSoundLevelChanged(level));
204}
205
206void StartPageService::OnSpeechRecognitionStateChanged(
207    SpeechRecognitionState new_state) {
208  if (!InSpeechRecognition(state_) && InSpeechRecognition(new_state)) {
209    if (!speech_button_toggled_manually_ &&
210        state_ == SPEECH_RECOGNITION_HOTWORD_LISTENING) {
211      RecordAction(UserMetricsAction("AppList_HotwordRecognized"));
212    } else {
213      RecordAction(UserMetricsAction("AppList_VoiceSearchStartedManually"));
214    }
215  } else if (InSpeechRecognition(state_) && !InSpeechRecognition(new_state) &&
216             !speech_result_obtained_) {
217    RecordAction(UserMetricsAction("AppList_VoiceSearchCanceled"));
218  }
219  speech_button_toggled_manually_ = false;
220  speech_result_obtained_ = false;
221  state_ = new_state;
222  FOR_EACH_OBSERVER(StartPageObserver,
223                    observers_,
224                    OnSpeechRecognitionStateChanged(new_state));
225}
226
227void StartPageService::Shutdown() {
228  UnloadContents();
229}
230
231void StartPageService::LoadContents() {
232  contents_.reset(content::WebContents::Create(
233      content::WebContents::CreateParams(profile_)));
234  contents_delegate_.reset(new StartPageWebContentsDelegate());
235  contents_->SetDelegate(contents_delegate_.get());
236
237  GURL url(chrome::kChromeUIAppListStartPageURL);
238  CommandLine* command_line = CommandLine::ForCurrentProcess();
239  if (command_line->HasSwitch(::switches::kAppListStartPageURL)) {
240    url = GURL(
241        command_line->GetSwitchValueASCII(::switches::kAppListStartPageURL));
242  }
243
244  contents_->GetController().LoadURL(
245      url,
246      content::Referrer(),
247      ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
248      std::string());
249}
250
251void StartPageService::UnloadContents() {
252  contents_.reset();
253}
254
255}  // namespace app_list
256