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() {
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) {
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_);
if (!router || !router->HasEventListener(event_name))
return;
scoped_ptr<base::ListValue> args(new base::ListValue());
scoped_ptr<Event> event(new Event(event_name, args.Pass()));
router->BroadcastEvent(event.Pass());
}
......
......@@ -44,10 +44,14 @@ class HotwordPrivateEventService : public BrowserContextKeyedAPI {
void OnSpeakerModelExists();
void OnMicrophoneStateChanged(bool enabled);
private:
friend class BrowserContextKeyedAPIFactory<HotwordPrivateEventService>;
void SignalEvent(const std::string& event_name);
void SignalEvent(const std::string& event_name,
scoped_ptr<base::ListValue> args);
Profile* profile_;
PrefChangeRegistrar pref_change_registrar_;
......
......@@ -35,6 +35,8 @@ cr.define('hotword', function() {
this.tabCreatedListener_ = this.handleCreatedTab_.bind(this);
this.tabUpdatedListener_ = this.handleUpdatedTab_.bind(this);
this.tabActivatedListener_ = this.handleActivatedTab_.bind(this);
this.microphoneStateChangedListener_ =
this.handleMicrophoneStateChanged_.bind(this);
this.windowFocusChangedListener_ = this.handleChangedWindow_.bind(this);
this.messageListener_ = this.handleMessageFromPage_.bind(this);
......@@ -237,6 +239,19 @@ cr.define('hotword', function() {
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.
......@@ -492,6 +507,8 @@ cr.define('hotword', function() {
chrome.tabs.onActivated.addListener(this.tabActivatedListener_);
chrome.windows.onFocusChanged.addListener(
this.windowFocusChangedListener_);
chrome.hotwordPrivate.onMicrophoneStateChanged.addListener(
this.microphoneStateChangedListener_);
if (chrome.runtime.onMessage.hasListener(this.messageListener_))
return;
chrome.runtime.onMessageExternal.addListener(
......@@ -509,6 +526,8 @@ cr.define('hotword', function() {
chrome.tabs.onActivated.removeListener(this.tabActivatedListener_);
chrome.windows.onFocusChanged.removeListener(
this.windowFocusChangedListener_);
chrome.hotwordPrivate.onMicrophoneStateChanged.removeListener(
this.microphoneStateChangedListener_);
// Don't remove the Message listener, as we want them listening all
// the time,
},
......
......@@ -323,6 +323,8 @@ void HotwordService::HotwordWebstoreInstaller::Shutdown() {
HotwordService::HotwordService(Profile* profile)
: profile_(profile),
extension_registry_observer_(this),
microphone_available_(false),
audio_device_state_updated_(false),
client_(NULL),
error_message_(0),
reinstall_pending_(false),
......@@ -386,6 +388,13 @@ HotwordService::HotwordService(Profile* profile)
session_observer_.get());
}
#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() {
......@@ -456,6 +465,10 @@ std::string HotwordService::ReinstalledExtensionId() {
return extension_misc::kHotwordSharedModuleId;
}
void HotwordService::InitializeMicrophoneObserver() {
MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
}
void HotwordService::InstalledFromWebstoreCallback(
int num_tries,
bool success,
......@@ -605,19 +618,16 @@ bool HotwordService::IsServiceAvailable() {
RecordErrorMetrics(error_message_);
// Determine if the proper audio capabilities exist.
// The first time this is called, it probably won't return in time, but that's
// why it won't be included in the error calculation (i.e., the call to
// IsAudioDeviceStateUpdated()). However, this use case is rare and typically
// the devices will be initialized by the time a user goes to settings.
bool audio_device_state_updated =
HotwordServiceFactory::IsAudioDeviceStateUpdated();
// Determine if the proper audio capabilities exist. The first time this is
// called, it probably won't return in time, but that's why it won't be
// included in the error calculation. However, this use case is rare and
// typically the devices will be initialized by the time a user goes to
// settings.
HotwordServiceFactory::GetInstance()->UpdateMicrophoneState();
if (audio_device_state_updated) {
if (audio_device_state_updated_) {
bool audio_capture_allowed =
profile_->GetPrefs()->GetBoolean(prefs::kAudioCaptureAllowed);
if (!audio_capture_allowed ||
!HotwordServiceFactory::IsMicrophoneAvailable())
if (!audio_capture_allowed || !microphone_available_)
error_message_ = IDS_HOTWORD_MICROPHONE_ERROR_MESSAGE;
}
......@@ -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(
const std::string& pref_name) {
// If the pref for always on has been changed in some way, that means that
......
......@@ -12,6 +12,7 @@
#include "base/prefs/pref_change_registrar.h"
#include "base/scoped_observer.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 "components/keyed_service/core/keyed_service.h"
#include "content/public/browser/notification_observer.h"
......@@ -38,7 +39,8 @@ extern const char kHotwordTrainingEnabled[];
// Provides an interface for the Hotword component that does voice triggered
// search.
class HotwordService : public extensions::ExtensionRegistryObserver,
class HotwordService : public MediaCaptureDevicesDispatcher::Observer,
public extensions::ExtensionRegistryObserver,
public KeyedService {
public:
// A simple subclass to allow for aborting an install during shutdown.
......@@ -125,6 +127,8 @@ class HotwordService : public extensions::ExtensionRegistryObserver,
// no error.
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
// the Hotword Audio Verification App.
//
......@@ -174,6 +178,10 @@ class HotwordService : public extensions::ExtensionRegistryObserver,
// Turn off the currently enabled version of hotwording if one exists.
void DisableHotwordPreferences();
// Overridden from MediaCaptureDevicesDispatcher::Observer
void OnUpdateAudioDevices(
const content::MediaStreamDevices& devices) override;
protected:
// Used in test subclasses.
scoped_refptr<HotwordWebstoreInstaller> installer_;
......@@ -181,6 +189,10 @@ class HotwordService : public extensions::ExtensionRegistryObserver,
private:
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.
void InstalledFromWebstoreCallback(
int num_tries,
......@@ -207,6 +219,13 @@ class HotwordService : public extensions::ExtensionRegistryObserver,
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_;
int error_message_;
bool reinstall_pending_;
......
......@@ -59,45 +59,16 @@ int HotwordServiceFactory::GetCurrentError(BrowserContext* context) {
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()
: BrowserContextKeyedServiceFactory(
"HotwordService",
BrowserContextDependencyManager::GetInstance()),
microphone_available_(false),
audio_device_state_updated_(false) {
BrowserContextDependencyManager::GetInstance()) {
// 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() {
}
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() {
// In order to trigger the monitor, just call getAudioCaptureDevices.
content::MediaStreamDevices devices =
......
......@@ -6,15 +6,13 @@
#define CHROME_BROWSER_SEARCH_HOTWORD_SERVICE_FACTORY_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"
class HotwordService;
class Profile;
// Singleton that owns all HotwordServices and associates them with Profiles.
class HotwordServiceFactory : public MediaCaptureDevicesDispatcher::Observer,
public BrowserContextKeyedServiceFactory {
class HotwordServiceFactory : public BrowserContextKeyedServiceFactory {
public:
// Returns the HotwordService for |context|.
static HotwordService* GetForProfile(content::BrowserContext* context);
......@@ -34,19 +32,6 @@ class HotwordServiceFactory : public MediaCaptureDevicesDispatcher::Observer,
// A value of 0 indicates no error.
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
// 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
......@@ -67,21 +52,6 @@ class HotwordServiceFactory : public MediaCaptureDevicesDispatcher::Observer,
KeyedService* BuildServiceInstanceFor(
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);
};
......
......@@ -270,9 +270,11 @@ class VoiceSearchDomHandler : public WebUIMessageHandler {
AddPair(list, "NaCl Enabled", nacl_enabled);
AddPair(list,
"Microphone",
HotwordServiceFactory::IsMicrophoneAvailable() ? "Yes" : "No");
HotwordService* hotword_service =
HotwordServiceFactory::GetForProfile(profile_);
AddPair(list, "Microphone",
hotword_service && hotword_service->microphone_available() ? "Yes"
: "No");
std::string audio_capture = "No";
if (profile_->GetPrefs()->GetBoolean(prefs::kAudioCaptureAllowed))
......
......@@ -182,5 +182,8 @@ namespace hotwordPrivate {
// Fired when the browser wants to find out whether the speaker model
// exists.
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