Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / speech / chrome_speech_recognition_manager_delegate.cc
blobd1251ac97f862ff453ec6d01824cdf20995b50b8
1 // Copyright (c) 2012 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/speech/chrome_speech_recognition_manager_delegate.h"
7 #include <set>
8 #include <string>
10 #include "base/bind.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/synchronization/lock.h"
14 #include "base/threading/thread_restrictions.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/profiles/profile_manager.h"
17 #include "chrome/browser/tab_contents/tab_util.h"
18 #include "chrome/common/pref_names.h"
19 #include "chrome/common/url_constants.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "content/public/browser/render_process_host.h"
22 #include "content/public/browser/render_view_host.h"
23 #include "content/public/browser/resource_context.h"
24 #include "content/public/browser/speech_recognition_manager.h"
25 #include "content/public/browser/speech_recognition_session_config.h"
26 #include "content/public/browser/speech_recognition_session_context.h"
27 #include "content/public/browser/web_contents.h"
28 #include "content/public/browser/web_contents_observer.h"
29 #include "content/public/common/speech_recognition_error.h"
30 #include "content/public/common/speech_recognition_result.h"
31 #include "net/url_request/url_request_context_getter.h"
33 #if defined(OS_WIN)
34 #include "chrome/installer/util/wmi.h"
35 #endif
37 #if defined(ENABLE_EXTENSIONS)
38 #include "chrome/browser/extensions/extension_service.h"
39 #include "extensions/browser/view_type_utils.h"
40 #endif
42 using content::BrowserThread;
43 using content::SpeechRecognitionManager;
44 using content::WebContents;
46 namespace speech {
48 namespace {
50 void TabClosedCallbackOnIOThread(int render_process_id, int render_view_id) {
51 DCHECK_CURRENTLY_ON(BrowserThread::IO);
53 SpeechRecognitionManager* manager = SpeechRecognitionManager::GetInstance();
54 // |manager| becomes NULL if a browser shutdown happens between the post of
55 // this task (from the UI thread) and this call (on the IO thread). In this
56 // case we just return.
57 if (!manager)
58 return;
60 manager->AbortAllSessionsForRenderView(render_process_id, render_view_id);
63 } // namespace
66 // Asynchronously fetches the PC and audio hardware/driver info if
67 // the user has opted into UMA. This information is sent with speech input
68 // requests to the server for identifying and improving quality issues with
69 // specific device configurations.
70 class ChromeSpeechRecognitionManagerDelegate::OptionalRequestInfo
71 : public base::RefCountedThreadSafe<OptionalRequestInfo> {
72 public:
73 OptionalRequestInfo() : can_report_metrics_(false) {
76 void Refresh() {
77 DCHECK_CURRENTLY_ON(BrowserThread::IO);
78 // UMA opt-in can be checked only from the UI thread, so switch to that.
79 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
80 base::Bind(&OptionalRequestInfo::CheckUMAAndGetHardwareInfo, this));
83 void CheckUMAAndGetHardwareInfo() {
84 DCHECK_CURRENTLY_ON(BrowserThread::UI);
85 // prefs::kMetricsReportingEnabled is not registered for OS_CHROMEOS.
86 #if !defined(OS_CHROMEOS)
87 if (g_browser_process->local_state()->GetBoolean(
88 prefs::kMetricsReportingEnabled)) {
89 // Access potentially slow OS calls from the FILE thread.
90 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
91 base::Bind(&OptionalRequestInfo::GetHardwareInfo, this));
93 #endif
96 void GetHardwareInfo() {
97 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
98 base::AutoLock lock(lock_);
99 can_report_metrics_ = true;
100 base::string16 device_model =
101 SpeechRecognitionManager::GetInstance()->GetAudioInputDeviceModel();
102 #if defined(OS_WIN)
103 value_ = base::UTF16ToUTF8(
104 installer::WMIComputerSystem::GetModel() + L"|" + device_model);
105 #else // defined(OS_WIN)
106 value_ = base::UTF16ToUTF8(device_model);
107 #endif // defined(OS_WIN)
110 std::string value() {
111 base::AutoLock lock(lock_);
112 return value_;
115 bool can_report_metrics() {
116 base::AutoLock lock(lock_);
117 return can_report_metrics_;
120 private:
121 friend class base::RefCountedThreadSafe<OptionalRequestInfo>;
123 ~OptionalRequestInfo() {}
125 base::Lock lock_;
126 std::string value_;
127 bool can_report_metrics_;
129 DISALLOW_COPY_AND_ASSIGN(OptionalRequestInfo);
132 // Simple utility to get notified when a WebContent (a tab or an extension's
133 // background page) is closed or crashes. The callback will always be called on
134 // the UI thread.
135 // There is no restriction on the constructor, however this class must be
136 // destroyed on the UI thread, due to the NotificationRegistrar dependency.
137 class ChromeSpeechRecognitionManagerDelegate::TabWatcher
138 : public base::RefCountedThreadSafe<TabWatcher> {
139 public:
140 typedef base::Callback<void(int render_process_id, int render_view_id)>
141 TabClosedCallback;
143 explicit TabWatcher(TabClosedCallback tab_closed_callback)
144 : tab_closed_callback_(tab_closed_callback) {
147 // Starts monitoring the WebContents corresponding to the given
148 // |render_process_id|, |render_view_id| pair, invoking |tab_closed_callback_|
149 // if closed/unloaded.
150 void Watch(int render_process_id, int render_view_id) {
151 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
152 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
153 &TabWatcher::Watch, this, render_process_id, render_view_id));
154 return;
157 WebContents* web_contents = tab_util::GetWebContentsByID(render_process_id,
158 render_view_id);
159 // Sessions initiated by speech input extension APIs will end up in a NULL
160 // WebContent here, but they are properly managed by the
161 // chrome::SpeechInputExtensionManager. However, sessions initiated within a
162 // extension using the (new) speech JS APIs, will be properly handled here.
163 // TODO(primiano) turn this line into a DCHECK once speech input extension
164 // API is deprecated.
165 if (!web_contents)
166 return;
168 // Avoid multiple registrations for the same |web_contents|.
169 if (FindWebContents(web_contents) != registered_web_contents_.end())
170 return;
172 registered_web_contents_.push_back(new WebContentsTracker(
173 web_contents, base::Bind(&TabWatcher::OnTabClosed,
174 // |this| outlives WebContentsTracker.
175 base::Unretained(this), web_contents),
176 render_process_id, render_view_id));
179 void OnTabClosed(content::WebContents* web_contents) {
180 ScopedVector<WebContentsTracker>::iterator iter =
181 FindWebContents(web_contents);
182 DCHECK(iter != registered_web_contents_.end());
183 int render_process_id = (*iter)->render_process_id();
184 int render_view_id = (*iter)->render_view_id();
185 registered_web_contents_.erase(iter);
187 tab_closed_callback_.Run(render_process_id, render_view_id);
190 private:
191 class WebContentsTracker : public content::WebContentsObserver {
192 public:
193 WebContentsTracker(content::WebContents* web_contents,
194 const base::Closure& finished_callback,
195 int render_process_id,
196 int render_view_id)
197 : content::WebContentsObserver(web_contents),
198 web_contents_(web_contents),
199 finished_callback_(finished_callback),
200 render_process_id_(render_process_id),
201 render_view_id_(render_view_id) {}
203 ~WebContentsTracker() override {}
205 int render_process_id() const { return render_process_id_; }
206 int render_view_id() const { return render_view_id_; }
207 const content::WebContents* GetWebContents() const { return web_contents_; }
209 private:
210 // content::WebContentsObserver overrides.
211 void WebContentsDestroyed() override {
212 Observe(nullptr);
213 finished_callback_.Run();
214 // NOTE: We are deleted now.
216 void RenderViewHostChanged(content::RenderViewHost* old_host,
217 content::RenderViewHost* new_host) override {
218 Observe(nullptr);
219 finished_callback_.Run();
220 // NOTE: We are deleted now.
223 // Raw pointer to our WebContents.
225 // Although we are a WebContentsObserver, calling
226 // WebContents::web_contents() would return NULL once we unregister
227 // ourselves in WebContentsDestroyed() or RenderViewHostChanged(). So we
228 // store a reference to perform cleanup.
229 const content::WebContents* const web_contents_;
230 const base::Closure finished_callback_;
231 const int render_process_id_;
232 const int render_view_id_;
235 friend class base::RefCountedThreadSafe<TabWatcher>;
237 ~TabWatcher() {
238 // Must be destroyed on the UI thread due to |registrar_| non thread-safety.
239 // TODO(lazyboy): Do we still need this?
240 DCHECK_CURRENTLY_ON(BrowserThread::UI);
243 // Helper function to find the iterator in |registered_web_contents_| which
244 // contains |web_contents|.
245 ScopedVector<WebContentsTracker>::iterator FindWebContents(
246 content::WebContents* web_contents) {
247 for (ScopedVector<WebContentsTracker>::iterator i(
248 registered_web_contents_.begin());
249 i != registered_web_contents_.end(); ++i) {
250 if ((*i)->GetWebContents() == web_contents)
251 return i;
254 return registered_web_contents_.end();
257 // Keeps track of which WebContent(s) have been registered, in order to avoid
258 // double registrations on WebContentsObserver and to pass the correct render
259 // process id and render view id to |tab_closed_callback_| after the process
260 // has gone away.
261 ScopedVector<WebContentsTracker> registered_web_contents_;
263 // Callback used to notify, on the thread specified by |callback_thread_| the
264 // closure of a registered tab.
265 TabClosedCallback tab_closed_callback_;
267 DISALLOW_COPY_AND_ASSIGN(TabWatcher);
270 ChromeSpeechRecognitionManagerDelegate
271 ::ChromeSpeechRecognitionManagerDelegate() {
274 ChromeSpeechRecognitionManagerDelegate
275 ::~ChromeSpeechRecognitionManagerDelegate() {
278 void ChromeSpeechRecognitionManagerDelegate::TabClosedCallback(
279 int render_process_id, int render_view_id) {
280 DCHECK_CURRENTLY_ON(BrowserThread::UI);
282 // Tell the S.R. Manager (which lives on the IO thread) to abort all the
283 // sessions for the given renderer view.
284 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
285 &TabClosedCallbackOnIOThread, render_process_id, render_view_id));
288 void ChromeSpeechRecognitionManagerDelegate::OnRecognitionStart(
289 int session_id) {
290 const content::SpeechRecognitionSessionContext& context =
291 SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
293 // Register callback to auto abort session on tab closure.
294 // |tab_watcher_| is lazyly istantiated on the first call.
295 if (!tab_watcher_.get()) {
296 tab_watcher_ = new TabWatcher(
297 base::Bind(&ChromeSpeechRecognitionManagerDelegate::TabClosedCallback,
298 base::Unretained(this)));
300 tab_watcher_->Watch(context.render_process_id, context.render_view_id);
303 void ChromeSpeechRecognitionManagerDelegate::OnAudioStart(int session_id) {
306 void ChromeSpeechRecognitionManagerDelegate::OnEnvironmentEstimationComplete(
307 int session_id) {
310 void ChromeSpeechRecognitionManagerDelegate::OnSoundStart(int session_id) {
313 void ChromeSpeechRecognitionManagerDelegate::OnSoundEnd(int session_id) {
316 void ChromeSpeechRecognitionManagerDelegate::OnAudioEnd(int session_id) {
319 void ChromeSpeechRecognitionManagerDelegate::OnRecognitionResults(
320 int session_id, const content::SpeechRecognitionResults& result) {
323 void ChromeSpeechRecognitionManagerDelegate::OnRecognitionError(
324 int session_id, const content::SpeechRecognitionError& error) {
327 void ChromeSpeechRecognitionManagerDelegate::OnAudioLevelsChange(
328 int session_id, float volume, float noise_volume) {
331 void ChromeSpeechRecognitionManagerDelegate::OnRecognitionEnd(int session_id) {
334 void ChromeSpeechRecognitionManagerDelegate::GetDiagnosticInformation(
335 bool* can_report_metrics,
336 std::string* hardware_info) {
337 DCHECK_CURRENTLY_ON(BrowserThread::IO);
338 if (!optional_request_info_.get()) {
339 optional_request_info_ = new OptionalRequestInfo();
340 // Since hardware info is optional with speech input requests, we start an
341 // asynchronous fetch here and move on with recording audio. This first
342 // speech input request would send an empty string for hardware info and
343 // subsequent requests may have the hardware info available if the fetch
344 // completed before them. This way we don't end up stalling the user with
345 // a long wait and disk seeks when they click on a UI element and start
346 // speaking.
347 optional_request_info_->Refresh();
349 *can_report_metrics = optional_request_info_->can_report_metrics();
350 *hardware_info = optional_request_info_->value();
353 void ChromeSpeechRecognitionManagerDelegate::CheckRecognitionIsAllowed(
354 int session_id,
355 base::Callback<void(bool ask_user, bool is_allowed)> callback) {
356 DCHECK_CURRENTLY_ON(BrowserThread::IO);
358 const content::SpeechRecognitionSessionContext& context =
359 SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
361 // Make sure that initiators (extensions/web pages) properly set the
362 // |render_process_id| field, which is needed later to retrieve the profile.
363 DCHECK_NE(context.render_process_id, 0);
365 int render_process_id = context.render_process_id;
366 int render_view_id = context.render_view_id;
367 if (context.embedder_render_process_id) {
368 // If this is a request originated from a guest, we need to re-route the
369 // permission check through the embedder (app).
370 render_process_id = context.embedder_render_process_id;
371 render_view_id = context.embedder_render_view_id;
374 // Check that the render view type is appropriate, and whether or not we
375 // need to request permission from the user.
376 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
377 base::Bind(&CheckRenderViewType,
378 callback,
379 render_process_id,
380 render_view_id));
383 content::SpeechRecognitionEventListener*
384 ChromeSpeechRecognitionManagerDelegate::GetEventListener() {
385 return this;
388 bool ChromeSpeechRecognitionManagerDelegate::FilterProfanities(
389 int render_process_id) {
390 content::RenderProcessHost* rph =
391 content::RenderProcessHost::FromID(render_process_id);
392 if (!rph) // Guard against race conditions on RPH lifetime.
393 return true;
395 return Profile::FromBrowserContext(rph->GetBrowserContext())->GetPrefs()->
396 GetBoolean(prefs::kSpeechRecognitionFilterProfanities);
399 // static.
400 void ChromeSpeechRecognitionManagerDelegate::CheckRenderViewType(
401 base::Callback<void(bool ask_user, bool is_allowed)> callback,
402 int render_process_id,
403 int render_view_id) {
404 DCHECK_CURRENTLY_ON(BrowserThread::UI);
405 const content::RenderViewHost* render_view_host =
406 content::RenderViewHost::FromID(render_process_id, render_view_id);
408 bool allowed = false;
409 bool check_permission = false;
411 if (!render_view_host) {
412 // This happens for extensions. Manifest should be checked for permission.
413 allowed = true;
414 check_permission = false;
415 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
416 base::Bind(callback, check_permission, allowed));
417 return;
420 #if defined(ENABLE_EXTENSIONS)
421 WebContents* web_contents = WebContents::FromRenderViewHost(render_view_host);
422 extensions::ViewType view_type = extensions::GetViewType(web_contents);
424 if (view_type == extensions::VIEW_TYPE_TAB_CONTENTS ||
425 view_type == extensions::VIEW_TYPE_APP_WINDOW ||
426 view_type == extensions::VIEW_TYPE_LAUNCHER_PAGE ||
427 view_type == extensions::VIEW_TYPE_VIRTUAL_KEYBOARD ||
428 view_type == extensions::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE) {
429 // If it is a tab, we can check for permission. For apps, this means
430 // manifest would be checked for permission.
431 allowed = true;
432 check_permission = true;
434 #else
435 // Otherwise this should be a regular tab contents.
436 allowed = true;
437 check_permission = true;
438 #endif
440 BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
441 base::Bind(callback, check_permission, allowed));
444 } // namespace speech