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 "base/command_line.h"
8 #include "base/i18n/case_conversion.h"
9 #include "base/metrics/field_trial.h"
10 #include "base/metrics/histogram.h"
11 #include "base/path_service.h"
12 #include "base/prefs/pref_service.h"
13 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/chrome_notification_types.h"
15 #include "chrome/browser/extensions/api/hotword_private/hotword_private_api.h"
16 #include "chrome/browser/extensions/extension_service.h"
17 #include "chrome/browser/extensions/pending_extension_manager.h"
18 #include "chrome/browser/extensions/updater/extension_updater.h"
19 #include "chrome/browser/extensions/webstore_startup_installer.h"
20 #include "chrome/browser/plugins/plugin_prefs.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/search/hotword_audio_history_handler.h"
23 #include "chrome/browser/search/hotword_service_factory.h"
24 #include "chrome/browser/ui/extensions/application_launch.h"
25 #include "chrome/common/chrome_paths.h"
26 #include "chrome/common/chrome_switches.h"
27 #include "chrome/common/extensions/extension_constants.h"
28 #include "chrome/common/pref_names.h"
29 #include "chrome/grit/generated_resources.h"
30 #include "content/public/browser/browser_thread.h"
31 #include "content/public/browser/notification_service.h"
32 #include "content/public/browser/plugin_service.h"
33 #include "content/public/common/webplugininfo.h"
34 #include "extensions/browser/extension_system.h"
35 #include "extensions/browser/uninstall_reason.h"
36 #include "extensions/common/extension.h"
37 #include "extensions/common/one_shot_event.h"
38 #include "ui/base/l10n/l10n_util.h"
40 using extensions::BrowserContextKeyedAPIFactory;
41 using extensions::HotwordPrivateEventService;
45 // Allowed languages for hotwording.
46 static const char* kSupportedLocales[] = {
53 // Enum describing the state of the hotword preference.
54 // This is used for UMA stats -- do not reorder or delete items; only add to
57 UNSET = 0, // The hotword preference has not been set.
58 ENABLED, // The hotword preference is enabled.
59 DISABLED, // The hotword preference is disabled.
60 NUM_HOTWORD_ENABLED_METRICS
63 // Enum describing the availability state of the hotword extension.
64 // This is used for UMA stats -- do not reorder or delete items; only add to
66 enum HotwordExtensionAvailability {
71 NUM_HOTWORD_EXTENSION_AVAILABILITY_METRICS
74 // Enum describing the types of errors that can arise when determining
75 // if hotwording can be used. NO_ERROR is used so it can be seen how often
76 // errors arise relative to when they do not.
77 // This is used for UMA stats -- do not reorder or delete items; only add to
81 GENERIC_HOTWORD_ERROR,
83 MICROPHONE_HOTWORD_ERROR,
84 NUM_HOTWORD_ERROR_METRICS
87 void RecordExtensionAvailabilityMetrics(
88 ExtensionService* service,
89 const extensions::Extension* extension) {
90 HotwordExtensionAvailability availability_state = UNAVAILABLE;
92 availability_state = AVAILABLE;
93 } else if (service->pending_extension_manager() &&
94 service->pending_extension_manager()->IsIdPending(
95 extension_misc::kHotwordExtensionId)) {
96 availability_state = PENDING_DOWNLOAD;
97 } else if (!service->IsExtensionEnabled(
98 extension_misc::kHotwordExtensionId)) {
99 availability_state = DISABLED_EXTENSION;
101 UMA_HISTOGRAM_ENUMERATION("Hotword.HotwordExtensionAvailability",
103 NUM_HOTWORD_EXTENSION_AVAILABILITY_METRICS);
106 void RecordLoggingMetrics(Profile* profile) {
107 // If the user is not opted in to hotword voice search, the audio logging
108 // metric is not valid so it is not recorded.
109 if (!profile->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled))
112 UMA_HISTOGRAM_BOOLEAN(
113 "Hotword.HotwordAudioLogging",
114 profile->GetPrefs()->GetBoolean(prefs::kHotwordAudioLoggingEnabled));
117 void RecordErrorMetrics(int error_message) {
118 HotwordError error = NO_HOTWORD_ERROR;
119 switch (error_message) {
120 case IDS_HOTWORD_GENERIC_ERROR_MESSAGE:
121 error = GENERIC_HOTWORD_ERROR;
123 case IDS_HOTWORD_NACL_DISABLED_ERROR_MESSAGE:
124 error = NACL_HOTWORD_ERROR;
126 case IDS_HOTWORD_MICROPHONE_ERROR_MESSAGE:
127 error = MICROPHONE_HOTWORD_ERROR;
130 error = NO_HOTWORD_ERROR;
133 UMA_HISTOGRAM_ENUMERATION("Hotword.HotwordError",
135 NUM_HOTWORD_ERROR_METRICS);
138 ExtensionService* GetExtensionService(Profile* profile) {
139 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
141 extensions::ExtensionSystem* extension_system =
142 extensions::ExtensionSystem::Get(profile);
143 return extension_system ? extension_system->extension_service() : NULL;
146 std::string GetCurrentLocale(Profile* profile) {
147 #if defined(OS_CHROMEOS)
148 std::string profile_locale =
149 profile->GetPrefs()->GetString(prefs::kApplicationLocale);
150 if (!profile_locale.empty()) {
151 // On ChromeOS locale is per-profile, but only if set.
152 return profile_locale;
155 return g_browser_process->GetApplicationLocale();
160 namespace hotword_internal {
161 // Constants for the hotword field trial.
162 const char kHotwordFieldTrialName[] = "VoiceTrigger";
163 const char kHotwordFieldTrialDisabledGroupName[] = "Disabled";
164 const char kHotwordFieldTrialExperimentalGroupName[] = "Experimental";
165 // Old preference constant.
166 const char kHotwordUnusablePrefName[] = "hotword.search_enabled";
167 // String passed to indicate the training state has changed.
168 const char kHotwordTrainingEnabled[] = "hotword_training_enabled";
169 } // namespace hotword_internal
172 bool HotwordService::DoesHotwordSupportLanguage(Profile* profile) {
173 std::string normalized_locale =
174 l10n_util::NormalizeLocale(GetCurrentLocale(profile));
175 base::StringToLowerASCII(&normalized_locale);
177 for (size_t i = 0; i < arraysize(kSupportedLocales); i++) {
178 if (normalized_locale.compare(0, 2, kSupportedLocales[i]) == 0)
185 bool HotwordService::IsExperimentalHotwordingEnabled() {
186 std::string group = base::FieldTrialList::FindFullName(
187 hotword_internal::kHotwordFieldTrialName);
188 if (!group.empty() &&
189 group == hotword_internal::kHotwordFieldTrialExperimentalGroupName) {
193 CommandLine* command_line = CommandLine::ForCurrentProcess();
194 return command_line->HasSwitch(switches::kEnableExperimentalHotwording);
197 HotwordService::HotwordService(Profile* profile)
199 extension_registry_observer_(this),
202 reinstall_pending_(false),
204 weak_factory_(this) {
205 extension_registry_observer_.Add(extensions::ExtensionRegistry::Get(profile));
206 // This will be called during profile initialization which is a good time
207 // to check the user's hotword state.
208 HotwordEnabled enabled_state = UNSET;
209 if (IsExperimentalHotwordingEnabled()) {
210 // Disable the old extension so it doesn't interfere with the new stuff.
211 DisableHotwordExtension(GetExtensionService(profile_));
213 if (profile_->GetPrefs()->HasPrefPath(prefs::kHotwordSearchEnabled)) {
214 if (profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled))
215 enabled_state = ENABLED;
217 enabled_state = DISABLED;
219 // If the preference has not been set the hotword extension should
220 // not be running. However, this should only be done if auto-install
221 // is enabled which is gated through the IsHotwordAllowed check.
222 if (IsHotwordAllowed())
223 DisableHotwordExtension(GetExtensionService(profile_));
226 UMA_HISTOGRAM_ENUMERATION("Hotword.Enabled", enabled_state,
227 NUM_HOTWORD_ENABLED_METRICS);
229 pref_registrar_.Init(profile_->GetPrefs());
231 prefs::kHotwordSearchEnabled,
232 base::Bind(&HotwordService::OnHotwordSearchEnabledChanged,
233 base::Unretained(this)));
235 extensions::ExtensionSystem::Get(profile_)->ready().Post(
237 base::Bind(base::IgnoreResult(
238 &HotwordService::MaybeReinstallHotwordExtension),
239 weak_factory_.GetWeakPtr()));
241 // Clear the old user pref because it became unusable.
242 // TODO(rlp): Remove this code per crbug.com/358789.
243 if (profile_->GetPrefs()->HasPrefPath(
244 hotword_internal::kHotwordUnusablePrefName)) {
245 profile_->GetPrefs()->ClearPref(hotword_internal::kHotwordUnusablePrefName);
248 audio_history_handler_.reset(new HotwordAudioHistoryHandler(profile_));
251 HotwordService::~HotwordService() {
254 void HotwordService::OnExtensionUninstalled(
255 content::BrowserContext* browser_context,
256 const extensions::Extension* extension,
257 extensions::UninstallReason reason) {
258 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
260 if ((extension->id() != extension_misc::kHotwordExtensionId &&
261 extension->id() != extension_misc::kHotwordSharedModuleId) ||
262 profile_ != Profile::FromBrowserContext(browser_context) ||
263 !GetExtensionService(profile_))
266 // If the extension wasn't uninstalled due to language change, don't try to
268 if (!reinstall_pending_)
271 InstallHotwordExtensionFromWebstore();
272 SetPreviousLanguagePref();
275 std::string HotwordService::ReinstalledExtensionId() {
276 if (IsExperimentalHotwordingEnabled())
277 return extension_misc::kHotwordSharedModuleId;
279 return extension_misc::kHotwordExtensionId;
282 void HotwordService::InstallHotwordExtensionFromWebstore() {
283 installer_ = new extensions::WebstoreStartupInstaller(
284 ReinstalledExtensionId(),
287 extensions::WebstoreStandaloneInstaller::Callback());
288 installer_->BeginInstall();
291 void HotwordService::OnExtensionInstalled(
292 content::BrowserContext* browser_context,
293 const extensions::Extension* extension,
296 if ((extension->id() != extension_misc::kHotwordExtensionId &&
297 extension->id() != extension_misc::kHotwordSharedModuleId) ||
298 profile_ != Profile::FromBrowserContext(browser_context))
301 // If the previous locale pref has never been set, set it now since
302 // the extension has been installed.
303 if (!profile_->GetPrefs()->HasPrefPath(prefs::kHotwordPreviousLanguage))
304 SetPreviousLanguagePref();
306 // If MaybeReinstallHotwordExtension already triggered an uninstall, we
307 // don't want to loop and trigger another uninstall-install cycle.
308 // However, if we arrived here via an uninstall-triggered-install (and in
309 // that case |reinstall_pending_| will be true) then we know install
310 // has completed and we can reset |reinstall_pending_|.
311 if (!reinstall_pending_)
312 MaybeReinstallHotwordExtension();
314 reinstall_pending_ = false;
316 // Now that the extension is installed, if the user has not selected
317 // the preference on, make sure it is turned off.
319 // Disabling the extension automatically on install should only occur
320 // if the user is in the field trial for auto-install which is gated
321 // by the IsHotwordAllowed check. The check for IsHotwordAllowed() here
322 // can be removed once it's known that few people have manually
323 // installed extension.
324 if (IsHotwordAllowed() &&
325 !profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled)) {
326 DisableHotwordExtension(GetExtensionService(profile_));
330 bool HotwordService::MaybeReinstallHotwordExtension() {
331 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
333 ExtensionService* extension_service = GetExtensionService(profile_);
334 if (!extension_service)
337 const extensions::Extension* extension = extension_service->GetExtensionById(
338 ReinstalledExtensionId(), true);
342 // If the extension is currently pending, return and we'll check again
343 // after the install is finished.
344 extensions::PendingExtensionManager* pending_manager =
345 extension_service->pending_extension_manager();
346 if (pending_manager->IsIdPending(extension->id()))
349 // If there is already a pending request from HotwordService, don't try
350 // to uninstall either.
351 if (reinstall_pending_)
354 // Check if the current locale matches the previous. If they don't match,
355 // uninstall the extension.
356 if (!ShouldReinstallHotwordExtension())
359 // Ensure the call to OnExtensionUninstalled was triggered by a language
360 // change so it's okay to reinstall.
361 reinstall_pending_ = true;
363 // Disable always-on on a language change. We do this because the speaker-id
364 // model needs to be re-trained.
365 if (IsAlwaysOnEnabled()) {
366 profile_->GetPrefs()->SetBoolean(prefs::kHotwordAlwaysOnSearchEnabled,
370 return UninstallHotwordExtension(extension_service);
373 bool HotwordService::UninstallHotwordExtension(
374 ExtensionService* extension_service) {
375 base::string16 error;
376 std::string extension_id = ReinstalledExtensionId();
377 if (!extension_service->UninstallExtension(
379 extensions::UNINSTALL_REASON_INTERNAL_MANAGEMENT,
380 base::Bind(&base::DoNothing),
382 LOG(WARNING) << "Cannot uninstall extension with id "
385 reinstall_pending_ = false;
391 bool HotwordService::IsServiceAvailable() {
394 // Determine if the extension is available.
395 extensions::ExtensionSystem* system =
396 extensions::ExtensionSystem::Get(profile_);
397 ExtensionService* service = system->extension_service();
398 // Include disabled extensions (true parameter) since it may not be enabled
399 // if the user opted out.
400 const extensions::Extension* extension =
401 service->GetExtensionById(ReinstalledExtensionId(), true);
403 error_message_ = IDS_HOTWORD_GENERIC_ERROR_MESSAGE;
405 RecordExtensionAvailabilityMetrics(service, extension);
406 RecordLoggingMetrics(profile_);
408 // Determine if NaCl is available.
409 bool nacl_enabled = false;
411 if (PathService::Get(chrome::FILE_NACL_PLUGIN, &path)) {
412 content::WebPluginInfo info;
413 PluginPrefs* plugin_prefs = PluginPrefs::GetForProfile(profile_).get();
414 if (content::PluginService::GetInstance()->GetPluginInfoByPath(path, &info))
415 nacl_enabled = plugin_prefs->IsPluginEnabled(info);
418 error_message_ = IDS_HOTWORD_NACL_DISABLED_ERROR_MESSAGE;
420 RecordErrorMetrics(error_message_);
422 // Determine if the proper audio capabilities exist.
423 // The first time this is called, it probably won't return in time, but that's
424 // why it won't be included in the error calculation (i.e., the call to
425 // IsAudioDeviceStateUpdated()). However, this use case is rare and typically
426 // the devices will be initialized by the time a user goes to settings.
427 bool audio_device_state_updated =
428 HotwordServiceFactory::IsAudioDeviceStateUpdated();
429 HotwordServiceFactory::GetInstance()->UpdateMicrophoneState();
430 if (audio_device_state_updated) {
431 bool audio_capture_allowed =
432 profile_->GetPrefs()->GetBoolean(prefs::kAudioCaptureAllowed);
433 if (!audio_capture_allowed ||
434 !HotwordServiceFactory::IsMicrophoneAvailable())
435 error_message_ = IDS_HOTWORD_MICROPHONE_ERROR_MESSAGE;
438 return (error_message_ == 0) && IsHotwordAllowed();
441 bool HotwordService::IsHotwordAllowed() {
442 std::string group = base::FieldTrialList::FindFullName(
443 hotword_internal::kHotwordFieldTrialName);
444 return !group.empty() &&
445 group != hotword_internal::kHotwordFieldTrialDisabledGroupName &&
446 DoesHotwordSupportLanguage(profile_);
449 bool HotwordService::IsOptedIntoAudioLogging() {
450 // Do not opt the user in if the preference has not been set.
452 profile_->GetPrefs()->HasPrefPath(prefs::kHotwordAudioLoggingEnabled) &&
453 profile_->GetPrefs()->GetBoolean(prefs::kHotwordAudioLoggingEnabled);
456 bool HotwordService::IsAlwaysOnEnabled() {
458 profile_->GetPrefs()->HasPrefPath(prefs::kHotwordAlwaysOnSearchEnabled) &&
459 profile_->GetPrefs()->GetBoolean(prefs::kHotwordAlwaysOnSearchEnabled);
462 void HotwordService::EnableHotwordExtension(
463 ExtensionService* extension_service) {
464 if (extension_service && !IsExperimentalHotwordingEnabled())
465 extension_service->EnableExtension(extension_misc::kHotwordExtensionId);
468 void HotwordService::DisableHotwordExtension(
469 ExtensionService* extension_service) {
470 if (extension_service) {
471 extension_service->DisableExtension(
472 extension_misc::kHotwordExtensionId,
473 extensions::Extension::DISABLE_USER_ACTION);
477 void HotwordService::LaunchHotwordAudioVerificationApp(
478 const LaunchMode& launch_mode) {
479 hotword_audio_verification_launch_mode_ = launch_mode;
481 ExtensionService* extension_service = GetExtensionService(profile_);
482 if (!extension_service)
484 const extensions::Extension* extension = extension_service->GetExtensionById(
485 extension_misc::kHotwordAudioVerificationAppId, true);
489 OpenApplication(AppLaunchParams(
490 profile_, extension, extensions::LAUNCH_CONTAINER_WINDOW, NEW_WINDOW));
493 HotwordService::LaunchMode
494 HotwordService::GetHotwordAudioVerificationLaunchMode() {
495 return hotword_audio_verification_launch_mode_;
498 void HotwordService::StartTraining() {
501 if (!IsServiceAvailable())
504 HotwordPrivateEventService* event_service =
505 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
507 event_service->OnEnabledChanged(hotword_internal::kHotwordTrainingEnabled);
510 void HotwordService::FinalizeSpeakerModel() {
511 if (!IsServiceAvailable())
514 HotwordPrivateEventService* event_service =
515 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
517 event_service->OnFinalizeSpeakerModel();
520 void HotwordService::StopTraining() {
523 if (!IsServiceAvailable())
526 HotwordPrivateEventService* event_service =
527 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
529 event_service->OnEnabledChanged(hotword_internal::kHotwordTrainingEnabled);
532 void HotwordService::NotifyHotwordTriggered() {
533 if (!IsServiceAvailable())
536 HotwordPrivateEventService* event_service =
537 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
539 event_service->OnHotwordTriggered();
542 bool HotwordService::IsTraining() {
546 void HotwordService::OnHotwordSearchEnabledChanged(
547 const std::string& pref_name) {
548 DCHECK_EQ(pref_name, std::string(prefs::kHotwordSearchEnabled));
550 ExtensionService* extension_service = GetExtensionService(profile_);
551 if (profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled))
552 EnableHotwordExtension(extension_service);
554 DisableHotwordExtension(extension_service);
557 void HotwordService::RequestHotwordSession(HotwordClient* client) {
558 if (!IsServiceAvailable() || (client_ && client_ != client))
563 HotwordPrivateEventService* event_service =
564 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
566 event_service->OnHotwordSessionRequested();
569 void HotwordService::StopHotwordSession(HotwordClient* client) {
570 if (!IsServiceAvailable())
573 // Do nothing if there's no client.
576 DCHECK(client_ == client);
579 HotwordPrivateEventService* event_service =
580 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
582 event_service->OnHotwordSessionStopped();
585 void HotwordService::SetPreviousLanguagePref() {
586 profile_->GetPrefs()->SetString(prefs::kHotwordPreviousLanguage,
587 GetCurrentLocale(profile_));
590 bool HotwordService::ShouldReinstallHotwordExtension() {
591 // If there is no previous locale pref, then this is the first install
592 // so no need to uninstall first.
593 if (!profile_->GetPrefs()->HasPrefPath(prefs::kHotwordPreviousLanguage))
596 std::string previous_locale =
597 profile_->GetPrefs()->GetString(prefs::kHotwordPreviousLanguage);
598 std::string locale = GetCurrentLocale(profile_);
600 // If it's a new locale, then the old extension should be uninstalled.
601 return locale != previous_locale &&
602 HotwordService::DoesHotwordSupportLanguage(profile_);