blob: ed567e1996903a9ccaecc6e84df97095d1fc9163 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/speech/speech_recognition_manager_impl.h"
#include "base/bind.h"
#include "content/browser/browser_main_loop.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/speech/input_tag_speech_dispatcher_host.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/speech_recognizer.h"
#include "content/public/browser/render_view_host_delegate.h"
#include "content/public/browser/resource_context.h"
#include "content/public/browser/speech_recognition_manager_delegate.h"
#include "content/public/browser/speech_recognition_preferences.h"
#include "content/public/common/view_type.h"
#include "media/audio/audio_manager.h"
using content::BrowserMainLoop;
using content::BrowserThread;
using content::RenderViewHostImpl;
using content::SpeechRecognitionManager;
using content::SpeechRecognitionManagerDelegate;
SpeechRecognitionManager* SpeechRecognitionManager::GetInstance() {
return speech::SpeechRecognitionManagerImpl::GetInstance();
}
namespace speech {
struct SpeechRecognitionManagerImpl::SpeechRecognitionParams {
SpeechRecognitionParams(
InputTagSpeechDispatcherHost* delegate,
int session_id,
int render_process_id,
int render_view_id,
const gfx::Rect& element_rect,
const std::string& language,
const std::string& grammar,
const std::string& origin_url,
net::URLRequestContextGetter* context_getter,
content::SpeechRecognitionPreferences* recognition_prefs)
: delegate(delegate),
session_id(session_id),
render_process_id(render_process_id),
render_view_id(render_view_id),
element_rect(element_rect),
language(language),
grammar(grammar),
origin_url(origin_url),
context_getter(context_getter),
recognition_prefs(recognition_prefs) {
}
InputTagSpeechDispatcherHost* delegate;
int session_id;
int render_process_id;
int render_view_id;
gfx::Rect element_rect;
std::string language;
std::string grammar;
std::string origin_url;
net::URLRequestContextGetter* context_getter;
content::SpeechRecognitionPreferences* recognition_prefs;
};
SpeechRecognitionManagerImpl* SpeechRecognitionManagerImpl::GetInstance() {
return Singleton<SpeechRecognitionManagerImpl>::get();
}
SpeechRecognitionManagerImpl::SpeechRecognitionManagerImpl()
: can_report_metrics_(false),
recording_session_id_(0) {
delegate_.reset(content::GetContentClient()->browser()->
GetSpeechRecognitionManagerDelegate());
}
SpeechRecognitionManagerImpl::~SpeechRecognitionManagerImpl() {
while (requests_.begin() != requests_.end())
CancelRecognition(requests_.begin()->first);
}
bool SpeechRecognitionManagerImpl::HasAudioInputDevices() {
return BrowserMainLoop::GetAudioManager()->HasAudioInputDevices();
}
bool SpeechRecognitionManagerImpl::IsCapturingAudio() {
return BrowserMainLoop::GetAudioManager()->IsRecordingInProcess();
}
string16 SpeechRecognitionManagerImpl::GetAudioInputDeviceModel() {
return BrowserMainLoop::GetAudioManager()->GetAudioInputDeviceModel();
}
bool SpeechRecognitionManagerImpl::HasPendingRequest(int session_id) const {
return requests_.find(session_id) != requests_.end();
}
InputTagSpeechDispatcherHost* SpeechRecognitionManagerImpl::GetDelegate(
int session_id) const {
return requests_.find(session_id)->second.delegate;
}
void SpeechRecognitionManagerImpl::ShowAudioInputSettings() {
// Since AudioManager::ShowAudioInputSettings can potentially launch external
// processes, do that in the FILE thread to not block the calling threads.
if (!BrowserThread::CurrentlyOn(BrowserThread::FILE)) {
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(&SpeechRecognitionManagerImpl::ShowAudioInputSettings,
base::Unretained(this)));
return;
}
media::AudioManager* audio_manager = BrowserMainLoop::GetAudioManager();
DCHECK(audio_manager->CanShowAudioInputSettings());
if (audio_manager->CanShowAudioInputSettings())
audio_manager->ShowAudioInputSettings();
}
void SpeechRecognitionManagerImpl::StartRecognition(
InputTagSpeechDispatcherHost* delegate,
int session_id,
int render_process_id,
int render_view_id,
const gfx::Rect& element_rect,
const std::string& language,
const std::string& grammar,
const std::string& origin_url,
net::URLRequestContextGetter* context_getter,
content::SpeechRecognitionPreferences* recognition_prefs) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(
&SpeechRecognitionManagerImpl::CheckRenderViewTypeAndStartRecognition,
base::Unretained(this),
SpeechRecognitionParams(
delegate, session_id, render_process_id, render_view_id,
element_rect, language, grammar, origin_url, context_getter,
recognition_prefs)));
}
void SpeechRecognitionManagerImpl::CheckRenderViewTypeAndStartRecognition(
const SpeechRecognitionParams& params) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
RenderViewHostImpl* render_view_host = RenderViewHostImpl::FromID(
params.render_process_id, params.render_view_id);
if (!render_view_host || !render_view_host->GetDelegate())
return;
// For host delegates other than VIEW_TYPE_WEB_CONTENTS we can't reliably show
// a popup, including the speech input bubble. In these cases for privacy
// reasons we don't want to start recording if the user can't be properly
// notified. An example of this is trying to show the speech input bubble
// within an extension popup: http://crbug.com/92083. In these situations the
// speech input extension API should be used instead.
if (render_view_host->GetDelegate()->GetRenderViewType() ==
content::VIEW_TYPE_WEB_CONTENTS) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&SpeechRecognitionManagerImpl::ProceedStartingRecognition,
base::Unretained(this), params));
}
}
void SpeechRecognitionManagerImpl::ProceedStartingRecognition(
const SpeechRecognitionParams& params) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(!HasPendingRequest(params.session_id));
if (delegate_.get()) {
delegate_->ShowRecognitionRequested(
params.session_id, params.render_process_id, params.render_view_id,
params.element_rect);
delegate_->GetRequestInfo(&can_report_metrics_, &request_info_);
}
Request* request = &requests_[params.session_id];
request->delegate = params.delegate;
request->recognizer = content::SpeechRecognizer::Create(
this, params.session_id, params.language, params.grammar,
params.context_getter, params.recognition_prefs->FilterProfanities(),
request_info_, can_report_metrics_ ? params.origin_url : "");
request->is_active = false;
StartRecognitionForRequest(params.session_id);
}
void SpeechRecognitionManagerImpl::StartRecognitionForRequest(int session_id) {
SpeechRecognizerMap::iterator request = requests_.find(session_id);
if (request == requests_.end()) {
NOTREACHED();
return;
}
// We should not currently be recording for the session.
CHECK(recording_session_id_ != session_id);
// If we are currently recording audio for another session, abort it cleanly.
if (recording_session_id_)
CancelRecognitionAndInformDelegate(recording_session_id_);
recording_session_id_ = session_id;
requests_[session_id].is_active = true;
requests_[session_id].recognizer->StartRecognition();
if (delegate_.get())
delegate_->ShowWarmUp(session_id);
}
void SpeechRecognitionManagerImpl::CancelRecognitionForRequest(int session_id) {
// Ignore if the session id was not in our active recognizers list because the
// user might have clicked more than once, or recognition could have been
// ended due to other reasons before the user click was processed.
if (!HasPendingRequest(session_id))
return;
CancelRecognitionAndInformDelegate(session_id);
}
void SpeechRecognitionManagerImpl::FocusLostForRequest(int session_id) {
// See above comment.
if (!HasPendingRequest(session_id))
return;
// If this is an ongoing recording or if we were displaying an error message
// to the user, abort it since user has switched focus. Otherwise
// recognition has started and keep that going so user can start speaking to
// another element while this gets the results in parallel.
if (recording_session_id_ == session_id || !requests_[session_id].is_active)
CancelRecognitionAndInformDelegate(session_id);
}
void SpeechRecognitionManagerImpl::CancelRecognition(int session_id) {
DCHECK(HasPendingRequest(session_id));
if (requests_[session_id].is_active)
requests_[session_id].recognizer->AbortRecognition();
requests_.erase(session_id);
if (recording_session_id_ == session_id)
recording_session_id_ = 0;
if (delegate_.get())
delegate_->DoClose(session_id);
}
void SpeechRecognitionManagerImpl::CancelAllRequestsWithDelegate(
InputTagSpeechDispatcherHost* delegate) {
SpeechRecognizerMap::iterator it = requests_.begin();
while (it != requests_.end()) {
if (it->second.delegate == delegate) {
CancelRecognition(it->first);
// This map will have very few elements so it is simpler to restart.
it = requests_.begin();
} else {
++it;
}
}
}
void SpeechRecognitionManagerImpl::StopRecording(int session_id) {
// No pending requests on extension popups.
if (!HasPendingRequest(session_id))
return;
requests_[session_id].recognizer->StopAudioCapture();
}
// -------- SpeechRecognitionEventListener interface implementation. ---------
void SpeechRecognitionManagerImpl::OnRecognitionResult(
int session_id, const content::SpeechRecognitionResult& result) {
DCHECK(HasPendingRequest(session_id));
GetDelegate(session_id)->SetRecognitionResult(session_id, result);
}
void SpeechRecognitionManagerImpl::OnAudioEnd(int session_id) {
if (recording_session_id_ != session_id)
return;
DCHECK_EQ(recording_session_id_, session_id);
DCHECK(HasPendingRequest(session_id));
if (!requests_[session_id].is_active)
return;
recording_session_id_ = 0;
GetDelegate(session_id)->DidCompleteRecording(session_id);
if (delegate_.get())
delegate_->ShowRecognizing(session_id);
}
void SpeechRecognitionManagerImpl::OnRecognitionEnd(int session_id) {
if (!HasPendingRequest(session_id) || !requests_[session_id].is_active)
return;
GetDelegate(session_id)->DidCompleteRecognition(session_id);
requests_.erase(session_id);
if (delegate_.get())
delegate_->DoClose(session_id);
}
void SpeechRecognitionManagerImpl::OnSoundStart(int session_id) {
}
void SpeechRecognitionManagerImpl::OnSoundEnd(int session_id) {
}
void SpeechRecognitionManagerImpl::OnRecognitionError(
int session_id, const content::SpeechRecognitionError& error) {
DCHECK(HasPendingRequest(session_id));
if (session_id == recording_session_id_)
recording_session_id_ = 0;
requests_[session_id].is_active = false;
if (delegate_.get()) {
if (error.code == content::SPEECH_RECOGNITION_ERROR_AUDIO &&
error.details == content::SPEECH_AUDIO_ERROR_DETAILS_NO_MIC) {
delegate_->ShowMicError(session_id,
SpeechRecognitionManagerDelegate::MIC_ERROR_NO_DEVICE_AVAILABLE);
} else if (error.code == content::SPEECH_RECOGNITION_ERROR_AUDIO &&
error.details == content::SPEECH_AUDIO_ERROR_DETAILS_IN_USE) {
delegate_->ShowMicError(session_id,
SpeechRecognitionManagerDelegate::MIC_ERROR_DEVICE_IN_USE);
} else {
delegate_->ShowRecognizerError(session_id, error.code);
}
}
}
void SpeechRecognitionManagerImpl::OnAudioStart(int session_id) {
DCHECK(HasPendingRequest(session_id));
DCHECK_EQ(recording_session_id_, session_id);
if (delegate_.get())
delegate_->ShowRecording(session_id);
}
void SpeechRecognitionManagerImpl::OnRecognitionStart(int session_id) {
}
void SpeechRecognitionManagerImpl::OnEnvironmentEstimationComplete(
int session_id) {
DCHECK(HasPendingRequest(session_id));
DCHECK_EQ(recording_session_id_, session_id);
}
void SpeechRecognitionManagerImpl::OnAudioLevelsChange(
int session_id, float volume, float noise_volume) {
DCHECK(HasPendingRequest(session_id));
DCHECK_EQ(recording_session_id_, session_id);
if (delegate_.get())
delegate_->ShowInputVolume(session_id, volume, noise_volume);
}
void SpeechRecognitionManagerImpl::CancelRecognitionAndInformDelegate(
int session_id) {
InputTagSpeechDispatcherHost* cur_delegate = GetDelegate(session_id);
CancelRecognition(session_id);
cur_delegate->DidCompleteRecording(session_id);
cur_delegate->DidCompleteRecognition(session_id);
}
SpeechRecognitionManagerImpl::Request::Request()
: is_active(false) {
}
SpeechRecognitionManagerImpl::Request::~Request() {
}
} // namespace speech