[Cronet] Delay StartNetLog and StopNetLog until native request context is initialized
[chromium-blink-merge.git] / chrome / browser / search / hotword_service.cc
blobe6881665f254051e0f8d97acdb6e047e631b86a1
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 // Old preference constant.
212 const char kHotwordUnusablePrefName[] = "hotword.search_enabled";
213 // String passed to indicate the training state has changed.
214 const char kHotwordTrainingEnabled[] = "hotword_training_enabled";
215 // Id of the hotword notification.
216 const char kHotwordNotificationId[] = "hotword";
217 // Notifier id for the hotword notification.
218 const char kHotwordNotifierId[] = "hotword.notification";
219 } // namespace hotword_internal
221 // Delegate for the hotword notification.
222 class HotwordNotificationDelegate : public NotificationDelegate {
223 public:
224 explicit HotwordNotificationDelegate(Profile* profile)
225 : profile_(profile) {
228 // Overridden from NotificationDelegate:
229 void ButtonClick(int button_index) override {
230 DCHECK_EQ(0, button_index);
231 Click();
234 void Click() override {
235 // Launch the hotword audio verification app in the right mode.
236 HotwordService::LaunchMode launch_mode =
237 HotwordService::HOTWORD_AND_AUDIO_HISTORY;
238 if (profile_->GetPrefs()->GetBoolean(
239 prefs::kHotwordAudioLoggingEnabled)) {
240 launch_mode = HotwordService::HOTWORD_ONLY;
243 HotwordService* hotword_service =
244 HotwordServiceFactory::GetForProfile(profile_);
246 if (!hotword_service)
247 return;
249 hotword_service->LaunchHotwordAudioVerificationApp(launch_mode);
251 // Close the notification after it's been clicked on to remove it
252 // from the notification tray.
253 g_browser_process->notification_ui_manager()->CancelById(
254 id(), NotificationUIManager::GetProfileID(profile_));
257 // Overridden from NotificationDelegate:
258 std::string id() const override {
259 return hotword_internal::kHotwordNotificationId;
262 private:
263 ~HotwordNotificationDelegate() override {}
265 Profile* profile_;
267 DISALLOW_COPY_AND_ASSIGN(HotwordNotificationDelegate);
270 // static
271 bool HotwordService::DoesHotwordSupportLanguage(Profile* profile) {
272 std::string normalized_locale =
273 l10n_util::NormalizeLocale(GetCurrentLocale(profile));
274 base::StringToLowerASCII(&normalized_locale);
276 for (size_t i = 0; i < arraysize(kSupportedLocales); i++) {
277 if (normalized_locale == kSupportedLocales[i])
278 return true;
280 return false;
283 // static
284 bool HotwordService::IsHotwordHardwareAvailable() {
285 #if defined(OS_CHROMEOS)
286 if (chromeos::CrasAudioHandler::IsInitialized()) {
287 chromeos::AudioDeviceList devices;
288 chromeos::CrasAudioHandler::Get()->GetAudioDevices(&devices);
289 for (size_t i = 0; i < devices.size(); ++i) {
290 if (devices[i].type == chromeos::AUDIO_TYPE_AOKR) {
291 DCHECK(devices[i].is_input);
292 return true;
296 #endif
297 return false;
300 #if defined(OS_CHROMEOS)
301 class HotwordService::HotwordUserSessionStateObserver
302 : public user_manager::UserManager::UserSessionStateObserver {
303 public:
304 explicit HotwordUserSessionStateObserver(HotwordService* service)
305 : service_(service) {}
307 // Overridden from UserSessionStateObserver:
308 void ActiveUserChanged(const user_manager::User* active_user) override {
309 service_->ActiveUserChanged();
312 private:
313 HotwordService* service_; // Not owned
315 #else
316 // Dummy class to please the linker.
317 class HotwordService::HotwordUserSessionStateObserver {
319 #endif
321 void HotwordService::HotwordWebstoreInstaller::Shutdown() {
322 AbortInstall();
325 HotwordService::HotwordService(Profile* profile)
326 : profile_(profile),
327 extension_registry_observer_(this),
328 client_(NULL),
329 error_message_(0),
330 reinstall_pending_(false),
331 training_(false),
332 weak_factory_(this) {
333 extension_registry_observer_.Add(extensions::ExtensionRegistry::Get(profile));
335 // Disable the old extension so it doesn't interfere with the new stuff.
336 ExtensionService* extension_service = GetExtensionService(profile_);
337 if (extension_service) {
338 extension_service->DisableExtension(
339 kHotwordOldExtensionId,
340 extensions::Extension::DISABLE_USER_ACTION);
343 // This will be called during profile initialization which is a good time
344 // to check the user's hotword state.
345 RecordHotwordEnabledMetric(this, profile_);
347 pref_registrar_.Init(profile_->GetPrefs());
348 pref_registrar_.Add(
349 prefs::kHotwordAlwaysOnSearchEnabled,
350 base::Bind(&HotwordService::OnHotwordAlwaysOnSearchEnabledChanged,
351 base::Unretained(this)));
353 extensions::ExtensionSystem::Get(profile_)->ready().Post(
354 FROM_HERE,
355 base::Bind(base::IgnoreResult(
356 &HotwordService::MaybeReinstallHotwordExtension),
357 weak_factory_.GetWeakPtr()));
359 // Clear the old user pref because it became unusable.
360 // TODO(rlp): Remove this code per crbug.com/358789.
361 if (profile_->GetPrefs()->HasPrefPath(
362 hotword_internal::kHotwordUnusablePrefName)) {
363 profile_->GetPrefs()->ClearPref(hotword_internal::kHotwordUnusablePrefName);
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::MessageLoop::current()->PostDelayedTask(
377 FROM_HERE,
378 base::Bind(&HotwordService::ShowHotwordNotification,
379 weak_factory_.GetWeakPtr()),
380 base::TimeDelta::FromSeconds(5));
381 } else if (!profile_->GetPrefs()->GetBoolean(
382 prefs::kHotwordAlwaysOnNotificationSeen)) {
383 base::MessageLoop::current()->PostDelayedTask(
384 FROM_HERE,
385 base::Bind(&HotwordService::ShowHotwordNotification,
386 weak_factory_.GetWeakPtr()),
387 base::TimeDelta::FromMinutes(10));
391 #if defined(OS_CHROMEOS)
392 if (user_manager::UserManager::IsInitialized()) {
393 session_observer_.reset(new HotwordUserSessionStateObserver(this));
394 user_manager::UserManager::Get()->AddSessionStateObserver(
395 session_observer_.get());
397 #endif
400 HotwordService::~HotwordService() {
401 #if defined(OS_CHROMEOS)
402 if (user_manager::UserManager::IsInitialized() && session_observer_) {
403 user_manager::UserManager::Get()->RemoveSessionStateObserver(
404 session_observer_.get());
406 #endif
409 void HotwordService::Shutdown() {
410 if (installer_.get())
411 installer_->Shutdown();
414 void HotwordService::ShowHotwordNotification() {
415 // Check for enabled here in case always-on was enabled during the delay.
416 if (!IsServiceAvailable() || IsAlwaysOnEnabled())
417 return;
419 message_center::RichNotificationData data;
420 const base::string16 label = l10n_util::GetStringUTF16(
421 IDS_HOTWORD_NOTIFICATION_BUTTON);
422 data.buttons.push_back(message_center::ButtonInfo(label));
424 Notification notification(
425 message_center::NOTIFICATION_TYPE_SIMPLE,
426 GURL(),
427 l10n_util::GetStringUTF16(IDS_HOTWORD_NOTIFICATION_TITLE),
428 l10n_util::GetStringUTF16(IDS_HOTWORD_NOTIFICATION_DESCRIPTION),
429 ui::ResourceBundle::GetSharedInstance().GetImageNamed(
430 IDR_HOTWORD_NOTIFICATION_ICON),
431 message_center::NotifierId(
432 message_center::NotifierId::SYSTEM_COMPONENT,
433 hotword_internal::kHotwordNotifierId),
434 base::string16(),
435 std::string(),
436 data,
437 new HotwordNotificationDelegate(profile_));
439 g_browser_process->notification_ui_manager()->Add(notification, profile_);
440 profile_->GetPrefs()->SetBoolean(
441 prefs::kHotwordAlwaysOnNotificationSeen, true);
444 void HotwordService::OnExtensionUninstalled(
445 content::BrowserContext* browser_context,
446 const extensions::Extension* extension,
447 extensions::UninstallReason reason) {
448 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
450 if (extension->id() != extension_misc::kHotwordSharedModuleId ||
451 profile_ != Profile::FromBrowserContext(browser_context) ||
452 !GetExtensionService(profile_))
453 return;
455 // If the extension wasn't uninstalled due to language change, don't try to
456 // reinstall it.
457 if (!reinstall_pending_)
458 return;
460 InstallHotwordExtensionFromWebstore(kMaxInstallRetries);
461 SetPreviousLanguagePref();
464 std::string HotwordService::ReinstalledExtensionId() {
465 return extension_misc::kHotwordSharedModuleId;
468 void HotwordService::InstalledFromWebstoreCallback(
469 int num_tries,
470 bool success,
471 const std::string& error,
472 extensions::webstore_install::Result result) {
473 if (result != extensions::webstore_install::SUCCESS && num_tries) {
474 // Try again on failure.
475 content::BrowserThread::PostDelayedTask(
476 content::BrowserThread::UI,
477 FROM_HERE,
478 base::Bind(&HotwordService::InstallHotwordExtensionFromWebstore,
479 weak_factory_.GetWeakPtr(),
480 num_tries),
481 base::TimeDelta::FromSeconds(kInstallRetryDelaySeconds));
485 void HotwordService::InstallHotwordExtensionFromWebstore(int num_tries) {
486 installer_ = new HotwordWebstoreInstaller(
487 ReinstalledExtensionId(),
488 profile_,
489 base::Bind(&HotwordService::InstalledFromWebstoreCallback,
490 weak_factory_.GetWeakPtr(),
491 num_tries - 1));
492 installer_->BeginInstall();
495 void HotwordService::OnExtensionInstalled(
496 content::BrowserContext* browser_context,
497 const extensions::Extension* extension,
498 bool is_update) {
500 if (extension->id() != extension_misc::kHotwordSharedModuleId ||
501 profile_ != Profile::FromBrowserContext(browser_context))
502 return;
504 // If the previous locale pref has never been set, set it now since
505 // the extension has been installed.
506 if (!profile_->GetPrefs()->HasPrefPath(prefs::kHotwordPreviousLanguage))
507 SetPreviousLanguagePref();
509 // If MaybeReinstallHotwordExtension already triggered an uninstall, we
510 // don't want to loop and trigger another uninstall-install cycle.
511 // However, if we arrived here via an uninstall-triggered-install (and in
512 // that case |reinstall_pending_| will be true) then we know install
513 // has completed and we can reset |reinstall_pending_|.
514 if (!reinstall_pending_)
515 MaybeReinstallHotwordExtension();
516 else
517 reinstall_pending_ = false;
520 bool HotwordService::MaybeReinstallHotwordExtension() {
521 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
523 ExtensionService* extension_service = GetExtensionService(profile_);
524 if (!extension_service)
525 return false;
527 const extensions::Extension* extension = extension_service->GetExtensionById(
528 ReinstalledExtensionId(), true);
529 if (!extension)
530 return false;
532 // If the extension is currently pending, return and we'll check again
533 // after the install is finished.
534 extensions::PendingExtensionManager* pending_manager =
535 extension_service->pending_extension_manager();
536 if (pending_manager->IsIdPending(extension->id()))
537 return false;
539 // If there is already a pending request from HotwordService, don't try
540 // to uninstall either.
541 if (reinstall_pending_)
542 return false;
544 // Check if the current locale matches the previous. If they don't match,
545 // uninstall the extension.
546 if (!ShouldReinstallHotwordExtension())
547 return false;
549 // Ensure the call to OnExtensionUninstalled was triggered by a language
550 // change so it's okay to reinstall.
551 reinstall_pending_ = true;
553 // Disable always-on on a language change. We do this because the speaker-id
554 // model needs to be re-trained.
555 if (IsAlwaysOnEnabled()) {
556 profile_->GetPrefs()->SetBoolean(prefs::kHotwordAlwaysOnSearchEnabled,
557 false);
560 // Record re-installs due to language change.
561 UMA_HISTOGRAM_SPARSE_SLOWLY(
562 "Hotword.SharedModuleReinstallLanguage",
563 language_usage_metrics::LanguageUsageMetrics::ToLanguageCode(
564 GetCurrentLocale(profile_)));
565 return UninstallHotwordExtension(extension_service);
568 bool HotwordService::UninstallHotwordExtension(
569 ExtensionService* extension_service) {
570 base::string16 error;
571 std::string extension_id = ReinstalledExtensionId();
572 if (!extension_service->UninstallExtension(
573 extension_id,
574 extensions::UNINSTALL_REASON_INTERNAL_MANAGEMENT,
575 base::Bind(&base::DoNothing),
576 &error)) {
577 LOG(WARNING) << "Cannot uninstall extension with id "
578 << extension_id
579 << ": " << error;
580 reinstall_pending_ = false;
581 return false;
583 return true;
586 bool HotwordService::IsServiceAvailable() {
587 error_message_ = 0;
589 // Determine if the extension is available.
590 extensions::ExtensionSystem* system =
591 extensions::ExtensionSystem::Get(profile_);
592 ExtensionService* service = system->extension_service();
593 // Include disabled extensions (true parameter) since it may not be enabled
594 // if the user opted out.
595 const extensions::Extension* extension =
596 service->GetExtensionById(ReinstalledExtensionId(), true);
597 if (!extension)
598 error_message_ = IDS_HOTWORD_GENERIC_ERROR_MESSAGE;
600 // TODO(amistry): Record availability of shared module in UMA.
601 RecordLoggingMetrics(profile_);
603 // Determine if NaCl is available.
604 bool nacl_enabled = false;
605 base::FilePath path;
606 if (PathService::Get(chrome::FILE_NACL_PLUGIN, &path)) {
607 content::WebPluginInfo info;
608 PluginPrefs* plugin_prefs = PluginPrefs::GetForProfile(profile_).get();
609 if (content::PluginService::GetInstance()->GetPluginInfoByPath(path, &info))
610 nacl_enabled = plugin_prefs->IsPluginEnabled(info);
612 if (!nacl_enabled)
613 error_message_ = IDS_HOTWORD_NACL_DISABLED_ERROR_MESSAGE;
615 RecordErrorMetrics(error_message_);
617 // Determine if the proper audio capabilities exist.
618 // The first time this is called, it probably won't return in time, but that's
619 // why it won't be included in the error calculation (i.e., the call to
620 // IsAudioDeviceStateUpdated()). However, this use case is rare and typically
621 // the devices will be initialized by the time a user goes to settings.
622 bool audio_device_state_updated =
623 HotwordServiceFactory::IsAudioDeviceStateUpdated();
624 HotwordServiceFactory::GetInstance()->UpdateMicrophoneState();
625 if (audio_device_state_updated) {
626 bool audio_capture_allowed =
627 profile_->GetPrefs()->GetBoolean(prefs::kAudioCaptureAllowed);
628 if (!audio_capture_allowed ||
629 !HotwordServiceFactory::IsMicrophoneAvailable())
630 error_message_ = IDS_HOTWORD_MICROPHONE_ERROR_MESSAGE;
633 return (error_message_ == 0) && IsHotwordAllowed();
636 bool HotwordService::IsHotwordAllowed() {
637 std::string group = base::FieldTrialList::FindFullName(
638 hotword_internal::kHotwordFieldTrialName);
639 // Allow hotwording by default, and only disable if the field trial has been
640 // set.
641 if (group == hotword_internal::kHotwordFieldTrialDisabledGroupName)
642 return false;
644 return DoesHotwordSupportLanguage(profile_);
647 bool HotwordService::IsOptedIntoAudioLogging() {
648 // Do not opt the user in if the preference has not been set.
649 return
650 profile_->GetPrefs()->HasPrefPath(prefs::kHotwordAudioLoggingEnabled) &&
651 profile_->GetPrefs()->GetBoolean(prefs::kHotwordAudioLoggingEnabled);
654 bool HotwordService::IsAlwaysOnEnabled() {
655 return
656 profile_->GetPrefs()->HasPrefPath(prefs::kHotwordAlwaysOnSearchEnabled) &&
657 profile_->GetPrefs()->GetBoolean(prefs::kHotwordAlwaysOnSearchEnabled) &&
658 HotwordServiceFactory::IsAlwaysOnAvailable();
661 bool HotwordService::IsSometimesOnEnabled() {
662 return profile_->GetPrefs()->HasPrefPath(prefs::kHotwordSearchEnabled) &&
663 profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled) &&
664 !HotwordServiceFactory::IsAlwaysOnAvailable();
667 void HotwordService::SpeakerModelExistsComplete(bool exists) {
668 if (exists) {
669 profile_->GetPrefs()->
670 SetBoolean(prefs::kHotwordAlwaysOnSearchEnabled, true);
671 } else {
672 LaunchHotwordAudioVerificationApp(HotwordService::HOTWORD_ONLY);
676 void HotwordService::OptIntoHotwording(
677 const LaunchMode& launch_mode) {
678 // First determine if we actually need to launch the app, or can just enable
679 // the pref. If Audio History has already been enabled, and we already have
680 // a speaker model, then we don't need to launch the app at all.
681 if (launch_mode == HotwordService::HOTWORD_ONLY) {
682 HotwordPrivateEventService* event_service =
683 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(
684 profile_);
685 if (event_service) {
686 event_service->OnSpeakerModelExists();
687 return;
691 LaunchHotwordAudioVerificationApp(launch_mode);
694 void HotwordService::LaunchHotwordAudioVerificationApp(
695 const LaunchMode& launch_mode) {
696 hotword_audio_verification_launch_mode_ = launch_mode;
698 ExtensionService* extension_service = GetExtensionService(profile_);
699 if (!extension_service)
700 return;
701 const extensions::Extension* extension = extension_service->GetExtensionById(
702 extension_misc::kHotwordAudioVerificationAppId, true);
703 if (!extension)
704 return;
706 OpenApplication(
707 AppLaunchParams(profile_, extension, extensions::LAUNCH_CONTAINER_WINDOW,
708 NEW_WINDOW, extensions::SOURCE_CHROME_INTERNAL));
711 HotwordService::LaunchMode
712 HotwordService::GetHotwordAudioVerificationLaunchMode() {
713 return hotword_audio_verification_launch_mode_;
716 void HotwordService::StartTraining() {
717 training_ = true;
719 if (!IsServiceAvailable())
720 return;
722 HotwordPrivateEventService* event_service =
723 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
724 if (event_service)
725 event_service->OnEnabledChanged(hotword_internal::kHotwordTrainingEnabled);
728 void HotwordService::FinalizeSpeakerModel() {
729 if (!IsServiceAvailable())
730 return;
732 HotwordPrivateEventService* event_service =
733 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
734 if (event_service)
735 event_service->OnFinalizeSpeakerModel();
738 void HotwordService::StopTraining() {
739 training_ = false;
741 if (!IsServiceAvailable())
742 return;
744 HotwordPrivateEventService* event_service =
745 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
746 if (event_service)
747 event_service->OnEnabledChanged(hotword_internal::kHotwordTrainingEnabled);
750 void HotwordService::NotifyHotwordTriggered() {
751 if (!IsServiceAvailable())
752 return;
754 HotwordPrivateEventService* event_service =
755 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
756 if (event_service)
757 event_service->OnHotwordTriggered();
760 bool HotwordService::IsTraining() {
761 return training_;
764 HotwordAudioHistoryHandler* HotwordService::GetAudioHistoryHandler() {
765 return audio_history_handler_.get();
768 void HotwordService::SetAudioHistoryHandler(
769 HotwordAudioHistoryHandler* handler) {
770 audio_history_handler_.reset(handler);
771 audio_history_handler_->UpdateAudioHistoryState();
774 void HotwordService::DisableHotwordPreferences() {
775 if (IsSometimesOnEnabled()) {
776 profile_->GetPrefs()->SetBoolean(prefs::kHotwordSearchEnabled, false);
778 if (IsAlwaysOnEnabled()) {
779 profile_->GetPrefs()->SetBoolean(prefs::kHotwordAlwaysOnSearchEnabled,
780 false);
784 void HotwordService::OnHotwordAlwaysOnSearchEnabledChanged(
785 const std::string& pref_name) {
786 // If the pref for always on has been changed in some way, that means that
787 // the user is aware of always on (either from settings or another source)
788 // so they don't need to be shown the notification.
789 profile_->GetPrefs()->SetBoolean(prefs::kHotwordAlwaysOnNotificationSeen,
790 true);
791 pref_registrar_.Remove(prefs::kHotwordAlwaysOnSearchEnabled);
794 void HotwordService::RequestHotwordSession(HotwordClient* client) {
795 if (!IsServiceAvailable() || (client_ && client_ != client))
796 return;
798 client_ = client;
800 HotwordPrivateEventService* event_service =
801 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
802 if (event_service)
803 event_service->OnHotwordSessionRequested();
806 void HotwordService::StopHotwordSession(HotwordClient* client) {
807 if (!IsServiceAvailable())
808 return;
810 // Do nothing if there's no client.
811 if (!client_)
812 return;
813 DCHECK(client_ == client);
815 client_ = NULL;
816 HotwordPrivateEventService* event_service =
817 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
818 if (event_service)
819 event_service->OnHotwordSessionStopped();
822 void HotwordService::SetPreviousLanguagePref() {
823 profile_->GetPrefs()->SetString(prefs::kHotwordPreviousLanguage,
824 GetCurrentLocale(profile_));
827 bool HotwordService::ShouldReinstallHotwordExtension() {
828 // If there is no previous locale pref, then this is the first install
829 // so no need to uninstall first.
830 if (!profile_->GetPrefs()->HasPrefPath(prefs::kHotwordPreviousLanguage))
831 return false;
833 std::string previous_locale =
834 profile_->GetPrefs()->GetString(prefs::kHotwordPreviousLanguage);
835 std::string locale = GetCurrentLocale(profile_);
837 // If it's a new locale, then the old extension should be uninstalled.
838 return locale != previous_locale &&
839 HotwordService::DoesHotwordSupportLanguage(profile_);
842 void HotwordService::ActiveUserChanged() {
843 // Don't bother notifying the extension if hotwording is completely off.
844 if (!IsSometimesOnEnabled() && !IsAlwaysOnEnabled() && !IsTraining())
845 return;
847 HotwordPrivateEventService* event_service =
848 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
849 // "enabled" isn't being changed, but piggy-back off the notification anyway.
850 if (event_service)
851 event_service->OnEnabledChanged(prefs::kHotwordSearchEnabled);
854 bool HotwordService::UserIsActive() {
855 #if defined(OS_CHROMEOS)
856 // Only support multiple profiles and profile switching in ChromeOS.
857 if (user_manager::UserManager::IsInitialized()) {
858 user_manager::User* user =
859 user_manager::UserManager::Get()->GetActiveUser();
860 if (user && user->is_profile_created())
861 return profile_ == ProfileManager::GetActiveUserProfile();
863 #endif
864 return true;