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"
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"
57 using extensions::BrowserContextKeyedAPIFactory
;
58 using extensions::HotwordPrivateEventService
;
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
[] = {
92 // Maximum number of retries for installing the hotword shared module from the
94 static const int kMaxInstallRetries
= 2;
96 // Delay between retries for installing the hotword shared module from the web
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
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
117 enum HotwordExtensionAvailability
{
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
131 NO_HOTWORD_ERROR
= 0,
132 GENERIC_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
))
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
;
155 case IDS_HOTWORD_NACL_DISABLED_ERROR_MESSAGE
:
156 error
= NACL_HOTWORD_ERROR
;
158 case IDS_HOTWORD_MICROPHONE_ERROR_MESSAGE
:
159 error
= MICROPHONE_HOTWORD_ERROR
;
162 error
= NO_HOTWORD_ERROR
;
165 UMA_HISTOGRAM_ENUMERATION("Hotword.HotwordError",
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
;
202 return g_browser_process
->GetApplicationLocale();
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
{
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
);
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
)
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
;
261 ~HotwordNotificationDelegate() override
{}
265 DISALLOW_COPY_AND_ASSIGN(HotwordNotificationDelegate
);
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
])
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
);
298 #if defined(OS_CHROMEOS)
299 class HotwordService::HotwordUserSessionStateObserver
300 : public user_manager::UserManager::UserSessionStateObserver
{
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();
311 HotwordService
* service_
; // Not owned
314 // Dummy class to please the linker.
315 class HotwordService::HotwordUserSessionStateObserver
{
319 void HotwordService::HotwordWebstoreInstaller::Shutdown() {
323 HotwordService::HotwordService(Profile
* profile
)
325 extension_registry_observer_(this),
328 reinstall_pending_(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());
347 prefs::kHotwordAlwaysOnSearchEnabled
,
348 base::Bind(&HotwordService::OnHotwordAlwaysOnSearchEnabledChanged
,
349 base::Unretained(this)));
351 extensions::ExtensionSystem::Get(profile_
)->ready().Post(
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(
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(
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());
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());
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())
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
,
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
),
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_
))
446 // If the extension wasn't uninstalled due to language change, don't try to
448 if (!reinstall_pending_
)
451 InstallHotwordExtensionFromWebstore(kMaxInstallRetries
);
452 SetPreviousLanguagePref();
455 std::string
HotwordService::ReinstalledExtensionId() {
456 return extension_misc::kHotwordSharedModuleId
;
459 void HotwordService::InstalledFromWebstoreCallback(
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
,
469 base::Bind(&HotwordService::InstallHotwordExtensionFromWebstore
,
470 weak_factory_
.GetWeakPtr(),
472 base::TimeDelta::FromSeconds(kInstallRetryDelaySeconds
));
476 void HotwordService::InstallHotwordExtensionFromWebstore(int num_tries
) {
477 installer_
= new HotwordWebstoreInstaller(
478 ReinstalledExtensionId(),
480 base::Bind(&HotwordService::InstalledFromWebstoreCallback
,
481 weak_factory_
.GetWeakPtr(),
483 installer_
->BeginInstall();
486 void HotwordService::OnExtensionInstalled(
487 content::BrowserContext
* browser_context
,
488 const extensions::Extension
* extension
,
491 if (extension
->id() != extension_misc::kHotwordSharedModuleId
||
492 profile_
!= Profile::FromBrowserContext(browser_context
))
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();
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
)
518 const extensions::Extension
* extension
= extension_service
->GetExtensionById(
519 ReinstalledExtensionId(), true);
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()))
530 // If there is already a pending request from HotwordService, don't try
531 // to uninstall either.
532 if (reinstall_pending_
)
535 // Check if the current locale matches the previous. If they don't match,
536 // uninstall the extension.
537 if (!ShouldReinstallHotwordExtension())
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
,
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(
565 extensions::UNINSTALL_REASON_INTERNAL_MANAGEMENT
,
566 base::Bind(&base::DoNothing
),
568 LOG(WARNING
) << "Cannot uninstall extension with id "
571 reinstall_pending_
= false;
577 bool HotwordService::IsServiceAvailable() {
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);
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;
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
);
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
632 if (group
== hotword_internal::kHotwordFieldTrialDisabledGroupName
)
635 return DoesHotwordSupportLanguage(profile_
);
638 bool HotwordService::IsOptedIntoAudioLogging() {
639 // Do not opt the user in if the preference has not been set.
641 profile_
->GetPrefs()->HasPrefPath(prefs::kHotwordAudioLoggingEnabled
) &&
642 profile_
->GetPrefs()->GetBoolean(prefs::kHotwordAudioLoggingEnabled
);
645 bool HotwordService::IsAlwaysOnEnabled() {
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
) {
660 profile_
->GetPrefs()->
661 SetBoolean(prefs::kHotwordAlwaysOnSearchEnabled
, true);
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(
677 event_service
->OnSpeakerModelExists();
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
)
692 const extensions::Extension
* extension
= extension_service
->GetExtensionById(
693 extension_misc::kHotwordAudioVerificationAppId
, true);
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() {
710 if (!IsServiceAvailable())
713 HotwordPrivateEventService
* event_service
=
714 BrowserContextKeyedAPIFactory
<HotwordPrivateEventService
>::Get(profile_
);
716 event_service
->OnEnabledChanged(hotword_internal::kHotwordTrainingEnabled
);
719 void HotwordService::FinalizeSpeakerModel() {
720 if (!IsServiceAvailable())
723 HotwordPrivateEventService
* event_service
=
724 BrowserContextKeyedAPIFactory
<HotwordPrivateEventService
>::Get(profile_
);
726 event_service
->OnFinalizeSpeakerModel();
729 void HotwordService::StopTraining() {
732 if (!IsServiceAvailable())
735 HotwordPrivateEventService
* event_service
=
736 BrowserContextKeyedAPIFactory
<HotwordPrivateEventService
>::Get(profile_
);
738 event_service
->OnEnabledChanged(hotword_internal::kHotwordTrainingEnabled
);
741 void HotwordService::NotifyHotwordTriggered() {
742 if (!IsServiceAvailable())
745 HotwordPrivateEventService
* event_service
=
746 BrowserContextKeyedAPIFactory
<HotwordPrivateEventService
>::Get(profile_
);
748 event_service
->OnHotwordTriggered();
751 bool HotwordService::IsTraining() {
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
,
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
,
782 pref_registrar_
.Remove(prefs::kHotwordAlwaysOnSearchEnabled
);
785 void HotwordService::RequestHotwordSession(HotwordClient
* client
) {
786 if (!IsServiceAvailable() || (client_
&& client_
!= client
))
791 HotwordPrivateEventService
* event_service
=
792 BrowserContextKeyedAPIFactory
<HotwordPrivateEventService
>::Get(profile_
);
794 event_service
->OnHotwordSessionRequested();
797 void HotwordService::StopHotwordSession(HotwordClient
* client
) {
798 if (!IsServiceAvailable())
801 // Do nothing if there's no client.
804 DCHECK(client_
== client
);
807 HotwordPrivateEventService
* event_service
=
808 BrowserContextKeyedAPIFactory
<HotwordPrivateEventService
>::Get(profile_
);
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
))
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())
838 HotwordPrivateEventService
* event_service
=
839 BrowserContextKeyedAPIFactory
<HotwordPrivateEventService
>::Get(profile_
);
840 // "enabled" isn't being changed, but piggy-back off the notification anyway.
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();