[Media Router] Add integration tests and e2e tests for media router and presentation...
[chromium-blink-merge.git] / chrome / browser / search / hotword_service.cc
blob30d12f010a324afa2f8ce4716b43c627ca5502fb
1 // Copyright 2013 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/search/hotword_service.h"
7 #include <string>
9 #include "base/command_line.h"
10 #include "base/i18n/case_conversion.h"
11 #include "base/location.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/metrics/field_trial.h"
14 #include "base/metrics/histogram.h"
15 #include "base/metrics/sparse_histogram.h"
16 #include "base/path_service.h"
17 #include "base/prefs/pref_service.h"
18 #include "base/single_thread_task_runner.h"
19 #include "base/thread_task_runner_handle.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/chrome_notification_types.h"
22 #include "chrome/browser/extensions/api/hotword_private/hotword_private_api.h"
23 #include "chrome/browser/extensions/extension_service.h"
24 #include "chrome/browser/extensions/pending_extension_manager.h"
25 #include "chrome/browser/extensions/updater/extension_updater.h"
26 #include "chrome/browser/notifications/notification.h"
27 #include "chrome/browser/notifications/notification_ui_manager.h"
28 #include "chrome/browser/plugins/plugin_prefs.h"
29 #include "chrome/browser/profiles/profile.h"
30 #include "chrome/browser/profiles/profile_manager.h"
31 #include "chrome/browser/search/hotword_audio_history_handler.h"
32 #include "chrome/browser/search/hotword_service_factory.h"
33 #include "chrome/browser/ui/extensions/app_launch_params.h"
34 #include "chrome/browser/ui/extensions/application_launch.h"
35 #include "chrome/common/chrome_paths.h"
36 #include "chrome/common/chrome_switches.h"
37 #include "chrome/common/extensions/extension_constants.h"
38 #include "chrome/common/pref_names.h"
39 #include "chrome/grit/generated_resources.h"
40 #include "components/language_usage_metrics/language_usage_metrics.h"
41 #include "components/user_manager/user.h"
42 #include "components/user_manager/user_manager.h"
43 #include "content/public/browser/browser_thread.h"
44 #include "content/public/browser/notification_service.h"
45 #include "content/public/browser/plugin_service.h"
46 #include "content/public/common/webplugininfo.h"
47 #include "extensions/browser/extension_system.h"
48 #include "extensions/browser/uninstall_reason.h"
49 #include "extensions/common/constants.h"
50 #include "extensions/common/extension.h"
51 #include "extensions/common/one_shot_event.h"
52 #include "grit/theme_resources.h"
53 #include "ui/base/l10n/l10n_util.h"
54 #include "ui/base/resource/resource_bundle.h"
56 #if defined(OS_CHROMEOS)
57 #include "chromeos/audio/cras_audio_handler.h"
58 #endif
60 using extensions::BrowserContextKeyedAPIFactory;
61 using extensions::HotwordPrivateEventService;
63 namespace {
65 // Allowed locales for hotwording. Note that Chrome does not support all of
66 // these locales, condensing them to their 2-letter equivalent, but the full
67 // list is here for completeness and testing.
68 static const char* kSupportedLocales[] = {
69 "en",
70 "en_au",
71 "en_ca",
72 "en_gb",
73 "en_nz",
74 "en_us",
75 "en_za",
76 "de",
77 "de_at",
78 "de_de",
79 "es",
80 "es_419",
81 "es_es",
82 "fr",
83 "fr_fr",
84 "it",
85 "it_it",
86 "ja",
87 "ja_jp",
88 "ko",
89 "ko_kr",
90 "pt_br",
91 "ru",
92 "ru_ru"
95 // Maximum number of retries for installing the hotword shared module from the
96 // web store.
97 static const int kMaxInstallRetries = 2;
99 // Delay between retries for installing the hotword shared module from the web
100 // store.
101 static const int kInstallRetryDelaySeconds = 5;
103 // The extension id of the old hotword voice search trigger extension.
104 const char kHotwordOldExtensionId[] = "bepbmhgboaologfdajaanbcjmnhjmhfn";
106 // Enum describing the state of the hotword preference.
107 // This is used for UMA stats -- do not reorder or delete items; only add to
108 // the end.
109 enum HotwordEnabled {
110 UNSET = 0, // No hotword preference has been set.
111 ENABLED, // The (classic) hotword preference is enabled.
112 DISABLED, // All hotwording is disabled.
113 ALWAYS_ON_ENABLED, // Always-on hotwording is enabled.
114 NUM_HOTWORD_ENABLED_METRICS
117 // Enum describing the availability state of the hotword extension.
118 // This is used for UMA stats -- do not reorder or delete items; only add to
119 // the end.
120 enum HotwordExtensionAvailability {
121 UNAVAILABLE = 0,
122 AVAILABLE,
123 PENDING_DOWNLOAD,
124 DISABLED_EXTENSION,
125 NUM_HOTWORD_EXTENSION_AVAILABILITY_METRICS
128 // Enum describing the types of errors that can arise when determining
129 // if hotwording can be used. NO_ERROR is used so it can be seen how often
130 // errors arise relative to when they do not.
131 // This is used for UMA stats -- do not reorder or delete items; only add to
132 // the end.
133 enum HotwordError {
134 NO_HOTWORD_ERROR = 0,
135 GENERIC_HOTWORD_ERROR,
136 NACL_HOTWORD_ERROR,
137 MICROPHONE_HOTWORD_ERROR,
138 NUM_HOTWORD_ERROR_METRICS
141 void RecordLoggingMetrics(Profile* profile) {
142 // If the user is not opted in to hotword voice search, the audio logging
143 // metric is not valid so it is not recorded.
144 if (!profile->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled))
145 return;
147 UMA_HISTOGRAM_BOOLEAN(
148 "Hotword.HotwordAudioLogging",
149 profile->GetPrefs()->GetBoolean(prefs::kHotwordAudioLoggingEnabled));
152 void RecordErrorMetrics(int error_message) {
153 HotwordError error = NO_HOTWORD_ERROR;
154 switch (error_message) {
155 case IDS_HOTWORD_GENERIC_ERROR_MESSAGE:
156 error = GENERIC_HOTWORD_ERROR;
157 break;
158 case IDS_HOTWORD_NACL_DISABLED_ERROR_MESSAGE:
159 error = NACL_HOTWORD_ERROR;
160 break;
161 case IDS_HOTWORD_MICROPHONE_ERROR_MESSAGE:
162 error = MICROPHONE_HOTWORD_ERROR;
163 break;
164 default:
165 error = NO_HOTWORD_ERROR;
168 UMA_HISTOGRAM_ENUMERATION("Hotword.HotwordError",
169 error,
170 NUM_HOTWORD_ERROR_METRICS);
173 void RecordHotwordEnabledMetric(HotwordService *service, Profile* profile) {
174 HotwordEnabled enabled_state = DISABLED;
175 auto prefs = profile->GetPrefs();
176 if (!prefs->HasPrefPath(prefs::kHotwordSearchEnabled) &&
177 !prefs->HasPrefPath(prefs::kHotwordAlwaysOnSearchEnabled)) {
178 enabled_state = UNSET;
179 } else if (service->IsAlwaysOnEnabled()) {
180 enabled_state = ALWAYS_ON_ENABLED;
181 } else if (prefs->GetBoolean(prefs::kHotwordSearchEnabled)) {
182 enabled_state = ENABLED;
184 UMA_HISTOGRAM_ENUMERATION("Hotword.Enabled", enabled_state,
185 NUM_HOTWORD_ENABLED_METRICS);
188 ExtensionService* GetExtensionService(Profile* profile) {
189 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
191 extensions::ExtensionSystem* extension_system =
192 extensions::ExtensionSystem::Get(profile);
193 return extension_system ? extension_system->extension_service() : NULL;
196 std::string GetCurrentLocale(Profile* profile) {
197 #if defined(OS_CHROMEOS)
198 std::string profile_locale =
199 profile->GetPrefs()->GetString(prefs::kApplicationLocale);
200 if (!profile_locale.empty()) {
201 // On ChromeOS locale is per-profile, but only if set.
202 return profile_locale;
204 #endif
205 return g_browser_process->GetApplicationLocale();
208 } // namespace
210 namespace hotword_internal {
211 // String passed to indicate the training state has changed.
212 const char kHotwordTrainingEnabled[] = "hotword_training_enabled";
213 // Id of the hotword notification.
214 const char kHotwordNotificationId[] = "hotword";
215 // Notifier id for the hotword notification.
216 const char kHotwordNotifierId[] = "hotword.notification";
217 } // namespace hotword_internal
219 // Delegate for the hotword notification.
220 class HotwordNotificationDelegate : public NotificationDelegate {
221 public:
222 explicit HotwordNotificationDelegate(Profile* profile)
223 : profile_(profile) {
226 // Overridden from NotificationDelegate:
227 void ButtonClick(int button_index) override {
228 DCHECK_EQ(0, button_index);
229 Click();
232 void Click() override {
233 // Launch the hotword audio verification app in the right mode.
234 HotwordService::LaunchMode launch_mode =
235 HotwordService::HOTWORD_AND_AUDIO_HISTORY;
236 if (profile_->GetPrefs()->GetBoolean(
237 prefs::kHotwordAudioLoggingEnabled)) {
238 launch_mode = HotwordService::HOTWORD_ONLY;
241 HotwordService* hotword_service =
242 HotwordServiceFactory::GetForProfile(profile_);
244 if (!hotword_service)
245 return;
247 hotword_service->LaunchHotwordAudioVerificationApp(launch_mode);
249 // Close the notification after it's been clicked on to remove it
250 // from the notification tray.
251 g_browser_process->notification_ui_manager()->CancelById(
252 id(), NotificationUIManager::GetProfileID(profile_));
255 // Overridden from NotificationDelegate:
256 std::string id() const override {
257 return hotword_internal::kHotwordNotificationId;
260 private:
261 ~HotwordNotificationDelegate() override {}
263 Profile* profile_;
265 DISALLOW_COPY_AND_ASSIGN(HotwordNotificationDelegate);
268 // static
269 bool HotwordService::DoesHotwordSupportLanguage(Profile* profile) {
270 std::string normalized_locale =
271 l10n_util::NormalizeLocale(GetCurrentLocale(profile));
272 base::StringToLowerASCII(&normalized_locale);
274 // For M43, we are limiting always-on to en_us only.
275 // TODO(kcarattini): Remove this once
276 // https://code.google.com/p/chrome-os-partner/issues/detail?id=39227
277 // is fixed.
278 if (HotwordServiceFactory::IsAlwaysOnAvailable())
279 return normalized_locale == "en_us";
281 for (size_t i = 0; i < arraysize(kSupportedLocales); i++) {
282 if (normalized_locale == kSupportedLocales[i])
283 return true;
285 return false;
288 // static
289 bool HotwordService::IsHotwordHardwareAvailable() {
290 #if defined(OS_CHROMEOS)
291 if (chromeos::CrasAudioHandler::IsInitialized()) {
292 chromeos::AudioDeviceList devices;
293 chromeos::CrasAudioHandler::Get()->GetAudioDevices(&devices);
294 for (size_t i = 0; i < devices.size(); ++i) {
295 if (devices[i].type == chromeos::AUDIO_TYPE_AOKR) {
296 DCHECK(devices[i].is_input);
297 return true;
301 #endif
302 return false;
305 #if defined(OS_CHROMEOS)
306 class HotwordService::HotwordUserSessionStateObserver
307 : public user_manager::UserManager::UserSessionStateObserver {
308 public:
309 explicit HotwordUserSessionStateObserver(HotwordService* service)
310 : service_(service) {}
312 // Overridden from UserSessionStateObserver:
313 void ActiveUserChanged(const user_manager::User* active_user) override {
314 service_->ActiveUserChanged();
317 private:
318 HotwordService* service_; // Not owned
320 #else
321 // Dummy class to please the linker.
322 class HotwordService::HotwordUserSessionStateObserver {
324 #endif
326 void HotwordService::HotwordWebstoreInstaller::Shutdown() {
327 AbortInstall();
330 HotwordService::HotwordService(Profile* profile)
331 : profile_(profile),
332 extension_registry_observer_(this),
333 microphone_available_(false),
334 audio_device_state_updated_(false),
335 client_(NULL),
336 error_message_(0),
337 reinstall_pending_(false),
338 training_(false),
339 weak_factory_(this) {
340 extension_registry_observer_.Add(extensions::ExtensionRegistry::Get(profile));
342 // Disable the old extension so it doesn't interfere with the new stuff.
343 ExtensionService* extension_service = GetExtensionService(profile_);
344 if (extension_service) {
345 extension_service->DisableExtension(
346 kHotwordOldExtensionId,
347 extensions::Extension::DISABLE_USER_ACTION);
350 // This will be called during profile initialization which is a good time
351 // to check the user's hotword state.
352 RecordHotwordEnabledMetric(this, profile_);
354 pref_registrar_.Init(profile_->GetPrefs());
355 pref_registrar_.Add(
356 prefs::kHotwordAlwaysOnSearchEnabled,
357 base::Bind(&HotwordService::OnHotwordAlwaysOnSearchEnabledChanged,
358 base::Unretained(this)));
360 extensions::ExtensionSystem::Get(profile_)->ready().Post(
361 FROM_HERE,
362 base::Bind(base::IgnoreResult(
363 &HotwordService::MaybeReinstallHotwordExtension),
364 weak_factory_.GetWeakPtr()));
366 SetAudioHistoryHandler(new HotwordAudioHistoryHandler(
367 profile_, base::MessageLoop::current()->task_runner()));
369 if (HotwordServiceFactory::IsAlwaysOnAvailable() &&
370 IsHotwordAllowed()) {
371 // Show the hotword notification in 5 seconds if the experimental flag is
372 // on, or in 10 minutes if not. We need to wait at least a few seconds
373 // for the hotword extension to be installed.
374 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
375 if (command_line->HasSwitch(switches::kEnableExperimentalHotwordHardware)) {
376 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
377 FROM_HERE, base::Bind(&HotwordService::ShowHotwordNotification,
378 weak_factory_.GetWeakPtr()),
379 base::TimeDelta::FromSeconds(5));
380 } else if (!profile_->GetPrefs()->GetBoolean(
381 prefs::kHotwordAlwaysOnNotificationSeen)) {
382 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
383 FROM_HERE, base::Bind(&HotwordService::ShowHotwordNotification,
384 weak_factory_.GetWeakPtr()),
385 base::TimeDelta::FromMinutes(10));
389 #if defined(OS_CHROMEOS)
390 if (user_manager::UserManager::IsInitialized()) {
391 session_observer_.reset(new HotwordUserSessionStateObserver(this));
392 user_manager::UserManager::Get()->AddSessionStateObserver(
393 session_observer_.get());
395 #endif
397 // Register with the device observer list to update the microphone
398 // availability.
399 content::BrowserThread::PostTask(
400 content::BrowserThread::UI, FROM_HERE,
401 base::Bind(&HotwordService::InitializeMicrophoneObserver,
402 base::Unretained(this)));
405 HotwordService::~HotwordService() {
406 #if defined(OS_CHROMEOS)
407 if (user_manager::UserManager::IsInitialized() && session_observer_) {
408 user_manager::UserManager::Get()->RemoveSessionStateObserver(
409 session_observer_.get());
411 #endif
414 void HotwordService::Shutdown() {
415 if (installer_.get())
416 installer_->Shutdown();
419 void HotwordService::ShowHotwordNotification() {
420 // Check for enabled here in case always-on was enabled during the delay.
421 if (!IsServiceAvailable() || IsAlwaysOnEnabled())
422 return;
424 message_center::RichNotificationData data;
425 const base::string16 label = l10n_util::GetStringUTF16(
426 IDS_HOTWORD_NOTIFICATION_BUTTON);
427 data.buttons.push_back(message_center::ButtonInfo(label));
429 Notification notification(
430 message_center::NOTIFICATION_TYPE_SIMPLE,
431 GURL(),
432 l10n_util::GetStringUTF16(IDS_HOTWORD_NOTIFICATION_TITLE),
433 l10n_util::GetStringUTF16(IDS_HOTWORD_NOTIFICATION_DESCRIPTION),
434 ui::ResourceBundle::GetSharedInstance().GetImageNamed(
435 IDR_HOTWORD_NOTIFICATION_ICON),
436 message_center::NotifierId(
437 message_center::NotifierId::SYSTEM_COMPONENT,
438 hotword_internal::kHotwordNotifierId),
439 base::string16(),
440 std::string(),
441 data,
442 new HotwordNotificationDelegate(profile_));
444 g_browser_process->notification_ui_manager()->Add(notification, profile_);
445 profile_->GetPrefs()->SetBoolean(
446 prefs::kHotwordAlwaysOnNotificationSeen, true);
449 void HotwordService::OnExtensionUninstalled(
450 content::BrowserContext* browser_context,
451 const extensions::Extension* extension,
452 extensions::UninstallReason reason) {
453 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
455 if (extension->id() != extension_misc::kHotwordSharedModuleId ||
456 profile_ != Profile::FromBrowserContext(browser_context) ||
457 !GetExtensionService(profile_))
458 return;
460 // If the extension wasn't uninstalled due to language change, don't try to
461 // reinstall it.
462 if (!reinstall_pending_)
463 return;
465 InstallHotwordExtensionFromWebstore(kMaxInstallRetries);
466 SetPreviousLanguagePref();
469 std::string HotwordService::ReinstalledExtensionId() {
470 return extension_misc::kHotwordSharedModuleId;
473 void HotwordService::InitializeMicrophoneObserver() {
474 MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
477 void HotwordService::InstalledFromWebstoreCallback(
478 int num_tries,
479 bool success,
480 const std::string& error,
481 extensions::webstore_install::Result result) {
482 if (result != extensions::webstore_install::SUCCESS && num_tries) {
483 // Try again on failure.
484 content::BrowserThread::PostDelayedTask(
485 content::BrowserThread::UI,
486 FROM_HERE,
487 base::Bind(&HotwordService::InstallHotwordExtensionFromWebstore,
488 weak_factory_.GetWeakPtr(),
489 num_tries),
490 base::TimeDelta::FromSeconds(kInstallRetryDelaySeconds));
494 void HotwordService::InstallHotwordExtensionFromWebstore(int num_tries) {
495 installer_ = new HotwordWebstoreInstaller(
496 ReinstalledExtensionId(),
497 profile_,
498 base::Bind(&HotwordService::InstalledFromWebstoreCallback,
499 weak_factory_.GetWeakPtr(),
500 num_tries - 1));
501 installer_->BeginInstall();
504 void HotwordService::OnExtensionInstalled(
505 content::BrowserContext* browser_context,
506 const extensions::Extension* extension,
507 bool is_update) {
509 if (extension->id() != extension_misc::kHotwordSharedModuleId ||
510 profile_ != Profile::FromBrowserContext(browser_context))
511 return;
513 // If the previous locale pref has never been set, set it now since
514 // the extension has been installed.
515 if (!profile_->GetPrefs()->HasPrefPath(prefs::kHotwordPreviousLanguage))
516 SetPreviousLanguagePref();
518 // If MaybeReinstallHotwordExtension already triggered an uninstall, we
519 // don't want to loop and trigger another uninstall-install cycle.
520 // However, if we arrived here via an uninstall-triggered-install (and in
521 // that case |reinstall_pending_| will be true) then we know install
522 // has completed and we can reset |reinstall_pending_|.
523 if (!reinstall_pending_)
524 MaybeReinstallHotwordExtension();
525 else
526 reinstall_pending_ = false;
529 bool HotwordService::MaybeReinstallHotwordExtension() {
530 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
532 ExtensionService* extension_service = GetExtensionService(profile_);
533 if (!extension_service)
534 return false;
536 const extensions::Extension* extension = extension_service->GetExtensionById(
537 ReinstalledExtensionId(), true);
538 if (!extension)
539 return false;
541 // If the extension is currently pending, return and we'll check again
542 // after the install is finished.
543 extensions::PendingExtensionManager* pending_manager =
544 extension_service->pending_extension_manager();
545 if (pending_manager->IsIdPending(extension->id()))
546 return false;
548 // If there is already a pending request from HotwordService, don't try
549 // to uninstall either.
550 if (reinstall_pending_)
551 return false;
553 // Check if the current locale matches the previous. If they don't match,
554 // uninstall the extension.
555 if (!ShouldReinstallHotwordExtension())
556 return false;
558 // Ensure the call to OnExtensionUninstalled was triggered by a language
559 // change so it's okay to reinstall.
560 reinstall_pending_ = true;
562 // Disable always-on on a language change. We do this because the speaker-id
563 // model needs to be re-trained.
564 if (IsAlwaysOnEnabled()) {
565 profile_->GetPrefs()->SetBoolean(prefs::kHotwordAlwaysOnSearchEnabled,
566 false);
569 // Record re-installs due to language change.
570 UMA_HISTOGRAM_SPARSE_SLOWLY(
571 "Hotword.SharedModuleReinstallLanguage",
572 language_usage_metrics::LanguageUsageMetrics::ToLanguageCode(
573 GetCurrentLocale(profile_)));
574 return UninstallHotwordExtension(extension_service);
577 bool HotwordService::UninstallHotwordExtension(
578 ExtensionService* extension_service) {
579 base::string16 error;
580 std::string extension_id = ReinstalledExtensionId();
581 if (!extension_service->UninstallExtension(
582 extension_id,
583 extensions::UNINSTALL_REASON_INTERNAL_MANAGEMENT,
584 base::Bind(&base::DoNothing),
585 &error)) {
586 LOG(WARNING) << "Cannot uninstall extension with id "
587 << extension_id
588 << ": " << error;
589 reinstall_pending_ = false;
590 return false;
592 return true;
595 bool HotwordService::IsServiceAvailable() {
596 error_message_ = 0;
598 // Determine if the extension is available.
599 extensions::ExtensionSystem* system =
600 extensions::ExtensionSystem::Get(profile_);
601 ExtensionService* service = system->extension_service();
602 // Include disabled extensions (true parameter) since it may not be enabled
603 // if the user opted out.
604 const extensions::Extension* extension =
605 service->GetExtensionById(ReinstalledExtensionId(), true);
606 if (!extension)
607 error_message_ = IDS_HOTWORD_GENERIC_ERROR_MESSAGE;
609 // TODO(amistry): Record availability of shared module in UMA.
610 RecordLoggingMetrics(profile_);
612 // Determine if NaCl is available.
613 bool nacl_enabled = false;
614 base::FilePath path;
615 if (PathService::Get(chrome::FILE_NACL_PLUGIN, &path)) {
616 content::WebPluginInfo info;
617 PluginPrefs* plugin_prefs = PluginPrefs::GetForProfile(profile_).get();
618 if (content::PluginService::GetInstance()->GetPluginInfoByPath(path, &info))
619 nacl_enabled = plugin_prefs->IsPluginEnabled(info);
621 if (!nacl_enabled)
622 error_message_ = IDS_HOTWORD_NACL_DISABLED_ERROR_MESSAGE;
624 RecordErrorMetrics(error_message_);
626 // Determine if the proper audio capabilities exist. The first time this is
627 // called, it probably won't return in time, but that's why it won't be
628 // included in the error calculation. However, this use case is rare and
629 // typically the devices will be initialized by the time a user goes to
630 // settings.
631 HotwordServiceFactory::GetInstance()->UpdateMicrophoneState();
632 if (audio_device_state_updated_) {
633 bool audio_capture_allowed =
634 profile_->GetPrefs()->GetBoolean(prefs::kAudioCaptureAllowed);
635 if (!audio_capture_allowed || !microphone_available_)
636 error_message_ = IDS_HOTWORD_MICROPHONE_ERROR_MESSAGE;
639 return (error_message_ == 0) && IsHotwordAllowed();
642 bool HotwordService::IsHotwordAllowed() {
643 #if defined(ENABLE_HOTWORDING)
644 return DoesHotwordSupportLanguage(profile_);
645 #else
646 return false;
647 #endif
650 bool HotwordService::IsOptedIntoAudioLogging() {
651 // Do not opt the user in if the preference has not been set.
652 return
653 profile_->GetPrefs()->HasPrefPath(prefs::kHotwordAudioLoggingEnabled) &&
654 profile_->GetPrefs()->GetBoolean(prefs::kHotwordAudioLoggingEnabled);
657 bool HotwordService::IsAlwaysOnEnabled() {
658 return
659 profile_->GetPrefs()->HasPrefPath(prefs::kHotwordAlwaysOnSearchEnabled) &&
660 profile_->GetPrefs()->GetBoolean(prefs::kHotwordAlwaysOnSearchEnabled) &&
661 HotwordServiceFactory::IsAlwaysOnAvailable();
664 bool HotwordService::IsSometimesOnEnabled() {
665 return profile_->GetPrefs()->HasPrefPath(prefs::kHotwordSearchEnabled) &&
666 profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled) &&
667 !HotwordServiceFactory::IsAlwaysOnAvailable();
670 void HotwordService::SpeakerModelExistsComplete(bool exists) {
671 if (exists) {
672 profile_->GetPrefs()->
673 SetBoolean(prefs::kHotwordAlwaysOnSearchEnabled, true);
674 } else {
675 LaunchHotwordAudioVerificationApp(HotwordService::HOTWORD_ONLY);
679 void HotwordService::OptIntoHotwording(
680 const LaunchMode& launch_mode) {
681 // If the notification is in the notification tray, remove it (since the user
682 // is manually opting in to hotwording, they do not need the promotion).
683 g_browser_process->notification_ui_manager()->CancelById(
684 hotword_internal::kHotwordNotificationId,
685 NotificationUIManager::GetProfileID(profile_));
687 // First determine if we actually need to launch the app, or can just enable
688 // the pref. If Audio History has already been enabled, and we already have
689 // a speaker model, then we don't need to launch the app at all.
690 if (launch_mode == HotwordService::HOTWORD_ONLY) {
691 HotwordPrivateEventService* event_service =
692 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(
693 profile_);
694 if (event_service) {
695 event_service->OnSpeakerModelExists();
696 return;
700 LaunchHotwordAudioVerificationApp(launch_mode);
703 void HotwordService::LaunchHotwordAudioVerificationApp(
704 const LaunchMode& launch_mode) {
705 hotword_audio_verification_launch_mode_ = launch_mode;
707 ExtensionService* extension_service = GetExtensionService(profile_);
708 if (!extension_service)
709 return;
710 const extensions::Extension* extension = extension_service->GetExtensionById(
711 extension_misc::kHotwordAudioVerificationAppId, true);
712 if (!extension)
713 return;
715 OpenApplication(
716 AppLaunchParams(profile_, extension, extensions::LAUNCH_CONTAINER_WINDOW,
717 NEW_WINDOW, extensions::SOURCE_CHROME_INTERNAL));
720 HotwordService::LaunchMode
721 HotwordService::GetHotwordAudioVerificationLaunchMode() {
722 return hotword_audio_verification_launch_mode_;
725 void HotwordService::StartTraining() {
726 training_ = true;
728 if (!IsServiceAvailable())
729 return;
731 HotwordPrivateEventService* event_service =
732 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
733 if (event_service)
734 event_service->OnEnabledChanged(hotword_internal::kHotwordTrainingEnabled);
737 void HotwordService::FinalizeSpeakerModel() {
738 if (!IsServiceAvailable())
739 return;
741 HotwordPrivateEventService* event_service =
742 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
743 if (event_service)
744 event_service->OnFinalizeSpeakerModel();
747 void HotwordService::StopTraining() {
748 training_ = false;
750 if (!IsServiceAvailable())
751 return;
753 HotwordPrivateEventService* event_service =
754 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
755 if (event_service)
756 event_service->OnEnabledChanged(hotword_internal::kHotwordTrainingEnabled);
759 void HotwordService::NotifyHotwordTriggered() {
760 if (!IsServiceAvailable())
761 return;
763 HotwordPrivateEventService* event_service =
764 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
765 if (event_service)
766 event_service->OnHotwordTriggered();
769 bool HotwordService::IsTraining() {
770 return training_;
773 HotwordAudioHistoryHandler* HotwordService::GetAudioHistoryHandler() {
774 return audio_history_handler_.get();
777 void HotwordService::SetAudioHistoryHandler(
778 HotwordAudioHistoryHandler* handler) {
779 audio_history_handler_.reset(handler);
780 audio_history_handler_->UpdateAudioHistoryState();
783 void HotwordService::DisableHotwordPreferences() {
784 if (IsSometimesOnEnabled()) {
785 profile_->GetPrefs()->SetBoolean(prefs::kHotwordSearchEnabled, false);
787 if (IsAlwaysOnEnabled()) {
788 profile_->GetPrefs()->SetBoolean(prefs::kHotwordAlwaysOnSearchEnabled,
789 false);
793 void HotwordService::OnUpdateAudioDevices(
794 const content::MediaStreamDevices& devices) {
795 bool microphone_was_available = microphone_available_;
796 microphone_available_ = !devices.empty();
797 audio_device_state_updated_ = true;
798 HotwordPrivateEventService* event_service =
799 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
800 if (event_service && microphone_was_available != microphone_available_)
801 event_service->OnMicrophoneStateChanged(microphone_available_);
804 void HotwordService::OnHotwordAlwaysOnSearchEnabledChanged(
805 const std::string& pref_name) {
806 // If the pref for always on has been changed in some way, that means that
807 // the user is aware of always on (either from settings or another source)
808 // so they don't need to be shown the notification.
809 profile_->GetPrefs()->SetBoolean(prefs::kHotwordAlwaysOnNotificationSeen,
810 true);
811 pref_registrar_.Remove(prefs::kHotwordAlwaysOnSearchEnabled);
814 void HotwordService::RequestHotwordSession(HotwordClient* client) {
815 if (!IsServiceAvailable() || (client_ && client_ != client))
816 return;
818 client_ = client;
820 HotwordPrivateEventService* event_service =
821 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
822 if (event_service)
823 event_service->OnHotwordSessionRequested();
826 void HotwordService::StopHotwordSession(HotwordClient* client) {
827 if (!IsServiceAvailable())
828 return;
830 // Do nothing if there's no client.
831 if (!client_)
832 return;
833 DCHECK(client_ == client);
835 client_ = NULL;
836 HotwordPrivateEventService* event_service =
837 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
838 if (event_service)
839 event_service->OnHotwordSessionStopped();
842 void HotwordService::SetPreviousLanguagePref() {
843 profile_->GetPrefs()->SetString(prefs::kHotwordPreviousLanguage,
844 GetCurrentLocale(profile_));
847 bool HotwordService::ShouldReinstallHotwordExtension() {
848 // If there is no previous locale pref, then this is the first install
849 // so no need to uninstall first.
850 if (!profile_->GetPrefs()->HasPrefPath(prefs::kHotwordPreviousLanguage))
851 return false;
853 std::string previous_locale =
854 profile_->GetPrefs()->GetString(prefs::kHotwordPreviousLanguage);
855 std::string locale = GetCurrentLocale(profile_);
857 // If it's a new locale, then the old extension should be uninstalled.
858 return locale != previous_locale &&
859 HotwordService::DoesHotwordSupportLanguage(profile_);
862 void HotwordService::ActiveUserChanged() {
863 // Don't bother notifying the extension if hotwording is completely off.
864 if (!IsSometimesOnEnabled() && !IsAlwaysOnEnabled() && !IsTraining())
865 return;
867 HotwordPrivateEventService* event_service =
868 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
869 // "enabled" isn't being changed, but piggy-back off the notification anyway.
870 if (event_service)
871 event_service->OnEnabledChanged(prefs::kHotwordSearchEnabled);
874 bool HotwordService::UserIsActive() {
875 #if defined(OS_CHROMEOS)
876 // Only support multiple profiles and profile switching in ChromeOS.
877 if (user_manager::UserManager::IsInitialized()) {
878 user_manager::User* user =
879 user_manager::UserManager::Get()->GetActiveUser();
880 if (user && user->is_profile_created())
881 return profile_ == ProfileManager::GetActiveUserProfile();
883 #endif
884 return true;