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