hotword_service.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
14b562cf889bc59e1914dd2c5d9fbd7e7bfa1ad77Argyrios Kyrtzidis// Copyright 2013 The Chromium Authors. All rights reserved. 24b562cf889bc59e1914dd2c5d9fbd7e7bfa1ad77Argyrios Kyrtzidis// Use of this source code is governed by a BSD-style license that can be 34b562cf889bc59e1914dd2c5d9fbd7e7bfa1ad77Argyrios Kyrtzidis// found in the LICENSE file. 44b562cf889bc59e1914dd2c5d9fbd7e7bfa1ad77Argyrios Kyrtzidis 54b562cf889bc59e1914dd2c5d9fbd7e7bfa1ad77Argyrios Kyrtzidis#include "chrome/browser/search/hotword_service.h" 64b562cf889bc59e1914dd2c5d9fbd7e7bfa1ad77Argyrios Kyrtzidis 74b562cf889bc59e1914dd2c5d9fbd7e7bfa1ad77Argyrios Kyrtzidis#include "base/command_line.h" 84b562cf889bc59e1914dd2c5d9fbd7e7bfa1ad77Argyrios Kyrtzidis#include "base/i18n/case_conversion.h" 94b562cf889bc59e1914dd2c5d9fbd7e7bfa1ad77Argyrios Kyrtzidis#include "base/metrics/field_trial.h" 104b562cf889bc59e1914dd2c5d9fbd7e7bfa1ad77Argyrios Kyrtzidis#include "base/metrics/histogram.h" 114b562cf889bc59e1914dd2c5d9fbd7e7bfa1ad77Argyrios Kyrtzidis#include "base/path_service.h" 124b562cf889bc59e1914dd2c5d9fbd7e7bfa1ad77Argyrios Kyrtzidis#include "base/prefs/pref_service.h" 134b562cf889bc59e1914dd2c5d9fbd7e7bfa1ad77Argyrios Kyrtzidis#include "chrome/browser/browser_process.h" 140853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis#include "chrome/browser/chrome_notification_types.h" 150853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis#include "chrome/browser/extensions/api/hotword_private/hotword_private_api.h" 160853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis#include "chrome/browser/extensions/extension_service.h" 1731b87d8006d4863dd9b17e515ac720941efc38e3Daniel Dunbar#include "chrome/browser/extensions/pending_extension_manager.h" 180853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis#include "chrome/browser/extensions/updater/extension_updater.h" 1936c4464ba6cfc2a63dc67c493ef2f5ab2aea09ccSteve Naroff#include "chrome/browser/extensions/webstore_startup_installer.h" 20f96b524306ccfa623235d375deee79637bd38f29Steve Naroff#include "chrome/browser/plugins/plugin_prefs.h" 210853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis#include "chrome/browser/profiles/profile.h" 22f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar#include "chrome/browser/search/hotword_service_factory.h" 23f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar#include "chrome/common/chrome_paths.h" 244db64a461cb3442934afe43c83ed3f17f7c11c1dDouglas Gregor#include "chrome/common/chrome_switches.h" 254db64a461cb3442934afe43c83ed3f17f7c11c1dDouglas Gregor#include "chrome/common/extensions/extension_constants.h" 264db64a461cb3442934afe43c83ed3f17f7c11c1dDouglas Gregor#include "chrome/common/pref_names.h" 274db64a461cb3442934afe43c83ed3f17f7c11c1dDouglas Gregor#include "chrome/grit/generated_resources.h" 284db64a461cb3442934afe43c83ed3f17f7c11c1dDouglas Gregor#include "content/public/browser/browser_thread.h" 290853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis#include "content/public/browser/notification_service.h" 300853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis#include "content/public/browser/plugin_service.h" 31521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar#include "content/public/common/webplugininfo.h" 32521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar#include "extensions/browser/extension_system.h" 33521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar#include "extensions/browser/uninstall_reason.h" 34521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar#include "extensions/common/extension.h" 35521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar#include "extensions/common/one_shot_event.h" 36521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar#include "ui/base/l10n/l10n_util.h" 37521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar 38521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbarusing extensions::BrowserContextKeyedAPIFactory; 39521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbarusing extensions::HotwordPrivateEventService; 40521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar 410853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidisnamespace { 42f96b524306ccfa623235d375deee79637bd38f29Steve Naroff 43f96b524306ccfa623235d375deee79637bd38f29Steve Naroff// Allowed languages for hotwording. 440853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidisstatic const char* kSupportedLocales[] = { 450853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis "en", 460853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis "de", 4736c4464ba6cfc2a63dc67c493ef2f5ab2aea09ccSteve Naroff "fr", 4836c4464ba6cfc2a63dc67c493ef2f5ab2aea09ccSteve Naroff "ru" 4931b87d8006d4863dd9b17e515ac720941efc38e3Daniel Dunbar}; 500853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis 510853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis// Enum describing the state of the hotword preference. 520853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis// This is used for UMA stats -- do not reorder or delete items; only add to 530853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis// the end. 54e19944c93961b7618f4f3f3185f698f46369ea54Steve Naroffenum HotwordEnabled { 55f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar UNSET = 0, // The hotword preference has not been set. 567d1d49d2971b20a97b3c2a301470b9eaaa130137Douglas Gregor ENABLED, // The hotword preference is enabled. 577d1d49d2971b20a97b3c2a301470b9eaaa130137Douglas Gregor DISABLED, // The hotword preference is disabled. 587d1d49d2971b20a97b3c2a301470b9eaaa130137Douglas Gregor NUM_HOTWORD_ENABLED_METRICS 597d1d49d2971b20a97b3c2a301470b9eaaa130137Douglas Gregor}; 60c7822dbf3c01a2a5f837cff82ba7889ea755dacaDaniel Dunbar 61f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar// Enum describing the availability state of the hotword extension. 62c7822dbf3c01a2a5f837cff82ba7889ea755dacaDaniel Dunbar// This is used for UMA stats -- do not reorder or delete items; only add to 63c7822dbf3c01a2a5f837cff82ba7889ea755dacaDaniel Dunbar// the end. 64f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbarenum HotwordExtensionAvailability { 65f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar UNAVAILABLE = 0, 66f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar AVAILABLE, 67f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar PENDING_DOWNLOAD, 68f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar DISABLED_EXTENSION, 69f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar NUM_HOTWORD_EXTENSION_AVAILABILITY_METRICS 70f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar}; 71f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar 72f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar// Enum describing the types of errors that can arise when determining 7368d40e2d16b9fadba386853d6bbb60089291fdc5Daniel Dunbar// if hotwording can be used. NO_ERROR is used so it can be seen how often 7468d40e2d16b9fadba386853d6bbb60089291fdc5Daniel Dunbar// errors arise relative to when they do not. 7568d40e2d16b9fadba386853d6bbb60089291fdc5Daniel Dunbar// This is used for UMA stats -- do not reorder or delete items; only add to 76f96b524306ccfa623235d375deee79637bd38f29Steve Naroff// the end. 77f96b524306ccfa623235d375deee79637bd38f29Steve Naroffenum HotwordError { 78f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar NO_HOTWORD_ERROR = 0, 7931b87d8006d4863dd9b17e515ac720941efc38e3Daniel Dunbar GENERIC_HOTWORD_ERROR, 8031b87d8006d4863dd9b17e515ac720941efc38e3Daniel Dunbar NACL_HOTWORD_ERROR, 811eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump MICROPHONE_HOTWORD_ERROR, 820853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis NUM_HOTWORD_ERROR_METRICS 835262fda30b876c8aae95f2eb92e349418d6b14bbDaniel Dunbar}; 840853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis 850853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidisvoid RecordExtensionAvailabilityMetrics( 86c7822dbf3c01a2a5f837cff82ba7889ea755dacaDaniel Dunbar ExtensionService* service, 87c7822dbf3c01a2a5f837cff82ba7889ea755dacaDaniel Dunbar const extensions::Extension* extension) { 8831b87d8006d4863dd9b17e515ac720941efc38e3Daniel Dunbar HotwordExtensionAvailability availability_state = UNAVAILABLE; 8931b87d8006d4863dd9b17e515ac720941efc38e3Daniel Dunbar if (extension) { 900853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis availability_state = AVAILABLE; 910853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis } else if (service->pending_extension_manager() && 920853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis service->pending_extension_manager()->IsIdPending( 931eb4433ac451dc16f4133a88af2d002ac26c58efMike Stump extension_misc::kHotwordExtensionId)) { 940853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis availability_state = PENDING_DOWNLOAD; 950853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis } else if (!service->IsExtensionEnabled( 960853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis extension_misc::kHotwordExtensionId)) { 9736c4464ba6cfc2a63dc67c493ef2f5ab2aea09ccSteve Naroff availability_state = DISABLED_EXTENSION; 9836c4464ba6cfc2a63dc67c493ef2f5ab2aea09ccSteve Naroff } 99f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar UMA_HISTOGRAM_ENUMERATION("Hotword.HotwordExtensionAvailability", 10077accc11f04ed4ff9afd4e27d430144d4714be56Steve Naroff availability_state, 101e19944c93961b7618f4f3f3185f698f46369ea54Steve Naroff NUM_HOTWORD_EXTENSION_AVAILABILITY_METRICS); 102b85bca2676b433ae555db09de4dd2823ff13b856Zhongxing Xu} 103e19944c93961b7618f4f3f3185f698f46369ea54Steve Naroff 104f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbarvoid RecordLoggingMetrics(Profile* profile) { 1057d1d49d2971b20a97b3c2a301470b9eaaa130137Douglas Gregor // If the user is not opted in to hotword voice search, the audio logging 106f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar // metric is not valid so it is not recorded. 107f96b524306ccfa623235d375deee79637bd38f29Steve Naroff if (!profile->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled)) 108f96b524306ccfa623235d375deee79637bd38f29Steve Naroff return; 109f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar 110f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar UMA_HISTOGRAM_BOOLEAN( 111f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar "Hotword.HotwordAudioLogging", 112f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar profile->GetPrefs()->GetBoolean(prefs::kHotwordAudioLoggingEnabled)); 113f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar} 114f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar 115f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbarvoid RecordErrorMetrics(int error_message) { 116f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar HotwordError error = NO_HOTWORD_ERROR; 117f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar switch (error_message) { 118f772d1e2a5688572d07f42896a50ac57a4a41fe8Daniel Dunbar case IDS_HOTWORD_GENERIC_ERROR_MESSAGE: 1194db64a461cb3442934afe43c83ed3f17f7c11c1dDouglas Gregor error = GENERIC_HOTWORD_ERROR; 1204db64a461cb3442934afe43c83ed3f17f7c11c1dDouglas Gregor break; 1214db64a461cb3442934afe43c83ed3f17f7c11c1dDouglas Gregor case IDS_HOTWORD_NACL_DISABLED_ERROR_MESSAGE: 1224db64a461cb3442934afe43c83ed3f17f7c11c1dDouglas Gregor error = NACL_HOTWORD_ERROR; 1230853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis break; 1240853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis case IDS_HOTWORD_MICROPHONE_ERROR_MESSAGE: 12531b87d8006d4863dd9b17e515ac720941efc38e3Daniel Dunbar error = MICROPHONE_HOTWORD_ERROR; 1260853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis break; 1275262fda30b876c8aae95f2eb92e349418d6b14bbDaniel Dunbar default: 1285262fda30b876c8aae95f2eb92e349418d6b14bbDaniel Dunbar error = NO_HOTWORD_ERROR; 12931b87d8006d4863dd9b17e515ac720941efc38e3Daniel Dunbar } 13031b87d8006d4863dd9b17e515ac720941efc38e3Daniel Dunbar 1310853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis UMA_HISTOGRAM_ENUMERATION("Hotword.HotwordError", 1325262fda30b876c8aae95f2eb92e349418d6b14bbDaniel Dunbar error, 1335cf48766d626ff6b223acc9d4b7e415ca8480836Ted Kremenek NUM_HOTWORD_ERROR_METRICS); 1344db64a461cb3442934afe43c83ed3f17f7c11c1dDouglas Gregor} 1354db64a461cb3442934afe43c83ed3f17f7c11c1dDouglas Gregor 1364db64a461cb3442934afe43c83ed3f17f7c11c1dDouglas GregorExtensionService* GetExtensionService(Profile* profile) { 137521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 138521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar 139521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar extensions::ExtensionSystem* extension_system = 140521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar extensions::ExtensionSystem::Get(profile); 141521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar return extension_system ? extension_system->extension_service() : NULL; 142521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar} 143521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar 1445262fda30b876c8aae95f2eb92e349418d6b14bbDaniel Dunbarstd::string GetCurrentLocale(Profile* profile) { 1455262fda30b876c8aae95f2eb92e349418d6b14bbDaniel Dunbar#if defined(OS_CHROMEOS) 146521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar std::string profile_locale = 147521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar profile->GetPrefs()->GetString(prefs::kApplicationLocale); 148521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar if (!profile_locale.empty()) { 149521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar // On ChromeOS locale is per-profile, but only if set. 150521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar return profile_locale; 15168ea2ac7fd98a5a44c7a5b04c54076cf794531cbDaniel Dunbar } 152521bf9c529e653ab28896d027352d3e16e2672d5Daniel Dunbar#endif 1537b55668db7618334cc40011d3c1e128524d89462Daniel Dunbar return g_browser_process->GetApplicationLocale(); 1547b55668db7618334cc40011d3c1e128524d89462Daniel Dunbar} 1557b55668db7618334cc40011d3c1e128524d89462Daniel Dunbar 1567b55668db7618334cc40011d3c1e128524d89462Daniel Dunbar} // namespace 1577b55668db7618334cc40011d3c1e128524d89462Daniel Dunbar 1587b55668db7618334cc40011d3c1e128524d89462Daniel Dunbarnamespace hotword_internal { 1597b55668db7618334cc40011d3c1e128524d89462Daniel Dunbar// Constants for the hotword field trial. 1605262fda30b876c8aae95f2eb92e349418d6b14bbDaniel Dunbarconst char kHotwordFieldTrialName[] = "VoiceTrigger"; 1615262fda30b876c8aae95f2eb92e349418d6b14bbDaniel Dunbarconst char kHotwordFieldTrialDisabledGroupName[] = "Disabled"; 1627b55668db7618334cc40011d3c1e128524d89462Daniel Dunbar// Old preference constant. 163869824e87940f97b87064db2df2861e82e08a8c6Daniel Dunbarconst char kHotwordUnusablePrefName[] = "hotword.search_enabled"; 1647b55668db7618334cc40011d3c1e128524d89462Daniel Dunbar} // namespace hotword_internal 1657b55668db7618334cc40011d3c1e128524d89462Daniel Dunbar 1667b55668db7618334cc40011d3c1e128524d89462Daniel Dunbar// static 1677b55668db7618334cc40011d3c1e128524d89462Daniel Dunbarbool HotwordService::DoesHotwordSupportLanguage(Profile* profile) { 1687b55668db7618334cc40011d3c1e128524d89462Daniel Dunbar std::string normalized_locale = 1697b55668db7618334cc40011d3c1e128524d89462Daniel Dunbar l10n_util::NormalizeLocale(GetCurrentLocale(profile)); 170869824e87940f97b87064db2df2861e82e08a8c6Daniel Dunbar base::StringToLowerASCII(&normalized_locale); 1717b55668db7618334cc40011d3c1e128524d89462Daniel Dunbar 1724db64a461cb3442934afe43c83ed3f17f7c11c1dDouglas Gregor for (size_t i = 0; i < arraysize(kSupportedLocales); i++) { 1734db64a461cb3442934afe43c83ed3f17f7c11c1dDouglas Gregor if (normalized_locale.compare(0, 2, kSupportedLocales[i]) == 0) 1744db64a461cb3442934afe43c83ed3f17f7c11c1dDouglas Gregor return true; 1750853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis } 1760853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis return false; 1770853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis} 1780853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis 1790853a02c3b04d96a3c432b883e403175c954cd81Argyrios Kyrtzidis// static 180bool HotwordService::IsExperimentalHotwordingEnabled() { 181 CommandLine* command_line = CommandLine::ForCurrentProcess(); 182 return command_line->HasSwitch(switches::kEnableExperimentalHotwording); 183} 184 185HotwordService::HotwordService(Profile* profile) 186 : profile_(profile), 187 extension_registry_observer_(this), 188 client_(NULL), 189 error_message_(0), 190 reinstall_pending_(false), 191 weak_factory_(this) { 192 extension_registry_observer_.Add(extensions::ExtensionRegistry::Get(profile)); 193 // This will be called during profile initialization which is a good time 194 // to check the user's hotword state. 195 HotwordEnabled enabled_state = UNSET; 196 if (profile_->GetPrefs()->HasPrefPath(prefs::kHotwordSearchEnabled)) { 197 if (profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled)) 198 enabled_state = ENABLED; 199 else 200 enabled_state = DISABLED; 201 } else { 202 // If the preference has not been set the hotword extension should 203 // not be running. However, this should only be done if auto-install 204 // is enabled which is gated through the IsHotwordAllowed check. 205 if (IsHotwordAllowed()) 206 DisableHotwordExtension(GetExtensionService(profile_)); 207 } 208 UMA_HISTOGRAM_ENUMERATION("Hotword.Enabled", enabled_state, 209 NUM_HOTWORD_ENABLED_METRICS); 210 211 pref_registrar_.Init(profile_->GetPrefs()); 212 pref_registrar_.Add( 213 prefs::kHotwordSearchEnabled, 214 base::Bind(&HotwordService::OnHotwordSearchEnabledChanged, 215 base::Unretained(this))); 216 217 registrar_.Add(this, 218 chrome::NOTIFICATION_BROWSER_WINDOW_READY, 219 content::NotificationService::AllSources()); 220 221 extensions::ExtensionSystem::Get(profile_)->ready().Post( 222 FROM_HERE, 223 base::Bind(base::IgnoreResult( 224 &HotwordService::MaybeReinstallHotwordExtension), 225 weak_factory_.GetWeakPtr())); 226 227 // Clear the old user pref because it became unusable. 228 // TODO(rlp): Remove this code per crbug.com/358789. 229 if (profile_->GetPrefs()->HasPrefPath( 230 hotword_internal::kHotwordUnusablePrefName)) { 231 profile_->GetPrefs()->ClearPref(hotword_internal::kHotwordUnusablePrefName); 232 } 233} 234 235HotwordService::~HotwordService() { 236} 237 238void HotwordService::Observe(int type, 239 const content::NotificationSource& source, 240 const content::NotificationDetails& details) { 241 if (type == chrome::NOTIFICATION_BROWSER_WINDOW_READY) { 242 // The microphone monitor must be initialized as the page is loading 243 // so that the state of the microphone is available when the page 244 // loads. The Ok Google Hotword setting will display an error if there 245 // is no microphone but this information will not be up-to-date unless 246 // the monitor had already been started. Furthermore, the pop up to 247 // opt in to hotwording won't be available if it thinks there is no 248 // microphone. There is no hard guarantee that the monitor will actually 249 // be up by the time it's needed, but this is the best we can do without 250 // starting it at start up which slows down start up too much. 251 // The content/media for microphone uses the same observer design and 252 // makes use of the same audio device monitor. 253 HotwordServiceFactory::GetInstance()->UpdateMicrophoneState(); 254 } 255} 256 257void HotwordService::OnExtensionUninstalled( 258 content::BrowserContext* browser_context, 259 const extensions::Extension* extension, 260 extensions::UninstallReason reason) { 261 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 262 263 if (extension->id() != extension_misc::kHotwordExtensionId || 264 profile_ != Profile::FromBrowserContext(browser_context) || 265 !GetExtensionService(profile_)) 266 return; 267 268 // If the extension wasn't uninstalled due to language change, don't try to 269 // reinstall it. 270 if (!reinstall_pending_) 271 return; 272 273 InstallHotwordExtensionFromWebstore(); 274 SetPreviousLanguagePref(); 275} 276 277void HotwordService::InstallHotwordExtensionFromWebstore() { 278 installer_ = new extensions::WebstoreStartupInstaller( 279 extension_misc::kHotwordExtensionId, 280 profile_, 281 false, 282 extensions::WebstoreStandaloneInstaller::Callback()); 283 installer_->BeginInstall(); 284} 285 286void HotwordService::OnExtensionInstalled( 287 content::BrowserContext* browser_context, 288 const extensions::Extension* extension, 289 bool is_update) { 290 291 if (extension->id() != extension_misc::kHotwordExtensionId || 292 profile_ != Profile::FromBrowserContext(browser_context)) 293 return; 294 295 // If the previous locale pref has never been set, set it now since 296 // the extension has been installed. 297 if (!profile_->GetPrefs()->HasPrefPath(prefs::kHotwordPreviousLanguage)) 298 SetPreviousLanguagePref(); 299 300 // If MaybeReinstallHotwordExtension already triggered an uninstall, we 301 // don't want to loop and trigger another uninstall-install cycle. 302 // However, if we arrived here via an uninstall-triggered-install (and in 303 // that case |reinstall_pending_| will be true) then we know install 304 // has completed and we can reset |reinstall_pending_|. 305 if (!reinstall_pending_) 306 MaybeReinstallHotwordExtension(); 307 else 308 reinstall_pending_ = false; 309 310 // Now that the extension is installed, if the user has not selected 311 // the preference on, make sure it is turned off. 312 // 313 // Disabling the extension automatically on install should only occur 314 // if the user is in the field trial for auto-install which is gated 315 // by the IsHotwordAllowed check. The check for IsHotwordAllowed() here 316 // can be removed once it's known that few people have manually 317 // installed extension. 318 if (IsHotwordAllowed() && 319 !profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled)) { 320 DisableHotwordExtension(GetExtensionService(profile_)); 321 } 322} 323 324bool HotwordService::MaybeReinstallHotwordExtension() { 325 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 326 327 ExtensionService* extension_service = GetExtensionService(profile_); 328 if (!extension_service) 329 return false; 330 331 const extensions::Extension* extension = extension_service->GetExtensionById( 332 extension_misc::kHotwordExtensionId, true); 333 if (!extension) 334 return false; 335 336 // If the extension is currently pending, return and we'll check again 337 // after the install is finished. 338 extensions::PendingExtensionManager* pending_manager = 339 extension_service->pending_extension_manager(); 340 if (pending_manager->IsIdPending(extension->id())) 341 return false; 342 343 // If there is already a pending request from HotwordService, don't try 344 // to uninstall either. 345 if (reinstall_pending_) 346 return false; 347 348 // Check if the current locale matches the previous. If they don't match, 349 // uninstall the extension. 350 if (!ShouldReinstallHotwordExtension()) 351 return false; 352 353 // Ensure the call to OnExtensionUninstalled was triggered by a language 354 // change so it's okay to reinstall. 355 reinstall_pending_ = true; 356 357 return UninstallHotwordExtension(extension_service); 358} 359 360bool HotwordService::UninstallHotwordExtension( 361 ExtensionService* extension_service) { 362 base::string16 error; 363 if (!extension_service->UninstallExtension( 364 extension_misc::kHotwordExtensionId, 365 extensions::UNINSTALL_REASON_INTERNAL_MANAGEMENT, 366 base::Bind(&base::DoNothing), 367 &error)) { 368 LOG(WARNING) << "Cannot uninstall extension with id " 369 << extension_misc::kHotwordExtensionId 370 << ": " << error; 371 reinstall_pending_ = false; 372 return false; 373 } 374 return true; 375} 376 377bool HotwordService::IsServiceAvailable() { 378 error_message_ = 0; 379 380 // Determine if the extension is available. 381 extensions::ExtensionSystem* system = 382 extensions::ExtensionSystem::Get(profile_); 383 ExtensionService* service = system->extension_service(); 384 // Include disabled extensions (true parameter) since it may not be enabled 385 // if the user opted out. 386 const extensions::Extension* extension = 387 service->GetExtensionById(extension_misc::kHotwordExtensionId, true); 388 if (!extension) 389 error_message_ = IDS_HOTWORD_GENERIC_ERROR_MESSAGE; 390 391 RecordExtensionAvailabilityMetrics(service, extension); 392 RecordLoggingMetrics(profile_); 393 394 // Determine if NaCl is available. 395 bool nacl_enabled = false; 396 base::FilePath path; 397 if (PathService::Get(chrome::FILE_NACL_PLUGIN, &path)) { 398 content::WebPluginInfo info; 399 PluginPrefs* plugin_prefs = PluginPrefs::GetForProfile(profile_).get(); 400 if (content::PluginService::GetInstance()->GetPluginInfoByPath(path, &info)) 401 nacl_enabled = plugin_prefs->IsPluginEnabled(info); 402 } 403 if (!nacl_enabled) 404 error_message_ = IDS_HOTWORD_NACL_DISABLED_ERROR_MESSAGE; 405 406 RecordErrorMetrics(error_message_); 407 408 // Determine if the proper audio capabilities exist. 409 bool audio_capture_allowed = 410 profile_->GetPrefs()->GetBoolean(prefs::kAudioCaptureAllowed); 411 if (!audio_capture_allowed || !HotwordServiceFactory::IsMicrophoneAvailable()) 412 error_message_ = IDS_HOTWORD_MICROPHONE_ERROR_MESSAGE; 413 414 return (error_message_ == 0) && IsHotwordAllowed(); 415} 416 417bool HotwordService::IsHotwordAllowed() { 418 std::string group = base::FieldTrialList::FindFullName( 419 hotword_internal::kHotwordFieldTrialName); 420 return !group.empty() && 421 group != hotword_internal::kHotwordFieldTrialDisabledGroupName && 422 DoesHotwordSupportLanguage(profile_); 423} 424 425bool HotwordService::IsOptedIntoAudioLogging() { 426 // Do not opt the user in if the preference has not been set. 427 return 428 profile_->GetPrefs()->HasPrefPath(prefs::kHotwordAudioLoggingEnabled) && 429 profile_->GetPrefs()->GetBoolean(prefs::kHotwordAudioLoggingEnabled); 430} 431 432void HotwordService::EnableHotwordExtension( 433 ExtensionService* extension_service) { 434 if (extension_service) 435 extension_service->EnableExtension(extension_misc::kHotwordExtensionId); 436} 437 438void HotwordService::DisableHotwordExtension( 439 ExtensionService* extension_service) { 440 if (extension_service) { 441 extension_service->DisableExtension( 442 extension_misc::kHotwordExtensionId, 443 extensions::Extension::DISABLE_USER_ACTION); 444 } 445} 446 447void HotwordService::OnHotwordSearchEnabledChanged( 448 const std::string& pref_name) { 449 DCHECK_EQ(pref_name, std::string(prefs::kHotwordSearchEnabled)); 450 451 ExtensionService* extension_service = GetExtensionService(profile_); 452 if (profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled)) 453 EnableHotwordExtension(extension_service); 454 else 455 DisableHotwordExtension(extension_service); 456} 457 458void HotwordService::RequestHotwordSession(HotwordClient* client) { 459 if (!IsServiceAvailable() || (client_ && client_ != client)) 460 return; 461 462 client_ = client; 463 464 HotwordPrivateEventService* event_service = 465 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_); 466 if (event_service) 467 event_service->OnHotwordSessionRequested(); 468} 469 470void HotwordService::StopHotwordSession(HotwordClient* client) { 471 if (!IsServiceAvailable()) 472 return; 473 474 DCHECK(client_ == client); 475 476 client_ = NULL; 477 HotwordPrivateEventService* event_service = 478 BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_); 479 if (event_service) 480 event_service->OnHotwordSessionStopped(); 481} 482 483void HotwordService::SetPreviousLanguagePref() { 484 profile_->GetPrefs()->SetString(prefs::kHotwordPreviousLanguage, 485 GetCurrentLocale(profile_)); 486} 487 488bool HotwordService::ShouldReinstallHotwordExtension() { 489 // If there is no previous locale pref, then this is the first install 490 // so no need to uninstall first. 491 if (!profile_->GetPrefs()->HasPrefPath(prefs::kHotwordPreviousLanguage)) 492 return false; 493 494 std::string previous_locale = 495 profile_->GetPrefs()->GetString(prefs::kHotwordPreviousLanguage); 496 std::string locale = GetCurrentLocale(profile_); 497 498 // If it's a new locale, then the old extension should be uninstalled. 499 return locale != previous_locale && 500 HotwordService::DoesHotwordSupportLanguage(profile_); 501} 502