Commit 509ce7f9 authored by calamity's avatar calamity Committed by Commit bot

Notify hotwording extension of microphone state change.

This CL notifies the hotwording extension of a microphone state change
by adding a hotwordPrivate.onMicrophoneStateChanged event which triggers
an update of the hotwording state.

It also moves the media device observation from HotwordServiceFactory to
HotwordService.

BUG=461787
TBR=estade@chromium.org

Review URL: https://codereview.chromium.org/1047973003

Cr-Commit-Position: refs/heads/master@{#324024}
parent 3a672fe0
...@@ -106,11 +106,21 @@ void HotwordPrivateEventService::OnSpeakerModelExists() { ...@@ -106,11 +106,21 @@ void HotwordPrivateEventService::OnSpeakerModelExists() {
SignalEvent(api::hotword_private::OnSpeakerModelExists::kEventName); SignalEvent(api::hotword_private::OnSpeakerModelExists::kEventName);
} }
void HotwordPrivateEventService::OnMicrophoneStateChanged(bool enabled) {
SignalEvent(api::hotword_private::OnMicrophoneStateChanged::kEventName,
api::hotword_private::OnMicrophoneStateChanged::Create(enabled));
}
void HotwordPrivateEventService::SignalEvent(const std::string& event_name) { void HotwordPrivateEventService::SignalEvent(const std::string& event_name) {
SignalEvent(event_name, make_scoped_ptr(new base::ListValue()));
}
void HotwordPrivateEventService::SignalEvent(const std::string& event_name,
scoped_ptr<base::ListValue> args) {
EventRouter* router = EventRouter::Get(profile_); EventRouter* router = EventRouter::Get(profile_);
if (!router || !router->HasEventListener(event_name)) if (!router || !router->HasEventListener(event_name))
return; return;
scoped_ptr<base::ListValue> args(new base::ListValue());
scoped_ptr<Event> event(new Event(event_name, args.Pass())); scoped_ptr<Event> event(new Event(event_name, args.Pass()));
router->BroadcastEvent(event.Pass()); router->BroadcastEvent(event.Pass());
} }
......
...@@ -44,10 +44,14 @@ class HotwordPrivateEventService : public BrowserContextKeyedAPI { ...@@ -44,10 +44,14 @@ class HotwordPrivateEventService : public BrowserContextKeyedAPI {
void OnSpeakerModelExists(); void OnSpeakerModelExists();
void OnMicrophoneStateChanged(bool enabled);
private: private:
friend class BrowserContextKeyedAPIFactory<HotwordPrivateEventService>; friend class BrowserContextKeyedAPIFactory<HotwordPrivateEventService>;
void SignalEvent(const std::string& event_name); void SignalEvent(const std::string& event_name);
void SignalEvent(const std::string& event_name,
scoped_ptr<base::ListValue> args);
Profile* profile_; Profile* profile_;
PrefChangeRegistrar pref_change_registrar_; PrefChangeRegistrar pref_change_registrar_;
......
...@@ -35,6 +35,8 @@ cr.define('hotword', function() { ...@@ -35,6 +35,8 @@ cr.define('hotword', function() {
this.tabCreatedListener_ = this.handleCreatedTab_.bind(this); this.tabCreatedListener_ = this.handleCreatedTab_.bind(this);
this.tabUpdatedListener_ = this.handleUpdatedTab_.bind(this); this.tabUpdatedListener_ = this.handleUpdatedTab_.bind(this);
this.tabActivatedListener_ = this.handleActivatedTab_.bind(this); this.tabActivatedListener_ = this.handleActivatedTab_.bind(this);
this.microphoneStateChangedListener_ =
this.handleMicrophoneStateChanged_.bind(this);
this.windowFocusChangedListener_ = this.handleChangedWindow_.bind(this); this.windowFocusChangedListener_ = this.handleChangedWindow_.bind(this);
this.messageListener_ = this.handleMessageFromPage_.bind(this); this.messageListener_ = this.handleMessageFromPage_.bind(this);
...@@ -237,6 +239,19 @@ cr.define('hotword', function() { ...@@ -237,6 +239,19 @@ cr.define('hotword', function() {
this.updateTabState_(); this.updateTabState_();
}, },
/**
* Handles the microphone state changing.
* @param {boolean} enabled Whether the microphone is now enabled.
* @private
*/
handleMicrophoneStateChanged_: function(enabled) {
if (enabled) {
this.updateTabState_();
return;
}
this.stopHotwording_();
},
/** /**
* Handles a change in Chrome windows. * Handles a change in Chrome windows.
...@@ -492,6 +507,8 @@ cr.define('hotword', function() { ...@@ -492,6 +507,8 @@ cr.define('hotword', function() {
chrome.tabs.onActivated.addListener(this.tabActivatedListener_); chrome.tabs.onActivated.addListener(this.tabActivatedListener_);
chrome.windows.onFocusChanged.addListener( chrome.windows.onFocusChanged.addListener(
this.windowFocusChangedListener_); this.windowFocusChangedListener_);
chrome.hotwordPrivate.onMicrophoneStateChanged.addListener(
this.microphoneStateChangedListener_);
if (chrome.runtime.onMessage.hasListener(this.messageListener_)) if (chrome.runtime.onMessage.hasListener(this.messageListener_))
return; return;
chrome.runtime.onMessageExternal.addListener( chrome.runtime.onMessageExternal.addListener(
...@@ -509,6 +526,8 @@ cr.define('hotword', function() { ...@@ -509,6 +526,8 @@ cr.define('hotword', function() {
chrome.tabs.onActivated.removeListener(this.tabActivatedListener_); chrome.tabs.onActivated.removeListener(this.tabActivatedListener_);
chrome.windows.onFocusChanged.removeListener( chrome.windows.onFocusChanged.removeListener(
this.windowFocusChangedListener_); this.windowFocusChangedListener_);
chrome.hotwordPrivate.onMicrophoneStateChanged.removeListener(
this.microphoneStateChangedListener_);
// Don't remove the Message listener, as we want them listening all // Don't remove the Message listener, as we want them listening all
// the time, // the time,
}, },
......
...@@ -323,6 +323,8 @@ void HotwordService::HotwordWebstoreInstaller::Shutdown() { ...@@ -323,6 +323,8 @@ void HotwordService::HotwordWebstoreInstaller::Shutdown() {
HotwordService::HotwordService(Profile* profile) HotwordService::HotwordService(Profile* profile)
: profile_(profile), : profile_(profile),
extension_registry_observer_(this), extension_registry_observer_(this),
microphone_available_(false),
audio_device_state_updated_(false),
client_(NULL), client_(NULL),
error_message_(0), error_message_(0),
reinstall_pending_(false), reinstall_pending_(false),
...@@ -386,6 +388,13 @@ HotwordService::HotwordService(Profile* profile) ...@@ -386,6 +388,13 @@ HotwordService::HotwordService(Profile* profile)
session_observer_.get()); session_observer_.get());
} }
#endif #endif
// Register with the device observer list to update the microphone
// availability.
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&HotwordService::InitializeMicrophoneObserver,
base::Unretained(this)));
} }
HotwordService::~HotwordService() { HotwordService::~HotwordService() {
...@@ -456,6 +465,10 @@ std::string HotwordService::ReinstalledExtensionId() { ...@@ -456,6 +465,10 @@ std::string HotwordService::ReinstalledExtensionId() {
return extension_misc::kHotwordSharedModuleId; return extension_misc::kHotwordSharedModuleId;
} }
void HotwordService::InitializeMicrophoneObserver() {
MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
}
void HotwordService::InstalledFromWebstoreCallback( void HotwordService::InstalledFromWebstoreCallback(
int num_tries, int num_tries,
bool success, bool success,
...@@ -605,19 +618,16 @@ bool HotwordService::IsServiceAvailable() { ...@@ -605,19 +618,16 @@ bool HotwordService::IsServiceAvailable() {
RecordErrorMetrics(error_message_); RecordErrorMetrics(error_message_);
// Determine if the proper audio capabilities exist. // Determine if the proper audio capabilities exist. The first time this is
// The first time this is called, it probably won't return in time, but that's // called, it probably won't return in time, but that's why it won't be
// why it won't be included in the error calculation (i.e., the call to // included in the error calculation. However, this use case is rare and
// IsAudioDeviceStateUpdated()). However, this use case is rare and typically // typically the devices will be initialized by the time a user goes to
// the devices will be initialized by the time a user goes to settings. // settings.
bool audio_device_state_updated =
HotwordServiceFactory::IsAudioDeviceStateUpdated();
HotwordServiceFactory::GetInstance()->UpdateMicrophoneState(); HotwordServiceFactory::GetInstance()->UpdateMicrophoneState();
if (audio_device_state_updated) { if (audio_device_state_updated_) {
bool audio_capture_allowed = bool audio_capture_allowed =
profile_->GetPrefs()->GetBoolean(prefs::kAudioCaptureAllowed); profile_->GetPrefs()->GetBoolean(prefs::kAudioCaptureAllowed);
if (!audio_capture_allowed || if (!audio_capture_allowed || !microphone_available_)
!HotwordServiceFactory::IsMicrophoneAvailable())
error_message_ = IDS_HOTWORD_MICROPHONE_ERROR_MESSAGE; error_message_ = IDS_HOTWORD_MICROPHONE_ERROR_MESSAGE;
} }
...@@ -772,6 +782,17 @@ void HotwordService::DisableHotwordPreferences() { ...@@ -772,6 +782,17 @@ void HotwordService::DisableHotwordPreferences() {
} }
} }
void HotwordService::OnUpdateAudioDevices(
const content::MediaStreamDevices& devices) {
bool microphone_was_available = microphone_available_;
microphone_available_ = !devices.empty();
audio_device_state_updated_ = true;
HotwordPrivateEventService* event_service =
BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
if (event_service && microphone_was_available != microphone_available_)
event_service->OnMicrophoneStateChanged(microphone_available_);
}
void HotwordService::OnHotwordAlwaysOnSearchEnabledChanged( void HotwordService::OnHotwordAlwaysOnSearchEnabledChanged(
const std::string& pref_name) { const std::string& pref_name) {
// If the pref for always on has been changed in some way, that means that // If the pref for always on has been changed in some way, that means that
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "base/prefs/pref_change_registrar.h" #include "base/prefs/pref_change_registrar.h"
#include "base/scoped_observer.h" #include "base/scoped_observer.h"
#include "chrome/browser/extensions/webstore_startup_installer.h" #include "chrome/browser/extensions/webstore_startup_installer.h"
#include "chrome/browser/media/media_capture_devices_dispatcher.h"
#include "chrome/common/extensions/webstore_install_result.h" #include "chrome/common/extensions/webstore_install_result.h"
#include "components/keyed_service/core/keyed_service.h" #include "components/keyed_service/core/keyed_service.h"
#include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_observer.h"
...@@ -38,7 +39,8 @@ extern const char kHotwordTrainingEnabled[]; ...@@ -38,7 +39,8 @@ extern const char kHotwordTrainingEnabled[];
// Provides an interface for the Hotword component that does voice triggered // Provides an interface for the Hotword component that does voice triggered
// search. // search.
class HotwordService : public extensions::ExtensionRegistryObserver, class HotwordService : public MediaCaptureDevicesDispatcher::Observer,
public extensions::ExtensionRegistryObserver,
public KeyedService { public KeyedService {
public: public:
// A simple subclass to allow for aborting an install during shutdown. // A simple subclass to allow for aborting an install during shutdown.
...@@ -125,6 +127,8 @@ class HotwordService : public extensions::ExtensionRegistryObserver, ...@@ -125,6 +127,8 @@ class HotwordService : public extensions::ExtensionRegistryObserver,
// no error. // no error.
int error_message() { return error_message_; } int error_message() { return error_message_; }
bool microphone_available() { return microphone_available_; }
// These methods are for launching, and getting and setting the launch mode of // These methods are for launching, and getting and setting the launch mode of
// the Hotword Audio Verification App. // the Hotword Audio Verification App.
// //
...@@ -174,6 +178,10 @@ class HotwordService : public extensions::ExtensionRegistryObserver, ...@@ -174,6 +178,10 @@ class HotwordService : public extensions::ExtensionRegistryObserver,
// Turn off the currently enabled version of hotwording if one exists. // Turn off the currently enabled version of hotwording if one exists.
void DisableHotwordPreferences(); void DisableHotwordPreferences();
// Overridden from MediaCaptureDevicesDispatcher::Observer
void OnUpdateAudioDevices(
const content::MediaStreamDevices& devices) override;
protected: protected:
// Used in test subclasses. // Used in test subclasses.
scoped_refptr<HotwordWebstoreInstaller> installer_; scoped_refptr<HotwordWebstoreInstaller> installer_;
...@@ -181,6 +189,10 @@ class HotwordService : public extensions::ExtensionRegistryObserver, ...@@ -181,6 +189,10 @@ class HotwordService : public extensions::ExtensionRegistryObserver,
private: private:
class HotwordUserSessionStateObserver; class HotwordUserSessionStateObserver;
// Must be called from the UI thread since the instance of
// MediaCaptureDevicesDispatcher can only be accessed on the UI thread.
void InitializeMicrophoneObserver();
// Callback for webstore extension installer. // Callback for webstore extension installer.
void InstalledFromWebstoreCallback( void InstalledFromWebstoreCallback(
int num_tries, int num_tries,
...@@ -207,6 +219,13 @@ class HotwordService : public extensions::ExtensionRegistryObserver, ...@@ -207,6 +219,13 @@ class HotwordService : public extensions::ExtensionRegistryObserver,
scoped_ptr<HotwordAudioHistoryHandler> audio_history_handler_; scoped_ptr<HotwordAudioHistoryHandler> audio_history_handler_;
bool microphone_available_;
// Indicates if the check for audio devices has been run such that it can be
// included in the error checking. Audio checking is not done immediately
// upon start up because of the negative impact on performance.
bool audio_device_state_updated_;
HotwordClient* client_; HotwordClient* client_;
int error_message_; int error_message_;
bool reinstall_pending_; bool reinstall_pending_;
......
...@@ -59,45 +59,16 @@ int HotwordServiceFactory::GetCurrentError(BrowserContext* context) { ...@@ -59,45 +59,16 @@ int HotwordServiceFactory::GetCurrentError(BrowserContext* context) {
return hotword_service->error_message(); return hotword_service->error_message();
} }
// static
bool HotwordServiceFactory::IsMicrophoneAvailable() {
return GetInstance()->microphone_available();
}
// static
bool HotwordServiceFactory::IsAudioDeviceStateUpdated() {
return GetInstance()->audio_device_state_updated();
}
HotwordServiceFactory::HotwordServiceFactory() HotwordServiceFactory::HotwordServiceFactory()
: BrowserContextKeyedServiceFactory( : BrowserContextKeyedServiceFactory(
"HotwordService", "HotwordService",
BrowserContextDependencyManager::GetInstance()), BrowserContextDependencyManager::GetInstance()) {
microphone_available_(false),
audio_device_state_updated_(false) {
// No dependencies. // No dependencies.
// Register with the device observer list to update the microphone
// availability.
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&HotwordServiceFactory::InitializeMicrophoneObserver,
base::Unretained(this)));
} }
HotwordServiceFactory::~HotwordServiceFactory() { HotwordServiceFactory::~HotwordServiceFactory() {
} }
void HotwordServiceFactory::InitializeMicrophoneObserver() {
MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
}
void HotwordServiceFactory::OnUpdateAudioDevices(
const content::MediaStreamDevices& devices) {
microphone_available_ = !devices.empty();
audio_device_state_updated_ = true;
}
void HotwordServiceFactory::UpdateMicrophoneState() { void HotwordServiceFactory::UpdateMicrophoneState() {
// In order to trigger the monitor, just call getAudioCaptureDevices. // In order to trigger the monitor, just call getAudioCaptureDevices.
content::MediaStreamDevices devices = content::MediaStreamDevices devices =
......
...@@ -6,15 +6,13 @@ ...@@ -6,15 +6,13 @@
#define CHROME_BROWSER_SEARCH_HOTWORD_SERVICE_FACTORY_H_ #define CHROME_BROWSER_SEARCH_HOTWORD_SERVICE_FACTORY_H_
#include "base/memory/singleton.h" #include "base/memory/singleton.h"
#include "chrome/browser/media/media_capture_devices_dispatcher.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h" #include "components/keyed_service/content/browser_context_keyed_service_factory.h"
class HotwordService; class HotwordService;
class Profile; class Profile;
// Singleton that owns all HotwordServices and associates them with Profiles. // Singleton that owns all HotwordServices and associates them with Profiles.
class HotwordServiceFactory : public MediaCaptureDevicesDispatcher::Observer, class HotwordServiceFactory : public BrowserContextKeyedServiceFactory {
public BrowserContextKeyedServiceFactory {
public: public:
// Returns the HotwordService for |context|. // Returns the HotwordService for |context|.
static HotwordService* GetForProfile(content::BrowserContext* context); static HotwordService* GetForProfile(content::BrowserContext* context);
...@@ -34,19 +32,6 @@ class HotwordServiceFactory : public MediaCaptureDevicesDispatcher::Observer, ...@@ -34,19 +32,6 @@ class HotwordServiceFactory : public MediaCaptureDevicesDispatcher::Observer,
// A value of 0 indicates no error. // A value of 0 indicates no error.
static int GetCurrentError(content::BrowserContext* context); static int GetCurrentError(content::BrowserContext* context);
// Returns the current known state of the microphone. Since this state
// is browser (not profile) specific, it resides in the factory.
static bool IsMicrophoneAvailable();
// Returns whether the state of the audio devices has been updated.
// Essentially it indicates the validity of the return value from
// IsMicrophoneAvailable().
static bool IsAudioDeviceStateUpdated();
// Overridden from MediaCaptureDevicesDispatcher::Observer
void OnUpdateAudioDevices(
const content::MediaStreamDevices& devices) override;
// This will kick off the monitor that calls OnUpdateAudioDevices when the // This will kick off the monitor that calls OnUpdateAudioDevices when the
// number of audio devices changes (or is initialized). It needs to be a // number of audio devices changes (or is initialized). It needs to be a
// separate function so it can be called after the service is initialized // separate function so it can be called after the service is initialized
...@@ -67,21 +52,6 @@ class HotwordServiceFactory : public MediaCaptureDevicesDispatcher::Observer, ...@@ -67,21 +52,6 @@ class HotwordServiceFactory : public MediaCaptureDevicesDispatcher::Observer,
KeyedService* BuildServiceInstanceFor( KeyedService* BuildServiceInstanceFor(
content::BrowserContext* context) const override; content::BrowserContext* context) const override;
// Must be called from the UI thread since the instance of
// MediaCaptureDevicesDispatcher can only be accessed on the UI thread.
void InitializeMicrophoneObserver();
bool microphone_available() { return microphone_available_; }
bool microphone_available_;
// Indicates if the check for audio devices has been run such that it can be
// included in the error checking. Audio checking is not done immediately
// upon start up because of the negative impact on performance.
bool audio_device_state_updated_;
bool audio_device_state_updated() { return audio_device_state_updated_; }
DISALLOW_COPY_AND_ASSIGN(HotwordServiceFactory); DISALLOW_COPY_AND_ASSIGN(HotwordServiceFactory);
}; };
......
...@@ -270,9 +270,11 @@ class VoiceSearchDomHandler : public WebUIMessageHandler { ...@@ -270,9 +270,11 @@ class VoiceSearchDomHandler : public WebUIMessageHandler {
AddPair(list, "NaCl Enabled", nacl_enabled); AddPair(list, "NaCl Enabled", nacl_enabled);
AddPair(list, HotwordService* hotword_service =
"Microphone", HotwordServiceFactory::GetForProfile(profile_);
HotwordServiceFactory::IsMicrophoneAvailable() ? "Yes" : "No"); AddPair(list, "Microphone",
hotword_service && hotword_service->microphone_available() ? "Yes"
: "No");
std::string audio_capture = "No"; std::string audio_capture = "No";
if (profile_->GetPrefs()->GetBoolean(prefs::kAudioCaptureAllowed)) if (profile_->GetPrefs()->GetBoolean(prefs::kAudioCaptureAllowed))
......
...@@ -182,5 +182,8 @@ namespace hotwordPrivate { ...@@ -182,5 +182,8 @@ namespace hotwordPrivate {
// Fired when the browser wants to find out whether the speaker model // Fired when the browser wants to find out whether the speaker model
// exists. // exists.
static void onSpeakerModelExists(); static void onSpeakerModelExists();
// Fired when the microphone state changes.
static void onMicrophoneStateChanged(boolean enabled);
}; };
}; };
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