Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / search / hotword_service.cc
blob6ee699fbab60557b516e3eacf3c55bf23447f387
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/message_loop/message_loop.h"
12 #include "base/metrics/field_trial.h"
13 #include "base/metrics/histogram.h"
14 #include "base/metrics/sparse_histogram.h"
15 #include "base/path_service.h"
16 #include "base/prefs/pref_service.h"
17 #include "chrome/browser/browser_process.h"
18 #include "chrome/browser/chrome_notification_types.h"
19 #include "chrome/browser/extensions/api/hotword_private/hotword_private_api.h"
20 #include "chrome/browser/extensions/extension_service.h"
21 #include "chrome/browser/extensions/pending_extension_manager.h"
22 #include "chrome/browser/extensions/updater/extension_updater.h"
23 #include "chrome/browser/notifications/notification.h"
24 #include "chrome/browser/notifications/notification_ui_manager.h"
25 #include "chrome/browser/plugins/plugin_prefs.h"
26 #include "chrome/browser/profiles/profile.h"
27 #include "chrome/browser/profiles/profile_manager.h"
28 #include "chrome/browser/search/hotword_audio_history_handler.h"
29 #include "chrome/browser/search/hotword_service_factory.h"
30 #include "chrome/browser/ui/extensions/app_launch_params.h"
31 #include "chrome/browser/ui/extensions/application_launch.h"
32 #include "chrome/common/chrome_paths.h"
33 #include "chrome/common/chrome_switches.h"
34 #include "chrome/common/extensions/extension_constants.h"
35 #include "chrome/common/pref_names.h"
36 #include "chrome/grit/generated_resources.h"
37 #include "components/language_usage_metrics/language_usage_metrics.h"
38 #include "components/user_manager/user.h"
39 #include "components/user_manager/user_manager.h"
40 #include "content/public/browser/browser_thread.h"
41 #include "content/public/browser/notification_service.h"
42 #include "content/public/browser/plugin_service.h"
43 #include "content/public/common/webplugininfo.h"
44 #include "extensions/browser/extension_system.h"
45 #include "extensions/browser/uninstall_reason.h"
46 #include "extensions/common/constants.h"
47 #include "extensions/common/extension.h"
48 #include "extensions/common/one_shot_event.h"
49 #include "grit/theme_resources.h"
50 #include "ui/base/l10n/l10n_util.h"
51 #include "ui/base/resource/resource_bundle.h"
53 #if defined(OS_CHROMEOS)
54 #include "chromeos/audio/cras_audio_handler.h"
55 #endif
57 using extensions::BrowserContextKeyedAPIFactory;
58 using extensions::HotwordPrivateEventService;
60 namespace {
62 // Allowed locales for hotwording. Note that Chrome does not support all of
63 // these locales, condensing them to their 2-letter equivalent, but the full
64 // list is here for completeness and testing.
65 static const char* kSupportedLocales[] = {
66 "en",
67 "en_au",
68 "en_ca",
69 "en_gb",
70 "en_nz",
71 "en_us",
72 "en_za",
73 "de",
74 "de_at",
75 "de_de",
76 "es",
77 "es_419",
78 "es_es",
79 "fr",
80 "fr_fr",
81 "it",
82 "it_it",
83 "ja",
84 "ja_jp",
85 "ko",
86 "ko_kr",
87 "pt_br",
88 "ru",
89 "ru_ru"
92 // Maximum number of retries for installing the hotword shared module from the
93 // web store.
94 static const int kMaxInstallRetries = 2;
96 // Delay between retries for installing the hotword shared module from the web
97 // store.
98 static const int kInstallRetryDelaySeconds = 5;
100 // The extension id of the old hotword voice search trigger extension.
101 const char kHotwordOldExtensionId[] = "bepbmhgboaologfdajaanbcjmnhjmhfn";
103 // Enum describing the state of the hotword preference.
104 // This is used for UMA stats -- do not reorder or delete items; only add to
105 // the end.
106 enum HotwordEnabled {
107 UNSET = 0, // No hotword preference has been set.
108 ENABLED, // The (classic) hotword preference is enabled.
109 DISABLED, // All hotwording is disabled.
110 ALWAYS_ON_ENABLED, // Always-on hotwording is enabled.
111 NUM_HOTWORD_ENABLED_METRICS
114 // Enum describing the availability state of the hotword extension.
115 // This is used for UMA stats -- do not reorder or delete items; only add to
116 // the end.
117 enum HotwordExtensionAvailability {
118 UNAVAILABLE = 0,
119 AVAILABLE,
120 PENDING_DOWNLOAD,
121 DISABLED_EXTENSION,
122 NUM_HOTWORD_EXTENSION_AVAILABILITY_METRICS
125 // Enum describing the types of errors that can arise when determining
126 // if hotwording can be used. NO_ERROR is used so it can be seen how often
127 // errors arise relative to when they do not.
128 // This is used for UMA stats -- do not reorder or delete items; only add to
129 // the end.
130 enum HotwordError {
131 NO_HOTWORD_ERROR = 0,
132 GENERIC_HOTWORD_ERROR,
133 NACL_HOTWORD_ERROR,
134 MICROPHONE_HOTWORD_ERROR,
135 NUM_HOTWORD_ERROR_METRICS
138 void RecordLoggingMetrics(Profile* profile) {
139 // If the user is not opted in to hotword voice search, the audio logging
140 // metric is not valid so it is not recorded.
141 if (!profile->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled))
142 return;
144 UMA_HISTOGRAM_BOOLEAN(
145 "Hotword.HotwordAudioLogging",
146 profile->GetPrefs()->GetBoolean(prefs::kHotwordAudioLoggingEnabled));
149 void RecordErrorMetrics(int error_message) {
150 HotwordError error = NO_HOTWORD_ERROR;
151 switch (error_message) {
152 case IDS_HOTWORD_GENERIC_ERROR_MESSAGE:
153 error = GENERIC_HOTWORD_ERROR;
154 break;
155 case IDS_HOTWORD_NACL_DISABLED_ERROR_MESSAGE:
156 error = NACL_HOTWORD_ERROR;
157 break;
158 case IDS_HOTWORD_MICROPHONE_ERROR_MESSAGE:
159 error = MICROPHONE_HOTWORD_ERROR;
160 break;
161 default:
162 error = NO_HOTWORD_ERROR;
165 UMA_HISTOGRAM_ENUMERATION("Hotword.HotwordError",
166 error,
167 NUM_HOTWORD_ERROR_METRICS);
170 void RecordHotwordEnabledMetric(HotwordService *service, Profile* profile) {
171 HotwordEnabled enabled_state = DISABLED;
172 auto prefs = profile->GetPrefs();
173 if (!prefs->HasPrefPath(prefs::kHotwordSearchEnabled) &&
174 !prefs->HasPrefPath(prefs::kHotwordAlwaysOnSearchEnabled)) {
175 enabled_state = UNSET;
176 } else if (service->IsAlwaysOnEnabled()) {
177 enabled_state = ALWAYS_ON_ENABLED;
178 } else if (prefs->GetBoolean(prefs::kHotwordSearchEnabled)) {
179 enabled_state = ENABLED;
181 UMA_HISTOGRAM_ENUMERATION("Hotword.Enabled", enabled_state,
182 NUM_HOTWORD_ENABLED_METRICS);
185 ExtensionService* GetExtensionService(Profile* profile) {
186 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
188 extensions::ExtensionSystem* extension_system =
189 extensions::ExtensionSystem::Get(profile);
190 return extension_system ? extension_system->extension_service() : NULL;
193 std::string GetCurrentLocale(Profile* profile) {
194 #if defined(OS_CHROMEOS)
195 std::string profile_locale =
196 profile->GetPrefs()->GetString(prefs::kApplicationLocale);
197 if (!profile_locale.empty()) {
198 // On ChromeOS locale is per-profile, but only if set.
199 return profile_locale;
201 #endif
202 return g_browser_process->GetApplicationLocale();
205 } // namespace
207 namespace hotword_internal {
208 // Constants for the hotword field trial.
209 const char kHotwordFieldTrialName[] = "VoiceTrigger";
210 const char kHotwordFieldTrialDisabledGroupName[] = "Disabled";
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 (size_t i = 0; i < arraysize(kSupportedLocales); i++) {
275 if (normalized_locale == kSupportedLocales[i])
276 return true;
278 return false;
281 // static
282 bool HotwordService::IsHotwordHardwareAvailable() {
283 #if defined(OS_CHROMEOS)
284 if (chromeos::CrasAudioHandler::IsInitialized()) {
285 chromeos::AudioDeviceList devices;
286 chromeos::CrasAudioHandler::Get()->GetAudioDevices(&devices);
287 for (size_t i = 0; i < devices.size(); ++i) {
288 if (devices[i].type == chromeos::AUDIO_TYPE_AOKR) {
289 DCHECK(devices[i].is_input);
290 return true;
294 #endif
295 return false;
298 #if defined(OS_CHROMEOS)
299 class HotwordService::HotwordUserSessionStateObserver
300 : public user_manager::UserManager::UserSessionStateObserver {
301 public:
302 explicit HotwordUserSessionStateObserver(HotwordService* service)
303 : service_(service) {}
305 // Overridden from UserSessionStateObserver:
306 void ActiveUserChanged(const user_manager::User* active_user) override {
307 service_->ActiveUserChanged();
310 private:
311 HotwordService* service_; // Not owned
313 #else
314 // Dummy class to please the linker.
315 class HotwordService::HotwordUserSessionStateObserver {
317 #endif
319 void HotwordService::HotwordWebstoreInstaller::Shutdown() {
320 AbortInstall();
323 HotwordService::HotwordService(Profile* profile)
324 : profile_(profile),
325 extension_registry_observer_(this),
326 client_(NULL),
327 error_message_(0),
328 reinstall_pending_(false),
329 training_(false),
330 weak_factory_(this) {
331 extension_registry_observer_.Add(extensions::ExtensionRegistry::Get(profile));
333 // Disable the old extension so it doesn't interfere with the new stuff.
334 ExtensionService* extension_service = GetExtensionService(profile_);
335 if (extension_service) {
336 extension_service->DisableExtension(
337 kHotwordOldExtensionId,
338 extensions::Extension::DISABLE_USER_ACTION);
341 // This will be called during profile initialization which is a good time
342 // to check the user's hotword state.
343 RecordHotwordEnabledMetric(this, profile_);
345 pref_registrar_.Init(profile_->GetPrefs());
346 pref_registrar_.Add(
347 prefs::kHotwordAlwaysOnSearchEnabled,
348 base::Bind(&HotwordService::OnHotwordAlwaysOnSearchEnabledChanged,
349 base::Unretained(this)));
351 extensions::ExtensionSystem::Get(profile_)->ready().Post(
352 FROM_HERE,
353 base::Bind(base::IgnoreResult(
354 &HotwordService::MaybeReinstallHotwordExtension),
355 weak_factory_.GetWeakPtr()));
357 SetAudioHistoryHandler(new HotwordAudioHistoryHandler(
358 profile_, base::MessageLoop::current()->task_runner()));
360 if (HotwordServiceFactory::IsAlwaysOnAvailable() &&
361 IsHotwordAllowed()) {
362 // Show the hotword notification in 5 seconds if the experimental flag is
363 // on, or in 10 minutes if not. We need to wait at least a few seconds
364 // for the hotword extension to be installed.
365 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
366 if (command_line->HasSwitch(switches::kEnableExperimentalHotwordHardware)) {
367 base::MessageLoop::current()->PostDelayedTask(
368 FROM_HERE,
369 base::Bind(&HotwordService::ShowHotwordNotification,
370 weak_factory_.GetWeakPtr()),
371 base::TimeDelta::FromSeconds(5));
372 } else if (!profile_->GetPrefs()->GetBoolean(
373 prefs::kHotwordAlwaysOnNotificationSeen)) {
374 base::MessageLoop::current()->PostDelayedTask(
375 FROM_HERE,
376 base::Bind(&HotwordService::ShowHotwordNotification,
377 weak_factory_.GetWeakPtr()),
378 base::TimeDelta::FromMinutes(10));
382 #if defined(OS_CHROMEOS)
383 if (user_manager::UserManager::IsInitialized()) {
384 session_observer_.reset(new HotwordUserSessionStateObserver(this));
385 user_manager::UserManager::Get()->AddSessionStateObserver(
386 session_observer_.get());
388 #endif
391 HotwordService::~HotwordService() {
392 #if defined(OS_CHROMEOS)
393 if (user_manager::UserManager::IsInitialized() && session_observer_) {
394 user_manager::UserManager::Get()->RemoveSessionStateObserver(
395 session_observer_.get());
397 #endif
400 void HotwordService::Shutdown() {
401 if (installer_.get())
402 installer_->Shutdown();
405 void HotwordService::ShowHotwordNotification() {
406 // Check for enabled here in case always-on was enabled during the delay.
407 if (!IsServiceAvailable() || IsAlwaysOnEnabled())
408 return;
410 message_center::RichNotificationData data;
411 const base::string16 label = l10n_util::GetStringUTF16(
412 IDS_HOTWORD_NOTIFICATION_BUTTON);
413 data.buttons.push_back(message_center::ButtonInfo(label));
415 Notification notification(
416 message_center::NOTIFICATION_TYPE_SIMPLE,
417 GURL(),
418 l10n_util::GetStringUTF16(IDS_HOTWORD_NOTIFICATION_TITLE),
419 l10n_util::GetStringUTF16(IDS_HOTWORD_NOTIFICATION_DESCRIPTION),
420 ui::ResourceBundle::GetSharedInstance().GetImageNamed(
421 IDR_HOTWORD_NOTIFICATION_ICON),
422 message_center::NotifierId(
423 message_center::NotifierId::SYSTEM_COMPONENT,
424 hotword_internal::kHotwordNotifierId),
425 base::string16(),
426 std::string(),
427 data,
428 new HotwordNotificationDelegate(profile_));
430 g_browser_process->notification_ui_manager()->Add(notification, profile_);
431 profile_->GetPrefs()->SetBoolean(
432 prefs::kHotwordAlwaysOnNotificationSeen, true);
435 void HotwordService::OnExtensionUninstalled(
436 content::BrowserContext* browser_context,
437 const extensions::Extension* extension,
438 extensions::UninstallReason reason) {
439 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
441 if (extension->id() != extension_misc::kHotwordSharedModuleId ||
442 profile_ != Profile::FromBrowserContext(browser_context) ||
443 !GetExtensionService(profile_))
444 return;
446 // If the extension wasn't uninstalled due to language change, don't try to
447 // reinstall it.
448 if (!reinstall_pending_)
449 return;
451 InstallHotwordExtensionFromWebstore(kMaxInstallRetries);
452 SetPreviousLanguagePref();
455 std::string HotwordService::ReinstalledExtensionId() {
456 return extension_misc::kHotwordSharedModuleId;
459 void HotwordService::InstalledFromWebstoreCallback(
460 int num_tries,
461 bool success,
462 const std::string& error,
463 extensions::webstore_install::Result result) {
464 if (result != extensions::webstore_install::SUCCESS && num_tries) {
465 // Try again on failure.
466 content::BrowserThread::PostDelayedTask(
467 content::BrowserThread::UI,
468 FROM_HERE,
469 base::Bind(&HotwordService::InstallHotwordExtensionFromWebstore,
470 weak_factory_.GetWeakPtr(),
471 num_tries),
472 base::TimeDelta::FromSeconds(kInstallRetryDelaySeconds));
476 void HotwordService::InstallHotwordExtensionFromWebstore(int num_tries) {
477 installer_ = new HotwordWebstoreInstaller(
478 ReinstalledExtensionId(),
479 profile_,
480 base::Bind(&HotwordService::InstalledFromWebstoreCallback,
481 weak_factory_.GetWeakPtr(),
482 num_tries - 1));
483 installer_->BeginInstall();
486 void HotwordService::OnExtensionInstalled(
487 content::BrowserContext* browser_context,
488 const extensions::Extension* extension,
489 bool is_update) {
491 if (extension->id() != extension_misc::kHotwordSharedModuleId ||
492 profile_ != Profile::FromBrowserContext(browser_context))
493 return;
495 // If the previous locale pref has never been set, set it now since
496 // the extension has been installed.
497 if (!profile_->GetPrefs()->HasPrefPath(prefs::kHotwordPreviousLanguage))
498 SetPreviousLanguagePref();
500 // If MaybeReinstallHotwordExtension already triggered an uninstall, we
501 // don't want to loop and trigger another uninstall-install cycle.
502 // However, if we arrived here via an uninstall-triggered-install (and in
503 // that case |reinstall_pending_| will be true) then we know install
504 // has completed and we can reset |reinstall_pending_|.
505 if (!reinstall_pending_)
506 MaybeReinstallHotwordExtension();
507 else
508 reinstall_pending_ = false;
511 bool HotwordService::MaybeReinstallHotwordExtension() {
512 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
514 ExtensionService* extension_service = GetExtensionService(profile_);
515 if (!extension_service)
516 return false;
518 const extensions::Extension* extension = extension_service->GetExtensionById(
519 ReinstalledExtensionId(), true);
520 if (!extension)
521 return false;
523 // If the extension is currently pending, return and we'll check again
524 // after the install is finished.
525 extensions::PendingExtensionManager* pending_manager =
526 extension_service->pending_extension_manager();
527 if (pending_manager->IsIdPending(extension->id()))
528 return false;
530 // If there is already a pending request from HotwordService, don't try
531 // to uninstall either.
532 if (reinstall_pending_)
533 return false;
535 // Check if the current locale matches the previous. If they don't match,
536 // uninstall the extension.
537 if (!ShouldReinstallHotwordExtension())
538 return false;
540 // Ensure the call to OnExtensionUninstalled was triggered by a language
541 // change so it's okay to reinstall.
542 reinstall_pending_ = true;
544 // Disable always-on on a language change. We do this because the speaker-id
545 // model needs to be re-trained.
546 if (IsAlwaysOnEnabled()) {
547 profile_->GetPrefs()->SetBoolean(prefs::kHotwordAlwaysOnSearchEnabled,
548 false);
551 // Record re-installs due to language change.
552 UMA_HISTOGRAM_SPARSE_SLOWLY(
553 "Hotword.SharedModuleReinstallLanguage",
554 language_usage_metrics::LanguageUsageMetrics::ToLanguageCode(
555 GetCurrentLocale(profile_)));
556 return UninstallHotwordExtension(extension_service);
559 bool HotwordService::UninstallHotwordExtension(
560 ExtensionService* extension_service) {
561 base::string16 error;
562 std::string extension_id = ReinstalledExtensionId();
563 if (!extension_service->UninstallExtension(
564 extension_id,
565 extensions::UNINSTALL_REASON_INTERNAL_MANAGEMENT,
566 base::Bind(&base::DoNothing),
567 &error)) {
568 LOG(WARNING) << "Cannot uninstall extension with id "
569 << extension_id
570 << ": " << error;
571 reinstall_pending_ = false;
572 return false;
574 return true;
577 bool HotwordService::IsServiceAvailable() {
578 error_message_ = 0;
580 // Determine if the extension is available.
581 extensions::ExtensionSystem* system =
582 extensions::ExtensionSystem::Get(profile_);
583 ExtensionService* service = system->extension_service();
584 // Include disabled extensions (true parameter) since it may not be enabled
585 // if the user opted out.
586 const extensions::Extension* extension =
587 service->GetExtensionById(ReinstalledExtensionId(), true);
588 if (!extension)
589 error_message_ = IDS_HOTWORD_GENERIC_ERROR_MESSAGE;
591 // TODO(amistry): Record availability of shared module in UMA.
592 RecordLoggingMetrics(profile_);
594 // Determine if NaCl is available.
595 bool nacl_enabled = false;
596 base::FilePath path;
597 if (PathService::Get(chrome::FILE_NACL_PLUGIN, &path)) {
598 content::WebPluginInfo info;
599 PluginPrefs* plugin_prefs = PluginPrefs::GetForProfile(profile_).get();
600 if (content::PluginService::GetInstance()->GetPluginInfoByPath(path, &info))
601 nacl_enabled = plugin_prefs->IsPluginEnabled(info);
603 if (!nacl_enabled)
604 error_message_ = IDS_HOTWORD_NACL_DISABLED_ERROR_MESSAGE;
606 RecordErrorMetrics(error_message_);
608 // Determine if the proper audio capabilities exist.
609 // The first time this is called, it probably won't return in time, but that's
610 // why it won't be included in the error calculation (i.e., the call to
611 // IsAudioDeviceStateUpdated()). However, this use case is rare and typically
612 // the devices will be initialized by the time a user goes to settings.
613 bool audio_device_state_updated =
614 HotwordServiceFactory::IsAudioDeviceStateUpdated();
615 HotwordServiceFactory::GetInstance()->UpdateMicrophoneState();
616 if (audio_device_state_updated) {
617 bool audio_capture_allowed =
618 profile_->GetPrefs()->GetBoolean(prefs::kAudioCaptureAllowed);
619 if (!audio_capture_allowed ||
620 !HotwordServiceFactory::IsMicrophoneAvailable())
621 error_message_ = IDS_HOTWORD_MICROPHONE_ERROR_MESSAGE;
624 return (error_message_ == 0) && IsHotwordAllowed();
627 bool HotwordService::IsHotwordAllowed() {
628 std::string group = base::FieldTrialList::FindFullName(
629 hotword_internal::kHotwordFieldTrialName);
630 // Allow hotwording by default, and only disable if the field trial has been
631 // set.
632 if (group == hotword_internal::kHotwordFieldTrialDisabledGroupName)
633 return false;
635 return DoesHotwordSupportLanguage(profile_);
638 bool HotwordService::IsOptedIntoAudioLogging() {
639 // Do not opt the user in if the preference has not been set.
640 return
641 profile_->GetPrefs()->HasPrefPath(prefs::kHotwordAudioLoggingEnabled) &&
642 profile_->GetPrefs()->GetBoolean(prefs::kHotwordAudioLoggingEnabled);
645 bool HotwordService::IsAlwaysOnEnabled() {
646 return
647 profile_->GetPrefs()->HasPrefPath(prefs::kHotwordAlwaysOnSearchEnabled) &&
648 profile_->GetPrefs()->GetBoolean(prefs::kHotwordAlwaysOnSearchEnabled) &&
649 HotwordServiceFactory::IsAlwaysOnAvailable();
652 bool HotwordService::IsSometimesOnEnabled() {
653 return profile_->GetPrefs()->HasPrefPath(prefs::kHotwordSearchEnabled) &&
654 profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled) &&
655 !HotwordServiceFactory::IsAlwaysOnAvailable();
658 void HotwordService::SpeakerModelExistsComplete(bool exists) {
659 if (exists) {
660 profile_->GetPrefs()->
661 SetBoolean(prefs::kHotwordAlwaysOnSearchEnabled, true);
662 } else {
663 LaunchHotwordAudioVerificationApp(HotwordService::HOTWORD_ONLY);
667 void HotwordService::OptIntoHotwording(
668 const LaunchMode& launch_mode) {
669 // First determine if we actually need to launch the app, or can just enable
670 // the pref. If Audio History has already been enabled, and we already have
671 // a speaker model, then we don't need to launch the app at all.
672 if (launch_mode == HotwordService::HOTWORD_ONLY) {
673 HotwordPrivateEventService* event_service =
674 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(
675 profile_);
676 if (event_service) {
677 event_service->OnSpeakerModelExists();
678 return;
682 LaunchHotwordAudioVerificationApp(launch_mode);
685 void HotwordService::LaunchHotwordAudioVerificationApp(
686 const LaunchMode& launch_mode) {
687 hotword_audio_verification_launch_mode_ = launch_mode;
689 ExtensionService* extension_service = GetExtensionService(profile_);
690 if (!extension_service)
691 return;
692 const extensions::Extension* extension = extension_service->GetExtensionById(
693 extension_misc::kHotwordAudioVerificationAppId, true);
694 if (!extension)
695 return;
697 OpenApplication(
698 AppLaunchParams(profile_, extension, extensions::LAUNCH_CONTAINER_WINDOW,
699 NEW_WINDOW, extensions::SOURCE_CHROME_INTERNAL));
702 HotwordService::LaunchMode
703 HotwordService::GetHotwordAudioVerificationLaunchMode() {
704 return hotword_audio_verification_launch_mode_;
707 void HotwordService::StartTraining() {
708 training_ = true;
710 if (!IsServiceAvailable())
711 return;
713 HotwordPrivateEventService* event_service =
714 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
715 if (event_service)
716 event_service->OnEnabledChanged(hotword_internal::kHotwordTrainingEnabled);
719 void HotwordService::FinalizeSpeakerModel() {
720 if (!IsServiceAvailable())
721 return;
723 HotwordPrivateEventService* event_service =
724 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
725 if (event_service)
726 event_service->OnFinalizeSpeakerModel();
729 void HotwordService::StopTraining() {
730 training_ = false;
732 if (!IsServiceAvailable())
733 return;
735 HotwordPrivateEventService* event_service =
736 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
737 if (event_service)
738 event_service->OnEnabledChanged(hotword_internal::kHotwordTrainingEnabled);
741 void HotwordService::NotifyHotwordTriggered() {
742 if (!IsServiceAvailable())
743 return;
745 HotwordPrivateEventService* event_service =
746 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
747 if (event_service)
748 event_service->OnHotwordTriggered();
751 bool HotwordService::IsTraining() {
752 return training_;
755 HotwordAudioHistoryHandler* HotwordService::GetAudioHistoryHandler() {
756 return audio_history_handler_.get();
759 void HotwordService::SetAudioHistoryHandler(
760 HotwordAudioHistoryHandler* handler) {
761 audio_history_handler_.reset(handler);
762 audio_history_handler_->UpdateAudioHistoryState();
765 void HotwordService::DisableHotwordPreferences() {
766 if (IsSometimesOnEnabled()) {
767 profile_->GetPrefs()->SetBoolean(prefs::kHotwordSearchEnabled, false);
769 if (IsAlwaysOnEnabled()) {
770 profile_->GetPrefs()->SetBoolean(prefs::kHotwordAlwaysOnSearchEnabled,
771 false);
775 void HotwordService::OnHotwordAlwaysOnSearchEnabledChanged(
776 const std::string& pref_name) {
777 // If the pref for always on has been changed in some way, that means that
778 // the user is aware of always on (either from settings or another source)
779 // so they don't need to be shown the notification.
780 profile_->GetPrefs()->SetBoolean(prefs::kHotwordAlwaysOnNotificationSeen,
781 true);
782 pref_registrar_.Remove(prefs::kHotwordAlwaysOnSearchEnabled);
785 void HotwordService::RequestHotwordSession(HotwordClient* client) {
786 if (!IsServiceAvailable() || (client_ && client_ != client))
787 return;
789 client_ = client;
791 HotwordPrivateEventService* event_service =
792 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
793 if (event_service)
794 event_service->OnHotwordSessionRequested();
797 void HotwordService::StopHotwordSession(HotwordClient* client) {
798 if (!IsServiceAvailable())
799 return;
801 // Do nothing if there's no client.
802 if (!client_)
803 return;
804 DCHECK(client_ == client);
806 client_ = NULL;
807 HotwordPrivateEventService* event_service =
808 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
809 if (event_service)
810 event_service->OnHotwordSessionStopped();
813 void HotwordService::SetPreviousLanguagePref() {
814 profile_->GetPrefs()->SetString(prefs::kHotwordPreviousLanguage,
815 GetCurrentLocale(profile_));
818 bool HotwordService::ShouldReinstallHotwordExtension() {
819 // If there is no previous locale pref, then this is the first install
820 // so no need to uninstall first.
821 if (!profile_->GetPrefs()->HasPrefPath(prefs::kHotwordPreviousLanguage))
822 return false;
824 std::string previous_locale =
825 profile_->GetPrefs()->GetString(prefs::kHotwordPreviousLanguage);
826 std::string locale = GetCurrentLocale(profile_);
828 // If it's a new locale, then the old extension should be uninstalled.
829 return locale != previous_locale &&
830 HotwordService::DoesHotwordSupportLanguage(profile_);
833 void HotwordService::ActiveUserChanged() {
834 // Don't bother notifying the extension if hotwording is completely off.
835 if (!IsSometimesOnEnabled() && !IsAlwaysOnEnabled() && !IsTraining())
836 return;
838 HotwordPrivateEventService* event_service =
839 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
840 // "enabled" isn't being changed, but piggy-back off the notification anyway.
841 if (event_service)
842 event_service->OnEnabledChanged(prefs::kHotwordSearchEnabled);
845 bool HotwordService::UserIsActive() {
846 #if defined(OS_CHROMEOS)
847 // Only support multiple profiles and profile switching in ChromeOS.
848 if (user_manager::UserManager::IsInitialized()) {
849 user_manager::User* user =
850 user_manager::UserManager::Get()->GetActiveUser();
851 if (user && user->is_profile_created())
852 return profile_ == ProfileManager::GetActiveUserProfile();
854 #endif
855 return true;