Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / search / hotword_service.cc
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.
4
5 #include "chrome/browser/search/hotword_service.h"
6
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_service_factory.h"
23 #include "chrome/browser/ui/extensions/application_launch.h"
24 #include "chrome/common/chrome_paths.h"
25 #include "chrome/common/chrome_switches.h"
26 #include "chrome/common/extensions/extension_constants.h"
27 #include "chrome/common/pref_names.h"
28 #include "chrome/grit/generated_resources.h"
29 #include "content/public/browser/browser_thread.h"
30 #include "content/public/browser/notification_service.h"
31 #include "content/public/browser/plugin_service.h"
32 #include "content/public/common/webplugininfo.h"
33 #include "extensions/browser/extension_system.h"
34 #include "extensions/browser/uninstall_reason.h"
35 #include "extensions/common/extension.h"
36 #include "extensions/common/one_shot_event.h"
37 #include "ui/base/l10n/l10n_util.h"
38
39 using extensions::BrowserContextKeyedAPIFactory;
40 using extensions::HotwordPrivateEventService;
41
42 namespace {
43
44 // Allowed languages for hotwording.
45 static const char* kSupportedLocales[] = {
46   "en",
47   "de",
48   "fr",
49   "ru"
50 };
51
52 // Enum describing the state of the hotword preference.
53 // This is used for UMA stats -- do not reorder or delete items; only add to
54 // the end.
55 enum HotwordEnabled {
56   UNSET = 0,  // The hotword preference has not been set.
57   ENABLED,    // The hotword preference is enabled.
58   DISABLED,   // The hotword preference is disabled.
59   NUM_HOTWORD_ENABLED_METRICS
60 };
61
62 // Enum describing the availability state of the hotword extension.
63 // This is used for UMA stats -- do not reorder or delete items; only add to
64 // the end.
65 enum HotwordExtensionAvailability {
66   UNAVAILABLE = 0,
67   AVAILABLE,
68   PENDING_DOWNLOAD,
69   DISABLED_EXTENSION,
70   NUM_HOTWORD_EXTENSION_AVAILABILITY_METRICS
71 };
72
73 // Enum describing the types of errors that can arise when determining
74 // if hotwording can be used. NO_ERROR is used so it can be seen how often
75 // errors arise relative to when they do not.
76 // This is used for UMA stats -- do not reorder or delete items; only add to
77 // the end.
78 enum HotwordError {
79   NO_HOTWORD_ERROR = 0,
80   GENERIC_HOTWORD_ERROR,
81   NACL_HOTWORD_ERROR,
82   MICROPHONE_HOTWORD_ERROR,
83   NUM_HOTWORD_ERROR_METRICS
84 };
85
86 void RecordExtensionAvailabilityMetrics(
87     ExtensionService* service,
88     const extensions::Extension* extension) {
89   HotwordExtensionAvailability availability_state = UNAVAILABLE;
90   if (extension) {
91     availability_state = AVAILABLE;
92   } else if (service->pending_extension_manager() &&
93              service->pending_extension_manager()->IsIdPending(
94                  extension_misc::kHotwordExtensionId)) {
95     availability_state = PENDING_DOWNLOAD;
96   } else if (!service->IsExtensionEnabled(
97       extension_misc::kHotwordExtensionId)) {
98     availability_state = DISABLED_EXTENSION;
99   }
100   UMA_HISTOGRAM_ENUMERATION("Hotword.HotwordExtensionAvailability",
101                             availability_state,
102                             NUM_HOTWORD_EXTENSION_AVAILABILITY_METRICS);
103 }
104
105 void RecordLoggingMetrics(Profile* profile) {
106   // If the user is not opted in to hotword voice search, the audio logging
107   // metric is not valid so it is not recorded.
108   if (!profile->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled))
109     return;
110
111   UMA_HISTOGRAM_BOOLEAN(
112       "Hotword.HotwordAudioLogging",
113       profile->GetPrefs()->GetBoolean(prefs::kHotwordAudioLoggingEnabled));
114 }
115
116 void RecordErrorMetrics(int error_message) {
117   HotwordError error = NO_HOTWORD_ERROR;
118   switch (error_message) {
119     case IDS_HOTWORD_GENERIC_ERROR_MESSAGE:
120       error = GENERIC_HOTWORD_ERROR;
121       break;
122     case IDS_HOTWORD_NACL_DISABLED_ERROR_MESSAGE:
123       error = NACL_HOTWORD_ERROR;
124       break;
125     case IDS_HOTWORD_MICROPHONE_ERROR_MESSAGE:
126       error = MICROPHONE_HOTWORD_ERROR;
127       break;
128     default:
129       error = NO_HOTWORD_ERROR;
130   }
131
132   UMA_HISTOGRAM_ENUMERATION("Hotword.HotwordError",
133                             error,
134                             NUM_HOTWORD_ERROR_METRICS);
135 }
136
137 ExtensionService* GetExtensionService(Profile* profile) {
138   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
139
140   extensions::ExtensionSystem* extension_system =
141       extensions::ExtensionSystem::Get(profile);
142   return extension_system ?  extension_system->extension_service() : NULL;
143 }
144
145 std::string GetCurrentLocale(Profile* profile) {
146 #if defined(OS_CHROMEOS)
147   std::string profile_locale =
148       profile->GetPrefs()->GetString(prefs::kApplicationLocale);
149   if (!profile_locale.empty()) {
150     // On ChromeOS locale is per-profile, but only if set.
151     return profile_locale;
152   }
153 #endif
154   return g_browser_process->GetApplicationLocale();
155 }
156
157 }  // namespace
158
159 namespace hotword_internal {
160 // Constants for the hotword field trial.
161 const char kHotwordFieldTrialName[] = "VoiceTrigger";
162 const char kHotwordFieldTrialDisabledGroupName[] = "Disabled";
163 // Old preference constant.
164 const char kHotwordUnusablePrefName[] = "hotword.search_enabled";
165 }  // namespace hotword_internal
166
167 // static
168 bool HotwordService::DoesHotwordSupportLanguage(Profile* profile) {
169   std::string normalized_locale =
170       l10n_util::NormalizeLocale(GetCurrentLocale(profile));
171   base::StringToLowerASCII(&normalized_locale);
172
173   for (size_t i = 0; i < arraysize(kSupportedLocales); i++) {
174     if (normalized_locale.compare(0, 2, kSupportedLocales[i]) == 0)
175       return true;
176   }
177   return false;
178 }
179
180 // static
181 bool HotwordService::IsExperimentalHotwordingEnabled() {
182   CommandLine* command_line = CommandLine::ForCurrentProcess();
183   return command_line->HasSwitch(switches::kEnableExperimentalHotwording);
184 }
185
186 HotwordService::HotwordService(Profile* profile)
187     : profile_(profile),
188       extension_registry_observer_(this),
189       client_(NULL),
190       error_message_(0),
191       reinstall_pending_(false),
192       weak_factory_(this) {
193   extension_registry_observer_.Add(extensions::ExtensionRegistry::Get(profile));
194   // This will be called during profile initialization which is a good time
195   // to check the user's hotword state.
196   HotwordEnabled enabled_state = UNSET;
197   if (profile_->GetPrefs()->HasPrefPath(prefs::kHotwordSearchEnabled)) {
198     if (profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled))
199       enabled_state = ENABLED;
200     else
201       enabled_state = DISABLED;
202   } else {
203     // If the preference has not been set the hotword extension should
204     // not be running. However, this should only be done if auto-install
205     // is enabled which is gated through the IsHotwordAllowed check.
206     if (IsHotwordAllowed())
207       DisableHotwordExtension(GetExtensionService(profile_));
208   }
209   UMA_HISTOGRAM_ENUMERATION("Hotword.Enabled", enabled_state,
210                             NUM_HOTWORD_ENABLED_METRICS);
211
212   pref_registrar_.Init(profile_->GetPrefs());
213   pref_registrar_.Add(
214       prefs::kHotwordSearchEnabled,
215       base::Bind(&HotwordService::OnHotwordSearchEnabledChanged,
216                  base::Unretained(this)));
217
218   registrar_.Add(this,
219                  chrome::NOTIFICATION_BROWSER_WINDOW_READY,
220                  content::NotificationService::AllSources());
221
222   extensions::ExtensionSystem::Get(profile_)->ready().Post(
223       FROM_HERE,
224       base::Bind(base::IgnoreResult(
225           &HotwordService::MaybeReinstallHotwordExtension),
226                  weak_factory_.GetWeakPtr()));
227
228   // Clear the old user pref because it became unusable.
229   // TODO(rlp): Remove this code per crbug.com/358789.
230   if (profile_->GetPrefs()->HasPrefPath(
231           hotword_internal::kHotwordUnusablePrefName)) {
232     profile_->GetPrefs()->ClearPref(hotword_internal::kHotwordUnusablePrefName);
233   }
234 }
235
236 HotwordService::~HotwordService() {
237 }
238
239 void HotwordService::Observe(int type,
240                              const content::NotificationSource& source,
241                              const content::NotificationDetails& details) {
242   if (type == chrome::NOTIFICATION_BROWSER_WINDOW_READY) {
243     // The microphone monitor must be initialized as the page is loading
244     // so that the state of the microphone is available when the page
245     // loads. The Ok Google Hotword setting will display an error if there
246     // is no microphone but this information will not be up-to-date unless
247     // the monitor had already been started. Furthermore, the pop up to
248     // opt in to hotwording won't be available if it thinks there is no
249     // microphone. There is no hard guarantee that the monitor will actually
250     // be up by the time it's needed, but this is the best we can do without
251     // starting it at start up which slows down start up too much.
252     // The content/media for microphone uses the same observer design and
253     // makes use of the same audio device monitor.
254     HotwordServiceFactory::GetInstance()->UpdateMicrophoneState();
255   }
256 }
257
258 void HotwordService::OnExtensionUninstalled(
259     content::BrowserContext* browser_context,
260     const extensions::Extension* extension,
261     extensions::UninstallReason reason) {
262   CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
263
264   if (extension->id() != extension_misc::kHotwordExtensionId ||
265       profile_ != Profile::FromBrowserContext(browser_context) ||
266       !GetExtensionService(profile_))
267     return;
268
269   // If the extension wasn't uninstalled due to language change, don't try to
270   // reinstall it.
271   if (!reinstall_pending_)
272     return;
273
274   InstallHotwordExtensionFromWebstore();
275   SetPreviousLanguagePref();
276 }
277
278 void HotwordService::InstallHotwordExtensionFromWebstore() {
279   installer_ = new extensions::WebstoreStartupInstaller(
280       extension_misc::kHotwordExtensionId,
281       profile_,
282       false,
283       extensions::WebstoreStandaloneInstaller::Callback());
284   installer_->BeginInstall();
285 }
286
287 void HotwordService::OnExtensionInstalled(
288     content::BrowserContext* browser_context,
289     const extensions::Extension* extension,
290     bool is_update) {
291
292   if (extension->id() != extension_misc::kHotwordExtensionId ||
293       profile_ != Profile::FromBrowserContext(browser_context))
294     return;
295
296   // If the previous locale pref has never been set, set it now since
297   // the extension has been installed.
298   if (!profile_->GetPrefs()->HasPrefPath(prefs::kHotwordPreviousLanguage))
299     SetPreviousLanguagePref();
300
301   // If MaybeReinstallHotwordExtension already triggered an uninstall, we
302   // don't want to loop and trigger another uninstall-install cycle.
303   // However, if we arrived here via an uninstall-triggered-install (and in
304   // that case |reinstall_pending_| will be true) then we know install
305   // has completed and we can reset |reinstall_pending_|.
306   if (!reinstall_pending_)
307     MaybeReinstallHotwordExtension();
308   else
309     reinstall_pending_ = false;
310
311   // Now that the extension is installed, if the user has not selected
312   // the preference on, make sure it is turned off.
313   //
314   // Disabling the extension automatically on install should only occur
315   // if the user is in the field trial for auto-install which is gated
316   // by the IsHotwordAllowed check. The check for IsHotwordAllowed() here
317   // can be removed once it's known that few people have manually
318   // installed extension.
319   if (IsHotwordAllowed() &&
320       !profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled)) {
321     DisableHotwordExtension(GetExtensionService(profile_));
322   }
323 }
324
325 bool HotwordService::MaybeReinstallHotwordExtension() {
326   CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
327
328   ExtensionService* extension_service = GetExtensionService(profile_);
329   if (!extension_service)
330     return false;
331
332   const extensions::Extension* extension = extension_service->GetExtensionById(
333       extension_misc::kHotwordExtensionId, true);
334   if (!extension)
335     return false;
336
337   // If the extension is currently pending, return and we'll check again
338   // after the install is finished.
339   extensions::PendingExtensionManager* pending_manager =
340       extension_service->pending_extension_manager();
341   if (pending_manager->IsIdPending(extension->id()))
342     return false;
343
344   // If there is already a pending request from HotwordService, don't try
345   // to uninstall either.
346   if (reinstall_pending_)
347     return false;
348
349   // Check if the current locale matches the previous. If they don't match,
350   // uninstall the extension.
351   if (!ShouldReinstallHotwordExtension())
352     return false;
353
354   // Ensure the call to OnExtensionUninstalled was triggered by a language
355   // change so it's okay to reinstall.
356   reinstall_pending_ = true;
357
358   return UninstallHotwordExtension(extension_service);
359 }
360
361 bool HotwordService::UninstallHotwordExtension(
362     ExtensionService* extension_service) {
363   base::string16 error;
364   if (!extension_service->UninstallExtension(
365           extension_misc::kHotwordExtensionId,
366           extensions::UNINSTALL_REASON_INTERNAL_MANAGEMENT,
367           base::Bind(&base::DoNothing),
368           &error)) {
369     LOG(WARNING) << "Cannot uninstall extension with id "
370                  << extension_misc::kHotwordExtensionId
371                  << ": " << error;
372     reinstall_pending_ = false;
373     return false;
374   }
375   return true;
376 }
377
378 bool HotwordService::IsServiceAvailable() {
379   error_message_ = 0;
380
381   // Determine if the extension is available.
382   extensions::ExtensionSystem* system =
383       extensions::ExtensionSystem::Get(profile_);
384   ExtensionService* service = system->extension_service();
385   // Include disabled extensions (true parameter) since it may not be enabled
386   // if the user opted out.
387   std::string extensionId;
388   if (IsExperimentalHotwordingEnabled()) {
389     // TODO(amistry): Handle reloading on language change as the old extension
390     // does.
391     extensionId = extension_misc::kHotwordSharedModuleId;
392   } else {
393     extensionId = extension_misc::kHotwordExtensionId;
394   }
395   const extensions::Extension* extension =
396       service->GetExtensionById(extensionId, true);
397   if (!extension)
398     error_message_ = IDS_HOTWORD_GENERIC_ERROR_MESSAGE;
399
400   RecordExtensionAvailabilityMetrics(service, extension);
401   RecordLoggingMetrics(profile_);
402
403   // Determine if NaCl is available.
404   bool nacl_enabled = false;
405   base::FilePath path;
406   if (PathService::Get(chrome::FILE_NACL_PLUGIN, &path)) {
407     content::WebPluginInfo info;
408     PluginPrefs* plugin_prefs = PluginPrefs::GetForProfile(profile_).get();
409     if (content::PluginService::GetInstance()->GetPluginInfoByPath(path, &info))
410       nacl_enabled = plugin_prefs->IsPluginEnabled(info);
411   }
412   if (!nacl_enabled)
413     error_message_ = IDS_HOTWORD_NACL_DISABLED_ERROR_MESSAGE;
414
415   RecordErrorMetrics(error_message_);
416
417   // Determine if the proper audio capabilities exist.
418   bool audio_capture_allowed =
419       profile_->GetPrefs()->GetBoolean(prefs::kAudioCaptureAllowed);
420   if (!audio_capture_allowed || !HotwordServiceFactory::IsMicrophoneAvailable())
421     error_message_ = IDS_HOTWORD_MICROPHONE_ERROR_MESSAGE;
422
423   return (error_message_ == 0) && IsHotwordAllowed();
424 }
425
426 bool HotwordService::IsHotwordAllowed() {
427   std::string group = base::FieldTrialList::FindFullName(
428       hotword_internal::kHotwordFieldTrialName);
429   return !group.empty() &&
430       group != hotword_internal::kHotwordFieldTrialDisabledGroupName &&
431       DoesHotwordSupportLanguage(profile_);
432 }
433
434 bool HotwordService::IsOptedIntoAudioLogging() {
435   // Do not opt the user in if the preference has not been set.
436   return
437       profile_->GetPrefs()->HasPrefPath(prefs::kHotwordAudioLoggingEnabled) &&
438       profile_->GetPrefs()->GetBoolean(prefs::kHotwordAudioLoggingEnabled);
439 }
440
441 void HotwordService::EnableHotwordExtension(
442     ExtensionService* extension_service) {
443   if (extension_service)
444     extension_service->EnableExtension(extension_misc::kHotwordExtensionId);
445 }
446
447 void HotwordService::DisableHotwordExtension(
448     ExtensionService* extension_service) {
449   if (extension_service) {
450     extension_service->DisableExtension(
451         extension_misc::kHotwordExtensionId,
452         extensions::Extension::DISABLE_USER_ACTION);
453   }
454 }
455
456 void HotwordService::LaunchHotwordAudioVerificationApp(
457     const LaunchMode& launch_mode) {
458   hotword_audio_verification_launch_mode_ = launch_mode;
459
460   ExtensionService* extension_service = GetExtensionService(profile_);
461   if (!extension_service)
462     return;
463   const extensions::Extension* extension = extension_service->GetExtensionById(
464       extension_misc::kHotwordAudioVerificationAppId, true);
465   if (!extension)
466     return;
467
468   OpenApplication(AppLaunchParams(
469       profile_, extension, extensions::LAUNCH_CONTAINER_WINDOW, NEW_WINDOW));
470 }
471
472 HotwordService::LaunchMode
473 HotwordService::GetHotwordAudioVerificationLaunchMode() {
474   return hotword_audio_verification_launch_mode_;
475 }
476
477 void HotwordService::OnHotwordSearchEnabledChanged(
478     const std::string& pref_name) {
479   DCHECK_EQ(pref_name, std::string(prefs::kHotwordSearchEnabled));
480
481   ExtensionService* extension_service = GetExtensionService(profile_);
482   if (profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled))
483     EnableHotwordExtension(extension_service);
484   else
485     DisableHotwordExtension(extension_service);
486 }
487
488 void HotwordService::RequestHotwordSession(HotwordClient* client) {
489   if (!IsServiceAvailable() || (client_ && client_ != client))
490     return;
491
492   client_ = client;
493
494   HotwordPrivateEventService* event_service =
495       BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
496   if (event_service)
497     event_service->OnHotwordSessionRequested();
498 }
499
500 void HotwordService::StopHotwordSession(HotwordClient* client) {
501   if (!IsServiceAvailable())
502     return;
503
504   DCHECK(client_ == client);
505
506   client_ = NULL;
507   HotwordPrivateEventService* event_service =
508       BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
509   if (event_service)
510     event_service->OnHotwordSessionStopped();
511 }
512
513 void HotwordService::SetPreviousLanguagePref() {
514   profile_->GetPrefs()->SetString(prefs::kHotwordPreviousLanguage,
515                                   GetCurrentLocale(profile_));
516 }
517
518 bool HotwordService::ShouldReinstallHotwordExtension() {
519   // If there is no previous locale pref, then this is the first install
520   // so no need to uninstall first.
521   if (!profile_->GetPrefs()->HasPrefPath(prefs::kHotwordPreviousLanguage))
522     return false;
523
524   std::string previous_locale =
525       profile_->GetPrefs()->GetString(prefs::kHotwordPreviousLanguage);
526   std::string locale = GetCurrentLocale(profile_);
527
528   // If it's a new locale, then the old extension should be uninstalled.
529   return locale != previous_locale &&
530       HotwordService::DoesHotwordSupportLanguage(profile_);
531 }