Commit 011cfc7d authored by primiano@chromium.org's avatar primiano@chromium.org

SpeechInputExtensionManager now interface (exclusively) with...

SpeechInputExtensionManager now interface (exclusively) with SpeechRecognitionManagerDelegate (Speech CL1.11).

- The tray icon and balloon handing has been moved from speech_input_extensions_manager to chrome_speech_recognition_manager_delegate, since that code (tray icon) will be used also for continuous recognition.
- Removed the SpeechRecognizer interface from /content/public (thus de-virtualized and refcounted the SpeechRecognizerImpl)

BUG=116954
TEST=none

Review URL: https://chromiumcodereview.appspot.com/10377082

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@138498 0039d316-1c4b-4281-b951-d872f2087c98
parent 8bf9ce70
......@@ -13,6 +13,7 @@
#include "chrome/browser/browser_process.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/speech/speech_recognition_tray_icon_controller.h"
#include "chrome/browser/tab_contents/tab_util.h"
#include "chrome/browser/view_type_utils.h"
#include "chrome/common/pref_names.h"
......@@ -36,10 +37,20 @@
using content::BrowserThread;
using content::SpeechRecognitionManager;
using content::WebContents;
using content::SpeechRecognitionSessionContext;
namespace {
const int kNoActiveBubble =
content::SpeechRecognitionManager::kSessionIDInvalid;
bool RequiresBubble(int session_id) {
return SpeechRecognitionManager::GetInstance()->
GetSessionContext(session_id).requested_by_page_element;
}
bool RequiresTrayIcon(int session_id) {
return !RequiresBubble(session_id);
}
} // namespace
namespace speech {
......@@ -108,13 +119,17 @@ class ChromeSpeechRecognitionManagerDelegate::OptionalRequestInfo
};
ChromeSpeechRecognitionManagerDelegate::ChromeSpeechRecognitionManagerDelegate()
: bubble_controller_(new SpeechRecognitionBubbleController(
ALLOW_THIS_IN_INITIALIZER_LIST(this))),
active_bubble_session_id_(kNoActiveBubble) {
: active_bubble_session_id_(kNoActiveBubble) {
}
ChromeSpeechRecognitionManagerDelegate::
~ChromeSpeechRecognitionManagerDelegate() {
if (tray_icon_controller_.get())
tray_icon_controller_->Hide();
if (active_bubble_session_id_ != kNoActiveBubble) {
DCHECK(bubble_controller_.get());
bubble_controller_->CloseBubble(active_bubble_session_id_);
}
}
void ChromeSpeechRecognitionManagerDelegate::InfoBubbleButtonClicked(
......@@ -122,8 +137,11 @@ void ChromeSpeechRecognitionManagerDelegate::InfoBubbleButtonClicked(
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK_EQ(active_bubble_session_id_, session_id);
// Note, the session might have been destroyed, therefore avoid calls to the
// manager which imply its existance (e.g., GetSessionContext()).
if (button == SpeechRecognitionBubble::BUTTON_CANCEL) {
bubble_controller_->CloseBubble(session_id);
GetBubbleController()->CloseBubble(session_id);
last_session_config_.reset();
active_bubble_session_id_ = kNoActiveBubble;
......@@ -131,7 +149,7 @@ void ChromeSpeechRecognitionManagerDelegate::InfoBubbleButtonClicked(
// the manager's public methods are reliable and will handle it properly.
SpeechRecognitionManager::GetInstance()->AbortSession(session_id);
} else if (button == SpeechRecognitionBubble::BUTTON_TRY_AGAIN) {
bubble_controller_->CloseBubble(session_id);
GetBubbleController()->CloseBubble(session_id);
active_bubble_session_id_ = kNoActiveBubble;
RestartLastSession();
}
......@@ -142,7 +160,10 @@ void ChromeSpeechRecognitionManagerDelegate::InfoBubbleFocusChanged(
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK_EQ(active_bubble_session_id_, session_id);
bubble_controller_->CloseBubble(session_id);
// Note, the session might have been destroyed, therefore avoid calls to the
// manager which imply its existance (e.g., GetSessionContext()).
GetBubbleController()->CloseBubble(session_id);
last_session_config_.reset();
active_bubble_session_id_ = kNoActiveBubble;
......@@ -164,6 +185,10 @@ void ChromeSpeechRecognitionManagerDelegate::RestartLastSession() {
void ChromeSpeechRecognitionManagerDelegate::OnRecognitionStart(
int session_id) {
const content::SpeechRecognitionSessionContext& context =
SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
if (RequiresBubble(session_id)) {
// Copy the configuration of the session (for the "try again" button).
last_session_config_.reset(new content::SpeechRecognitionSessionConfig(
SpeechRecognitionManager::GetInstance()->GetSessionConfig(session_id)));
......@@ -171,18 +196,25 @@ void ChromeSpeechRecognitionManagerDelegate::OnRecognitionStart(
// Create and show the bubble.
DCHECK_EQ(active_bubble_session_id_, kNoActiveBubble);
active_bubble_session_id_ = session_id;
const content::SpeechRecognitionSessionContext& context =
SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
bubble_controller_->CreateBubble(session_id,
GetBubbleController()->CreateBubble(session_id,
context.render_process_id,
context.render_view_id,
context.element_rect);
// TODO(primiano) Why not create directly the bubble in warmup mode?
bubble_controller_->SetBubbleWarmUpMode(session_id);
// TODO(primiano) Why not creating directly the bubble in warmup mode?
GetBubbleController()->SetBubbleWarmUpMode(session_id);
}
}
void ChromeSpeechRecognitionManagerDelegate::OnAudioStart(int session_id) {
bubble_controller_->SetBubbleRecordingMode(session_id);
if (RequiresBubble(session_id)) {
GetBubbleController()->SetBubbleRecordingMode(session_id);
} else if (RequiresTrayIcon(session_id)) {
const content::SpeechRecognitionSessionContext& context =
SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
GetTrayIconController()->Show(context.context_name,
context.is_first_request_for_context);
}
}
void ChromeSpeechRecognitionManagerDelegate::OnEnvironmentEstimationComplete(
......@@ -198,16 +230,19 @@ void ChromeSpeechRecognitionManagerDelegate::OnSoundEnd(int session_id) {
void ChromeSpeechRecognitionManagerDelegate::OnAudioEnd(int session_id) {
// OnAudioEnd can be also raised after an abort, when the bubble has already
// been closed.
if (active_bubble_session_id_ == session_id)
bubble_controller_->SetBubbleRecognizingMode(session_id);
if (RequiresBubble(session_id) && active_bubble_session_id_ == session_id) {
GetBubbleController()->SetBubbleRecognizingMode(session_id);
} else if (RequiresTrayIcon(session_id)) {
GetTrayIconController()->Hide();
}
}
void ChromeSpeechRecognitionManagerDelegate::OnRecognitionResult(
int session_id, const content::SpeechRecognitionResult& result) {
// A result can be dispatched when the bubble is not visible anymore (e.g.,
// lost focus while waiting for a result, thus continuing in background).
if (active_bubble_session_id_ == session_id) {
bubble_controller_->CloseBubble(session_id);
if (RequiresBubble(session_id) && active_bubble_session_id_ == session_id) {
GetBubbleController()->CloseBubble(session_id);
last_session_config_.reset();
active_bubble_session_id_ = kNoActiveBubble;
}
......@@ -218,6 +253,7 @@ void ChromeSpeechRecognitionManagerDelegate::OnRecognitionError(
// An error can be dispatched when the bubble is not visible anymore.
if (active_bubble_session_id_ != session_id)
return;
DCHECK(RequiresBubble(session_id));
int error_message_id = 0;
switch (error.code) {
......@@ -250,17 +286,29 @@ void ChromeSpeechRecognitionManagerDelegate::OnRecognitionError(
NOTREACHED() << "unknown error " << error.code;
return;
}
bubble_controller_->SetBubbleMessage(
GetBubbleController()->SetBubbleMessage(
session_id, l10n_util::GetStringUTF16(error_message_id));
}
void ChromeSpeechRecognitionManagerDelegate::OnAudioLevelsChange(
int session_id, float volume, float noise_volume) {
if (active_bubble_session_id_ == session_id)
bubble_controller_->SetBubbleInputVolume(session_id, volume, noise_volume);
if (active_bubble_session_id_ == session_id) {
DCHECK(RequiresBubble(session_id));
GetBubbleController()->SetBubbleInputVolume(session_id,
volume, noise_volume);
} else if (RequiresTrayIcon(session_id)) {
GetTrayIconController()->SetVUMeterVolume(volume);
}
}
void ChromeSpeechRecognitionManagerDelegate::OnRecognitionEnd(int session_id) {
// No need to remove the bubble here, since either one of the following events
// must have happened prior to this callback:
// - A previous OnRecognitionResult event already closed the bubble.
// - An error occurred, so the bubble is showing the error and will be closed
// when it will lose focus (by InfoBubbleFocusChanged()).
// - The bubble lost focus or the user pressed the Cancel button, thus it has
// been closed by InfoBubbleFocusChanged(), which triggered an AbortSession.
}
void ChromeSpeechRecognitionManagerDelegate::GetDiagnosticInformation(
......@@ -286,11 +334,20 @@ void ChromeSpeechRecognitionManagerDelegate::CheckRecognitionIsAllowed(
int session_id,
base::Callback<void(int session_id, bool is_allowed)> callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
const content::SpeechRecognitionSessionContext& context =
SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
// We don't need any particular check for sessions not using a bubble. In such
// cases, we just notify it to the manager (calling-back synchronously, since
// we remain in the IO thread).
if (RequiresTrayIcon(session_id)) {
callback.Run(session_id, true /* is_allowed */);
return;
}
// Sessions using bubbles, conversely, need a check on the renderer view type.
// The check must be performed in the UI thread. We defer it posting to
// CheckRenderViewType, which will issue the callback on our behalf.
const content::SpeechRecognitionSessionContext& context =
SpeechRecognitionManager::GetInstance()->GetSessionContext(session_id);
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&CheckRenderViewType,
session_id,
......@@ -330,4 +387,19 @@ void ChromeSpeechRecognitionManagerDelegate::CheckRenderViewType(
base::Bind(callback, session_id, allowed));
}
SpeechRecognitionBubbleController*
ChromeSpeechRecognitionManagerDelegate::GetBubbleController() {
if (!bubble_controller_.get())
bubble_controller_ = new SpeechRecognitionBubbleController(this);
return bubble_controller_.get();
}
SpeechRecognitionTrayIconController*
ChromeSpeechRecognitionManagerDelegate::GetTrayIconController() {
if (!tray_icon_controller_.get())
tray_icon_controller_ = new SpeechRecognitionTrayIconController();
return tray_icon_controller_.get();
}
} // namespace speech
......@@ -13,6 +13,8 @@
#include "content/public/browser/speech_recognition_manager_delegate.h"
#include "content/public/browser/speech_recognition_session_config.h"
class SpeechRecognitionTrayIconController;
namespace speech {
// This is Chrome's implementation of the SpeechRecognitionManagerDelegate
......@@ -69,7 +71,12 @@ class ChromeSpeechRecognitionManagerDelegate
// (which is copied into |last_session_config_|). Used for "try again".
void RestartLastSession();
// Lazy initializers for bubble and tray icon controller.
SpeechRecognitionBubbleController* GetBubbleController();
SpeechRecognitionTrayIconController* GetTrayIconController();
scoped_refptr<SpeechRecognitionBubbleController> bubble_controller_;
scoped_refptr<SpeechRecognitionTrayIconController> tray_icon_controller_;
scoped_refptr<OptionalRequestInfo> optional_request_info_;
scoped_ptr<content::SpeechRecognitionSessionConfig> last_session_config_;
......
......@@ -23,6 +23,10 @@ namespace net {
class URLRequestContextGetter;
}
namespace {
const int kSessionIDForTests = 0;
}
// Mock class used to test the extension speech input API.
class SpeechInputExtensionApiTest : public ExtensionApiTest,
public SpeechInputExtensionInterface {
......@@ -77,10 +81,11 @@ class SpeechInputExtensionApiTest : public ExtensionApiTest,
virtual void StartRecording(
content::SpeechRecognitionEventListener* listener,
net::URLRequestContextGetter* context_getter,
int session_id,
const string16& extension_name,
const std::string& language,
const std::string& grammar,
bool filter_profanities) OVERRIDE;
bool filter_profanities,
bool show_notification) OVERRIDE;
virtual void StopRecording(bool recognition_failed) OVERRIDE;
......@@ -107,7 +112,7 @@ class SpeechInputExtensionApiTest : public ExtensionApiTest,
};
private:
void ProvideResults(int session_id);
void ProvideResults();
bool recording_devices_available_;
bool recognizer_is_valid_;
......@@ -131,10 +136,11 @@ SpeechInputExtensionApiTest::~SpeechInputExtensionApiTest() {
void SpeechInputExtensionApiTest::StartRecording(
content::SpeechRecognitionEventListener* listener,
net::URLRequestContextGetter* context_getter,
int session_id,
const string16& extension_name,
const std::string& language,
const std::string& grammar,
bool filter_profanities) {
bool filter_profanities,
bool show_notification) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
recognizer_is_valid_ = true;
......@@ -143,7 +149,7 @@ void SpeechInputExtensionApiTest::StartRecording(
FROM_HERE,
base::Bind(&SpeechInputExtensionManager::OnAudioStart,
GetManager(),
session_id),
kSessionIDForTests),
base::TimeDelta());
// Notify sound start in the input device.
......@@ -151,16 +157,14 @@ void SpeechInputExtensionApiTest::StartRecording(
FROM_HERE,
base::Bind(&SpeechInputExtensionManager::OnSoundStart,
GetManager(),
session_id),
kSessionIDForTests),
base::TimeDelta());
if (result_delay_ms_ != kDontDispatchCall) {
// Dispatch the recognition results.
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&SpeechInputExtensionApiTest::ProvideResults,
this,
session_id),
base::Bind(&SpeechInputExtensionApiTest::ProvideResults, this),
base::TimeDelta::FromMilliseconds(result_delay_ms_));
}
}
......@@ -170,18 +174,19 @@ void SpeechInputExtensionApiTest::StopRecording(bool recognition_failed) {
recognizer_is_valid_ = false;
}
void SpeechInputExtensionApiTest::ProvideResults(int session_id) {
void SpeechInputExtensionApiTest::ProvideResults() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (next_error_ != content::SPEECH_RECOGNITION_ERROR_NONE) {
GetManager()->OnRecognitionError(
session_id, content::SpeechRecognitionError(next_error_));
kSessionIDForTests, content::SpeechRecognitionError(next_error_));
return;
}
GetManager()->OnSoundEnd(session_id);
GetManager()->OnAudioEnd(session_id);
GetManager()->OnRecognitionResult(session_id, next_result_);
GetManager()->OnSoundEnd(kSessionIDForTests);
GetManager()->OnAudioEnd(kSessionIDForTests);
GetManager()->OnRecognitionResult(kSessionIDForTests, next_result_);
GetManager()->OnRecognitionEnd(kSessionIDForTests);
}
// Every test should leave the manager in the idle state when finished.
......
......@@ -15,7 +15,6 @@
#include "chrome/browser/profiles/profile_dependency_manager.h"
#include "chrome/browser/profiles/profile_keyed_service.h"
#include "chrome/browser/profiles/profile_keyed_service_factory.h"
#include "chrome/browser/speech/speech_recognition_tray_icon_controller.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/pref_names.h"
......@@ -23,9 +22,11 @@
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/speech_recognition_manager.h"
#include "content/public/browser/speech_recognizer.h"
#include "content/public/browser/speech_recognition_session_config.h"
#include "content/public/browser/speech_recognition_session_context.h"
#include "content/public/common/speech_recognition_error.h"
#include "content/public/common/speech_recognition_result.h"
#include "net/url_request/url_request_context_getter.h"
using content::BrowserThread;
using content::SpeechRecognitionHypothesis;
......@@ -55,12 +56,6 @@ const char kOnResultEvent[] = "experimental.speechInput.onResult";
const char kOnSoundStartEvent[] = "experimental.speechInput.onSoundStart";
const char kOnSoundEndEvent[] = "experimental.speechInput.onSoundEnd";
// Session id provided to the speech recognizer. Since only one extension can
// be recording on the same time a constant value is enough as id.
// TODO(primiano) this will not be valid anymore once speech input extension
// will use the SpeechRecognitionManager and not the SpeechRecognizer directly.
static const int kSpeechInputSessionId = 1;
// Wrap an SpeechInputExtensionManager using scoped_refptr to avoid
// assertion failures on destruction because of not using release().
class SpeechInputExtensionManagerWrapper : public ProfileKeyedService {
......@@ -157,7 +152,10 @@ SpeechInputExtensionManager::SpeechInputExtensionManager(Profile* profile)
: profile_(profile),
state_(kIdle),
registrar_(new content::NotificationRegistrar),
speech_interface_(NULL) {
speech_interface_(NULL),
is_recognition_in_progress_(false),
speech_recognition_session_id_(
SpeechRecognitionManager::kSessionIDInvalid) {
registrar_->Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
content::Source<Profile>(profile_));
}
......@@ -194,11 +192,17 @@ void SpeechInputExtensionManager::ShutdownOnUIThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
VLOG(1) << "Profile shutting down.";
// Note: Unretained(this) is safe, also if we are freed in the meanwhile.
// It is used by the SR manager just for comparing the raw pointer and remove
// the associated sessions.
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(&SpeechRecognitionManager::AbortAllSessionsForListener,
base::Unretained(SpeechRecognitionManager::GetInstance()),
base::Unretained(this)));
base::AutoLock auto_lock(state_lock_);
DCHECK(state_ != kShutdown);
if (state_ != kIdle) {
DCHECK(notification_.get());
notification_->Hide();
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(&SpeechInputExtensionManager::ForceStopOnIOThread, this));
}
......@@ -245,7 +249,7 @@ void SpeechInputExtensionManager::OnRecognitionResult(
int session_id,
const content::SpeechRecognitionResult& result) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK_EQ(session_id, kSpeechInputSessionId);
DCHECK_EQ(session_id, speech_recognition_session_id_);
// Stopping will start the disassociation with the extension.
// Make a copy to report the results to the proper one.
......@@ -288,13 +292,13 @@ void SpeechInputExtensionManager::SetRecognitionResultOnUIThread(
}
void SpeechInputExtensionManager::OnRecognitionStart(int session_id) {
DCHECK_EQ(session_id, kSpeechInputSessionId);
DCHECK_EQ(session_id, speech_recognition_session_id_);
}
void SpeechInputExtensionManager::OnAudioStart(int session_id) {
VLOG(1) << "OnAudioStart";
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK_EQ(session_id, kSpeechInputSessionId);
DCHECK_EQ(session_id, speech_recognition_session_id_);
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread,
......@@ -302,11 +306,16 @@ void SpeechInputExtensionManager::OnAudioStart(int session_id) {
}
void SpeechInputExtensionManager::OnAudioEnd(int session_id) {
DCHECK_EQ(session_id, kSpeechInputSessionId);
}
void SpeechInputExtensionManager::OnRecognitionEnd(int session_id) {
DCHECK_EQ(session_id, kSpeechInputSessionId);
// In the very exceptional case in which we requested a new recognition before
// the previous one ended, don't clobber the speech_recognition_session_id_.
if (speech_recognition_session_id_ == session_id) {
is_recognition_in_progress_ = false;
speech_recognition_session_id_ =
SpeechRecognitionManager::kSessionIDInvalid;
}
}
void SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread() {
......@@ -320,21 +329,9 @@ void SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread() {
VLOG(1) << "State changed to recording";
state_ = kRecording;
const extensions::Extension* extension = profile_->GetExtensionService()->
GetExtensionById(extension_id_in_use_, true);
DCHECK(extension);
bool show_notification = !profile_->GetPrefs()->GetBoolean(
prefs::kSpeechInputTrayNotificationShown);
if (!notification_.get())
notification_ = new SpeechRecognitionTrayIconController();
notification_->Show(UTF8ToUTF16(extension->name()), show_notification);
if (show_notification) {
DCHECK(profile_);
profile_->GetPrefs()->SetBoolean(
prefs::kSpeechInputTrayNotificationShown, true);
}
VLOG(1) << "Sending start notification";
content::NotificationService::current()->Notify(
......@@ -346,20 +343,13 @@ void SpeechInputExtensionManager::DidStartReceivingAudioOnUIThread() {
void SpeechInputExtensionManager::OnRecognitionError(
int session_id, const content::SpeechRecognitionError& error) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK_EQ(session_id, kSpeechInputSessionId);
// Simply return in case of an ERROR_ABORTED, since it is not contemplated
// in the speech input extensions architecture.
if (error.code == content::SPEECH_RECOGNITION_ERROR_ABORTED)
return;
DCHECK_EQ(session_id, speech_recognition_session_id_);
VLOG(1) << "OnRecognitionError: " << error.code;
base::AutoLock auto_lock(state_lock_);
if (state_ == kShutdown)
return;
// Release the recognizer object.
GetSpeechInputExtensionInterface()->StopRecording(true);
std::string event_error_code;
......@@ -369,6 +359,15 @@ void SpeechInputExtensionManager::OnRecognitionError(
case content::SPEECH_RECOGNITION_ERROR_NONE:
break;
case content::SPEECH_RECOGNITION_ERROR_ABORTED:
// ERROR_ABORTED is received whenever AbortSession is called on the
// manager. However, we want propagate the error only if it is triggered
// by an external cause (another recognition started, aborting us), thus
// only if it occurs while we are capturing audio.
if (state_ == kRecording)
event_error_code = kErrorCaptureError;
break;
case content::SPEECH_RECOGNITION_ERROR_AUDIO:
if (state_ == kStarting) {
event_error_code = kErrorUnableToStart;
......@@ -411,12 +410,12 @@ void SpeechInputExtensionManager::OnRecognitionError(
void SpeechInputExtensionManager::OnEnvironmentEstimationComplete(
int session_id) {
DCHECK_EQ(session_id, kSpeechInputSessionId);
DCHECK_EQ(session_id, speech_recognition_session_id_);
}
void SpeechInputExtensionManager::OnSoundStart(int session_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK_EQ(session_id, kSpeechInputSessionId);
DCHECK_EQ(session_id, speech_recognition_session_id_);
VLOG(1) << "OnSoundStart";
std::string json_args;
......@@ -428,7 +427,6 @@ void SpeechInputExtensionManager::OnSoundStart(int session_id) {
void SpeechInputExtensionManager::OnSoundEnd(int session_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK_EQ(session_id, kSpeechInputSessionId);
VLOG(1) << "OnSoundEnd";
std::string json_args;
......@@ -471,11 +469,6 @@ void SpeechInputExtensionManager::DispatchError(
if (state_ == kShutdown)
return;
if (state_ == kRecording) {
DCHECK(notification_.get());
notification_->Hide();
}
extension_id = extension_id_in_use_;
ResetToIdleState();
......@@ -531,23 +524,37 @@ bool SpeechInputExtensionManager::Start(
NOTREACHED();
}
const extensions::Extension* extension = profile_->GetExtensionService()->
GetExtensionById(extension_id, true);
DCHECK(extension);
const string16& extension_name = UTF8ToUTF16(extension->name());
extension_id_in_use_ = extension_id;
VLOG(1) << "State changed to starting";
state_ = kStarting;
// Checks if the security notification balloon has been already shown (only
// once for a profile). It is reset on DidStartReceivingAudioOnUIThread.
scoped_refptr<net::URLRequestContextGetter> url_request_context_getter =
profile_->GetRequestContext();
const bool show_notification = !profile_->GetPrefs()->GetBoolean(
prefs::kSpeechInputTrayNotificationShown);
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&SpeechInputExtensionManager::StartOnIOThread, this,
profile_->GetRequestContext(), language, grammar,
filter_profanities));
url_request_context_getter, extension_name, language, grammar,
filter_profanities, show_notification));
return true;
}
void SpeechInputExtensionManager::StartOnIOThread(
net::URLRequestContextGetter* context_getter,
scoped_refptr<net::URLRequestContextGetter> context_getter,
const string16& extension_name,
const std::string& language,
const std::string& grammar,
bool filter_profanities) {
bool filter_profanities,
bool show_notification) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
VLOG(1) << "Requesting start (IO thread)";
......@@ -558,6 +565,12 @@ void SpeechInputExtensionManager::StartOnIOThread(
if (state_ == kShutdown)
return;
// TODO(primiano) These two checks below could be avoided, since they are
// already handled in the speech recognition classes. However, since the
// speech input extensions tests are bypassing the manager, we need them to
// pass the tests. A complete unit test which puts all the pieces together,
// mocking just the endpoints (the audio input controller and the URL fetcher)
// should be written.
if (!GetSpeechInputExtensionInterface()->HasAudioInputDevices()) {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
......@@ -573,9 +586,13 @@ void SpeechInputExtensionManager::StartOnIOThread(
return;
}
GetSpeechInputExtensionInterface()->StartRecording(
this, context_getter, kSpeechInputSessionId, language, grammar,
filter_profanities);
GetSpeechInputExtensionInterface()->StartRecording(this,
context_getter,
extension_name,
language,
grammar,
filter_profanities,
show_notification);
}
bool SpeechInputExtensionManager::HasAudioInputDevices() {
......@@ -602,6 +619,7 @@ void SpeechInputExtensionManager::IsRecordingOnIOThread(
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
bool result = GetSpeechInputExtensionInterface()->IsCapturingAudio();
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&SpeechInputExtensionManager::IsRecordingOnUIThread,
......@@ -618,20 +636,41 @@ void SpeechInputExtensionManager::IsRecordingOnUIThread(
void SpeechInputExtensionManager::StartRecording(
content::SpeechRecognitionEventListener* listener,
net::URLRequestContextGetter* context_getter,
int session_id,
const string16& extension_name,
const std::string& language,
const std::string& grammar,
bool filter_profanities) {
bool filter_profanities,
bool show_notification) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(!recognizer_);
recognizer_ = content::SpeechRecognizer::Create(
listener, session_id, language, grammar, context_getter,
filter_profanities, "", "");
recognizer_->StartRecognition();
content::SpeechRecognitionSessionContext context;
context.requested_by_page_element = false;
context.is_first_request_for_context = show_notification;
context.context_name = extension_name;
content::SpeechRecognitionSessionConfig config;
// config.is_one_shot = true; // TODO(primiano) Uncomment when CL2.0 lands.
config.language = language;
config.grammars.push_back(content::SpeechRecognitionGrammar(grammar));
config.initial_context = context;
config.url_request_context_getter = context_getter;
config.filter_profanities = filter_profanities;
config.event_listener = listener;
DCHECK(!is_recognition_in_progress_);
SpeechRecognitionManager& manager = *SpeechRecognitionManager::GetInstance();
speech_recognition_session_id_ =
manager.CreateSession(config);
DCHECK_NE(speech_recognition_session_id_,
SpeechRecognitionManager::kSessionIDInvalid);
is_recognition_in_progress_ = true;
manager.StartSession(speech_recognition_session_id_);
}
bool SpeechInputExtensionManager::HasValidRecognizer() {
return !!recognizer_;
if (!is_recognition_in_progress_)
return false;
return SpeechRecognitionManager::GetInstance()->IsCapturingAudio();
}
bool SpeechInputExtensionManager::Stop(const std::string& extension_id,
......@@ -692,13 +731,13 @@ void SpeechInputExtensionManager::ForceStopOnIOThread() {
}
void SpeechInputExtensionManager::StopRecording(bool recognition_failed) {
if (recognizer_) {
// Recognition is already cancelled in case of failure.
// Double-cancelling leads to assertion failures.
if (!recognition_failed)
recognizer_->AbortRecognition();
recognizer_.release();
}
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!is_recognition_in_progress_)
return;
DCHECK_NE(speech_recognition_session_id_,
SpeechRecognitionManager::kSessionIDInvalid);
SpeechRecognitionManager::GetInstance()->AbortSession(
speech_recognition_session_id_);
}
void SpeechInputExtensionManager::StopSucceededOnUIThread() {
......@@ -712,9 +751,6 @@ void SpeechInputExtensionManager::StopSucceededOnUIThread() {
std::string extension_id = extension_id_in_use_;
ResetToIdleState();
DCHECK(notification_.get());
notification_->Hide();
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_EXTENSION_SPEECH_INPUT_RECORDING_STOPPED,
// Guarded by the state_ == kShutdown check.
......@@ -724,17 +760,4 @@ void SpeechInputExtensionManager::StopSucceededOnUIThread() {
void SpeechInputExtensionManager::OnAudioLevelsChange(int session_id,
float volume,
float noise_volume) {
DCHECK_EQ(session_id, kSpeechInputSessionId);
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(&SpeechInputExtensionManager::SetInputVolumeOnUIThread,
this, volume));
}
void SpeechInputExtensionManager::SetInputVolumeOnUIThread(
float volume) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(notification_.get());
notification_->SetVUMeterVolume(volume);
}
float noise_volume) {}
......@@ -11,18 +11,18 @@
#include "base/callback_forward.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/string16.h"
#include "base/synchronization/lock.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/speech_recognition_event_listener.h"
class Profile;
class SpeechRecognitionTrayIconController;
namespace content {
class NotificationRegistrar;
struct SpeechRecognitionError;
class SpeechRecognitionManager;
struct SpeechRecognitionResult;
class SpeechRecognizer;
}
namespace net {
......@@ -39,10 +39,11 @@ class SpeechInputExtensionInterface {
virtual void StartRecording(
content::SpeechRecognitionEventListener* listener,
net::URLRequestContextGetter* context_getter,
int session_id,
const string16& extension_name,
const std::string& language,
const std::string& grammar,
bool filter_profanities) = 0;
bool filter_profanities,
bool show_notification) = 0;
virtual void StopRecording(bool recognition_failed) = 0;
virtual bool HasAudioInputDevices() = 0;
......@@ -143,19 +144,22 @@ class SpeechInputExtensionManager
virtual void StartRecording(
content::SpeechRecognitionEventListener* listener,
net::URLRequestContextGetter* context_getter,
int session_id,
const string16& extension_name,
const std::string& language,
const std::string& grammar,
bool filter_profanities) OVERRIDE;
bool filter_profanities,
bool show_notification) OVERRIDE;
virtual void StopRecording(bool recognition_failed) OVERRIDE;
// Internal methods.
void StartOnIOThread(
net::URLRequestContextGetter* context_getter,
scoped_refptr<net::URLRequestContextGetter> context_getter,
const string16& extension_name,
const std::string& language,
const std::string& grammar,
bool filter_profanities);
bool filter_profanities,
bool show_notification);
void ForceStopOnIOThread();
void IsRecordingOnIOThread(const IsRecordingCallback& callback);
......@@ -172,7 +176,6 @@ class SpeechInputExtensionManager
const std::string& json_args);
void ExtensionUnloaded(const std::string& extension_id);
void SetInputVolumeOnUIThread(float volume);
void ResetToIdleState();
virtual ~SpeechInputExtensionManager();
......@@ -196,10 +199,10 @@ class SpeechInputExtensionManager
// Used in the UI thread.
scoped_ptr<content::NotificationRegistrar> registrar_;
SpeechInputExtensionInterface* speech_interface_;
scoped_refptr<SpeechRecognitionTrayIconController> notification_;
// Used in the IO thread.
scoped_refptr<content::SpeechRecognizer> recognizer_;
bool is_recognition_in_progress_;
int speech_recognition_session_id_;
};
#endif // CHROME_BROWSER_SPEECH_SPEECH_INPUT_EXTENSION_MANAGER_H_
......@@ -327,6 +327,12 @@ void SpeechRecognitionManagerImpl::AbortAllSessionsForListener(
void SpeechRecognitionManagerImpl::DispatchEvent(int session_id,
FSMEvent event) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// There are some corner cases in which the session might be deleted (due to
// an EndRecognition event) between a request (e.g. Abort) and its dispatch.
if (!SessionExists(session_id))
return;
const Session& session = GetSession(session_id);
FSMState session_state = GetSessionState(session_id);
DCHECK_LE(session_state, SESSION_STATE_MAX_VALUE);
......
......@@ -12,7 +12,6 @@
#include "content/browser/speech/google_one_shot_remote_engine.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/speech_recognition_event_listener.h"
#include "content/public/browser/speech_recognizer.h"
#include "content/public/common/speech_recognition_error.h"
#include "content/public/common/speech_recognition_grammar.h"
#include "content/public/common/speech_recognition_result.h"
......@@ -24,7 +23,6 @@ using content::SpeechRecognitionError;
using content::SpeechRecognitionEventListener;
using content::SpeechRecognitionGrammar;
using content::SpeechRecognitionResult;
using content::SpeechRecognizer;
using media::AudioInputController;
using media::AudioManager;
using media::AudioParameters;
......@@ -68,41 +66,6 @@ void KeepAudioControllerRefcountedForDtor(scoped_refptr<AudioInputController>) {
} // namespace
// TODO(primiano) Create(...) is transitional (until we fix speech input
// extensions) and should be removed soon. The manager should be the only one
// knowing the existence of SpeechRecognizer(Impl), thus the only one in charge
// of instantiating it.
SpeechRecognizer* SpeechRecognizer::Create(
SpeechRecognitionEventListener* listener,
int session_id,
const std::string& language,
const std::string& grammar,
net::URLRequestContextGetter* context_getter,
bool filter_profanities,
const std::string& hardware_info,
const std::string& origin_url) {
speech::SpeechRecognitionEngineConfig remote_engine_config;
remote_engine_config.language = language;
if (!grammar.empty())
remote_engine_config.grammars.push_back(SpeechRecognitionGrammar(grammar));
remote_engine_config.audio_sample_rate =
speech::SpeechRecognizerImpl::kAudioSampleRate;
remote_engine_config.audio_num_bits_per_sample =
speech::SpeechRecognizerImpl::kNumBitsPerAudioSample;
remote_engine_config.filter_profanities = filter_profanities;
remote_engine_config.hardware_info = hardware_info;
remote_engine_config.origin_url = origin_url;
// SpeechRecognizerImpl takes ownership of google_remote_engine.
speech::GoogleOneShotRemoteEngine* google_remote_engine =
new speech::GoogleOneShotRemoteEngine(context_getter);
google_remote_engine->SetConfig(remote_engine_config);
return new speech::SpeechRecognizerImpl(listener,
session_id,
google_remote_engine);
}
namespace speech {
const int SpeechRecognizerImpl::kAudioSampleRate = 16000;
......
......@@ -11,7 +11,6 @@
#include "base/memory/scoped_ptr.h"
#include "content/browser/speech/endpointer/endpointer.h"
#include "content/browser/speech/speech_recognition_engine.h"
#include "content/public/browser/speech_recognizer.h"
#include "content/public/common/speech_recognition_error.h"
#include "content/public/common/speech_recognition_result.h"
#include "media/audio/audio_input_controller.h"
......@@ -27,15 +26,13 @@ class AudioManager;
namespace speech {
// TODO(primiano) Next CL: Remove the Impl suffix and the exported
// /content/public/browser/speech_recognizer.h interface since this class should
// not be visible outside (currently we need it for speech input extension API).
// TODO(primiano) Next CL: Remove the Impl suffix.
// Handles speech recognition for a session (identified by |session_id|), taking
// care of audio capture, silence detection/endpointer and interaction with the
// SpeechRecognitionEngine.
class CONTENT_EXPORT SpeechRecognizerImpl
: public NON_EXPORTED_BASE(content::SpeechRecognizer),
: public base::RefCountedThreadSafe<SpeechRecognizerImpl>,
public media::AudioInputController::EventHandler,
public NON_EXPORTED_BASE(SpeechRecognitionEngineDelegate) {
public:
......@@ -50,18 +47,15 @@ class CONTENT_EXPORT SpeechRecognizerImpl
int session_id,
SpeechRecognitionEngine* engine);
// content::SpeechRecognizer methods.
virtual void StartRecognition() OVERRIDE;
virtual void AbortRecognition() OVERRIDE;
virtual void StopAudioCapture() OVERRIDE;
virtual bool IsActive() const OVERRIDE;
virtual bool IsCapturingAudio() const OVERRIDE;
void StartRecognition();
void AbortRecognition();
void StopAudioCapture();
bool IsActive() const;
bool IsCapturingAudio() const;
const SpeechRecognitionEngine& recognition_engine() const;
protected:
virtual ~SpeechRecognizerImpl();
private:
friend class base::RefCountedThreadSafe<SpeechRecognizerImpl>;
friend class SpeechRecognizerImplTest;
enum FSMState {
......@@ -96,6 +90,8 @@ class CONTENT_EXPORT SpeechRecognizerImpl
content::SpeechRecognitionError engine_error;
};
virtual ~SpeechRecognizerImpl();
// Entry point for pushing any new external event into the recognizer FSM.
void DispatchEvent(const FSMEventArgs& event_args);
......
......@@ -139,8 +139,8 @@
'public/browser/speech_recognition_preferences.h',
'public/browser/speech_recognition_session_config.cc',
'public/browser/speech_recognition_session_config.h',
'public/browser/speech_recognition_session_context.cc',
'public/browser/speech_recognition_session_context.h',
'public/browser/speech_recognizer.h',
'public/browser/trace_controller.h',
'public/browser/trace_subscriber.h',
'public/browser/user_metrics.h',
......
// 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/public/browser/speech_recognition_session_context.h"
namespace content {
SpeechRecognitionSessionContext::SpeechRecognitionSessionContext()
: render_process_id(0),
render_view_id(0),
render_request_id(0),
requested_by_page_element(true),
is_first_request_for_context(false) {
}
SpeechRecognitionSessionContext::~SpeechRecognitionSessionContext() {
}
} // namespace content
......@@ -6,6 +6,7 @@
#define CONTENT_PUBLIC_BROWSER_SPEECH_RECOGNITION_SESSION_CONTEXT_H_
#pragma once
#include "base/string16.h"
#include "content/common/content_export.h"
#include "ui/gfx/rect.h"
......@@ -21,18 +22,32 @@ namespace content {
// SpeechRecognitionManager::GetSessionContext and
// SpeechRecognitionManager::LookupSessionByContext methods).
struct CONTENT_EXPORT SpeechRecognitionSessionContext {
SpeechRecognitionSessionContext()
: render_process_id(0),
render_view_id(0),
render_request_id(0),
js_handle_id(0) {}
~SpeechRecognitionSessionContext() {}
SpeechRecognitionSessionContext();
~SpeechRecognitionSessionContext();
int render_process_id;
int render_view_id;
int render_request_id;
int js_handle_id;
// Determines whether recognition was requested by a page element (in which
// case its coordinates are passed in |element_rect|).
bool requested_by_page_element;
// The coordinates of the page element for placing the bubble (valid only when
// |requested_by_page_element| = true).
gfx::Rect element_rect;
// Determines whether this is the first time that this context (identified by
// |context_name|) is requesting a recognition.
// TODO(primiano) This is really temporary, remove after CL1.12 which will
// refactor SpeechRecognitionPreferences and move this check entirely whithin
// chrome, without involving content.
bool is_first_request_for_context;
// A texual description of the context (website, extension name) that is
// requesting recognition, for prompting security notifications to the user.
string16 context_name;
};
} // namespace content
......
// 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.
#ifndef CONTENT_PUBLIC_BROWSER_SPEECH_RECOGNIZER_H_
#define CONTENT_PUBLIC_BROWSER_SPEECH_RECOGNIZER_H_
#pragma once
#include <string>
#include "base/memory/ref_counted.h"
#include "content/common/content_export.h"
namespace net {
class URLRequestContextGetter;
}
namespace content {
class SpeechRecognitionEventListener;
// Records audio, sends recorded audio to server and translates server response
// to recognition result.
// TODO(primiano) remove this public interface as soon as we fix speech input
// extensions.
class SpeechRecognizer : public base::RefCountedThreadSafe<SpeechRecognizer> {
public:
CONTENT_EXPORT static SpeechRecognizer* Create(
SpeechRecognitionEventListener* event_listener,
int session_id,
const std::string& language,
const std::string& grammar,
net::URLRequestContextGetter* context_getter,
bool filter_profanities,
const std::string& hardware_info,
const std::string& origin_url);
// Starts audio recording and the recognition process. The same
// SpeechRecognizer instance can be used multiple times for speech recognition
// though each recognition request can be made only after the previous one
// completes (i.e. after receiving
// SpeechRecognitionEventListener::OnRecognitionEnd).
virtual void StartRecognition() = 0;
// Stops recording audio and cancels recognition. Any audio recorded so far
// gets discarded.
virtual void AbortRecognition() = 0;
// Stops recording audio and finalizes recognition, possibly getting results.
virtual void StopAudioCapture() = 0;
// Checks wether the recognizer is active, that is, either capturing audio
// or waiting for a result.
virtual bool IsActive() const = 0;
// Checks whether the recognizer is capturing audio.
virtual bool IsCapturingAudio() const = 0;
protected:
friend class base::RefCountedThreadSafe<SpeechRecognizer>;
virtual ~SpeechRecognizer() {}
};
} // namespace content
#endif // CONTENT_PUBLIC_BROWSER_SPEECH_RECOGNIZER_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment