Update To 11.40.268.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_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"
39
40 using extensions::BrowserContextKeyedAPIFactory;
41 using extensions::HotwordPrivateEventService;
42
43 namespace {
44
45 // Allowed languages for hotwording.
46 static const char* kSupportedLocales[] = {
47   "en",
48   "de",
49   "fr",
50   "ru"
51 };
52
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
55 // the end.
56 enum HotwordEnabled {
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
61 };
62
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
65 // the end.
66 enum HotwordExtensionAvailability {
67   UNAVAILABLE = 0,
68   AVAILABLE,
69   PENDING_DOWNLOAD,
70   DISABLED_EXTENSION,
71   NUM_HOTWORD_EXTENSION_AVAILABILITY_METRICS
72 };
73
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
78 // the end.
79 enum HotwordError {
80   NO_HOTWORD_ERROR = 0,
81   GENERIC_HOTWORD_ERROR,
82   NACL_HOTWORD_ERROR,
83   MICROPHONE_HOTWORD_ERROR,
84   NUM_HOTWORD_ERROR_METRICS
85 };
86
87 void RecordExtensionAvailabilityMetrics(
88     ExtensionService* service,
89     const extensions::Extension* extension) {
90   HotwordExtensionAvailability availability_state = UNAVAILABLE;
91   if (extension) {
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;
100   }
101   UMA_HISTOGRAM_ENUMERATION("Hotword.HotwordExtensionAvailability",
102                             availability_state,
103                             NUM_HOTWORD_EXTENSION_AVAILABILITY_METRICS);
104 }
105
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))
110     return;
111
112   UMA_HISTOGRAM_BOOLEAN(
113       "Hotword.HotwordAudioLogging",
114       profile->GetPrefs()->GetBoolean(prefs::kHotwordAudioLoggingEnabled));
115 }
116
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;
122       break;
123     case IDS_HOTWORD_NACL_DISABLED_ERROR_MESSAGE:
124       error = NACL_HOTWORD_ERROR;
125       break;
126     case IDS_HOTWORD_MICROPHONE_ERROR_MESSAGE:
127       error = MICROPHONE_HOTWORD_ERROR;
128       break;
129     default:
130       error = NO_HOTWORD_ERROR;
131   }
132
133   UMA_HISTOGRAM_ENUMERATION("Hotword.HotwordError",
134                             error,
135                             NUM_HOTWORD_ERROR_METRICS);
136 }
137
138 ExtensionService* GetExtensionService(Profile* profile) {
139   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
140
141   extensions::ExtensionSystem* extension_system =
142       extensions::ExtensionSystem::Get(profile);
143   return extension_system ?  extension_system->extension_service() : NULL;
144 }
145
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;
153   }
154 #endif
155   return g_browser_process->GetApplicationLocale();
156 }
157
158 }  // namespace
159
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
170
171 // static
172 bool HotwordService::DoesHotwordSupportLanguage(Profile* profile) {
173   std::string normalized_locale =
174       l10n_util::NormalizeLocale(GetCurrentLocale(profile));
175   base::StringToLowerASCII(&normalized_locale);
176
177   for (size_t i = 0; i < arraysize(kSupportedLocales); i++) {
178     if (normalized_locale.compare(0, 2, kSupportedLocales[i]) == 0)
179       return true;
180   }
181   return false;
182 }
183
184 // static
185 bool HotwordService::IsExperimentalHotwordingEnabled() {
186   std::string group = base::FieldTrialList::FindFullName(
187       hotword_internal::kHotwordFieldTrialName);
188   if (!group.empty() &&
189       group == hotword_internal::kHotwordFieldTrialExperimentalGroupName) {
190     return true;
191   }
192
193   CommandLine* command_line = CommandLine::ForCurrentProcess();
194   return command_line->HasSwitch(switches::kEnableExperimentalHotwording);
195 }
196
197 HotwordService::HotwordService(Profile* profile)
198     : profile_(profile),
199       extension_registry_observer_(this),
200       client_(NULL),
201       error_message_(0),
202       reinstall_pending_(false),
203       training_(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_));
212   } else {
213     if (profile_->GetPrefs()->HasPrefPath(prefs::kHotwordSearchEnabled)) {
214       if (profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled))
215         enabled_state = ENABLED;
216       else
217         enabled_state = DISABLED;
218     } else {
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_));
224     }
225   }
226   UMA_HISTOGRAM_ENUMERATION("Hotword.Enabled", enabled_state,
227                             NUM_HOTWORD_ENABLED_METRICS);
228
229   pref_registrar_.Init(profile_->GetPrefs());
230   pref_registrar_.Add(
231       prefs::kHotwordSearchEnabled,
232       base::Bind(&HotwordService::OnHotwordSearchEnabledChanged,
233                  base::Unretained(this)));
234
235   extensions::ExtensionSystem::Get(profile_)->ready().Post(
236       FROM_HERE,
237       base::Bind(base::IgnoreResult(
238           &HotwordService::MaybeReinstallHotwordExtension),
239                  weak_factory_.GetWeakPtr()));
240
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);
246   }
247
248   audio_history_handler_.reset(new HotwordAudioHistoryHandler(profile_));
249 }
250
251 HotwordService::~HotwordService() {
252 }
253
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));
259
260   if ((extension->id() != extension_misc::kHotwordExtensionId &&
261        extension->id() != extension_misc::kHotwordSharedModuleId) ||
262        profile_ != Profile::FromBrowserContext(browser_context) ||
263       !GetExtensionService(profile_))
264     return;
265
266   // If the extension wasn't uninstalled due to language change, don't try to
267   // reinstall it.
268   if (!reinstall_pending_)
269     return;
270
271   InstallHotwordExtensionFromWebstore();
272   SetPreviousLanguagePref();
273 }
274
275 std::string HotwordService::ReinstalledExtensionId() {
276   if (IsExperimentalHotwordingEnabled())
277     return extension_misc::kHotwordSharedModuleId;
278
279   return extension_misc::kHotwordExtensionId;
280 }
281
282 void HotwordService::InstallHotwordExtensionFromWebstore() {
283   installer_ = new extensions::WebstoreStartupInstaller(
284       ReinstalledExtensionId(),
285       profile_,
286       false,
287       extensions::WebstoreStandaloneInstaller::Callback());
288   installer_->BeginInstall();
289 }
290
291 void HotwordService::OnExtensionInstalled(
292     content::BrowserContext* browser_context,
293     const extensions::Extension* extension,
294     bool is_update) {
295
296   if ((extension->id() != extension_misc::kHotwordExtensionId &&
297        extension->id() != extension_misc::kHotwordSharedModuleId) ||
298        profile_ != Profile::FromBrowserContext(browser_context))
299     return;
300
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();
305
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();
313   else
314     reinstall_pending_ = false;
315
316   // Now that the extension is installed, if the user has not selected
317   // the preference on, make sure it is turned off.
318   //
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_));
327   }
328 }
329
330 bool HotwordService::MaybeReinstallHotwordExtension() {
331   CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
332
333   ExtensionService* extension_service = GetExtensionService(profile_);
334   if (!extension_service)
335     return false;
336
337   const extensions::Extension* extension = extension_service->GetExtensionById(
338       ReinstalledExtensionId(), true);
339   if (!extension)
340     return false;
341
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()))
347     return false;
348
349   // If there is already a pending request from HotwordService, don't try
350   // to uninstall either.
351   if (reinstall_pending_)
352     return false;
353
354   // Check if the current locale matches the previous. If they don't match,
355   // uninstall the extension.
356   if (!ShouldReinstallHotwordExtension())
357     return false;
358
359   // Ensure the call to OnExtensionUninstalled was triggered by a language
360   // change so it's okay to reinstall.
361   reinstall_pending_ = true;
362
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,
367                                      false);
368   }
369
370   return UninstallHotwordExtension(extension_service);
371 }
372
373 bool HotwordService::UninstallHotwordExtension(
374     ExtensionService* extension_service) {
375   base::string16 error;
376   std::string extension_id = ReinstalledExtensionId();
377   if (!extension_service->UninstallExtension(
378           extension_id,
379           extensions::UNINSTALL_REASON_INTERNAL_MANAGEMENT,
380           base::Bind(&base::DoNothing),
381           &error)) {
382     LOG(WARNING) << "Cannot uninstall extension with id "
383                  << extension_id
384                  << ": " << error;
385     reinstall_pending_ = false;
386     return false;
387   }
388   return true;
389 }
390
391 bool HotwordService::IsServiceAvailable() {
392   error_message_ = 0;
393
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);
402   if (!extension)
403     error_message_ = IDS_HOTWORD_GENERIC_ERROR_MESSAGE;
404
405   RecordExtensionAvailabilityMetrics(service, extension);
406   RecordLoggingMetrics(profile_);
407
408   // Determine if NaCl is available.
409   bool nacl_enabled = false;
410   base::FilePath path;
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);
416   }
417   if (!nacl_enabled)
418     error_message_ = IDS_HOTWORD_NACL_DISABLED_ERROR_MESSAGE;
419
420   RecordErrorMetrics(error_message_);
421
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;
436   }
437
438   return (error_message_ == 0) && IsHotwordAllowed();
439 }
440
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_);
447 }
448
449 bool HotwordService::IsOptedIntoAudioLogging() {
450   // Do not opt the user in if the preference has not been set.
451   return
452       profile_->GetPrefs()->HasPrefPath(prefs::kHotwordAudioLoggingEnabled) &&
453       profile_->GetPrefs()->GetBoolean(prefs::kHotwordAudioLoggingEnabled);
454 }
455
456 bool HotwordService::IsAlwaysOnEnabled() {
457   return
458       profile_->GetPrefs()->HasPrefPath(prefs::kHotwordAlwaysOnSearchEnabled) &&
459       profile_->GetPrefs()->GetBoolean(prefs::kHotwordAlwaysOnSearchEnabled);
460 }
461
462 void HotwordService::EnableHotwordExtension(
463     ExtensionService* extension_service) {
464   if (extension_service && !IsExperimentalHotwordingEnabled())
465     extension_service->EnableExtension(extension_misc::kHotwordExtensionId);
466 }
467
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);
474   }
475 }
476
477 void HotwordService::LaunchHotwordAudioVerificationApp(
478     const LaunchMode& launch_mode) {
479   hotword_audio_verification_launch_mode_ = launch_mode;
480
481   ExtensionService* extension_service = GetExtensionService(profile_);
482   if (!extension_service)
483     return;
484   const extensions::Extension* extension = extension_service->GetExtensionById(
485       extension_misc::kHotwordAudioVerificationAppId, true);
486   if (!extension)
487     return;
488
489   OpenApplication(AppLaunchParams(
490       profile_, extension, extensions::LAUNCH_CONTAINER_WINDOW, NEW_WINDOW));
491 }
492
493 HotwordService::LaunchMode
494 HotwordService::GetHotwordAudioVerificationLaunchMode() {
495   return hotword_audio_verification_launch_mode_;
496 }
497
498 void HotwordService::StartTraining() {
499   training_ = true;
500
501   if (!IsServiceAvailable())
502     return;
503
504   HotwordPrivateEventService* event_service =
505       BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
506   if (event_service)
507     event_service->OnEnabledChanged(hotword_internal::kHotwordTrainingEnabled);
508 }
509
510 void HotwordService::FinalizeSpeakerModel() {
511   if (!IsServiceAvailable())
512     return;
513
514   HotwordPrivateEventService* event_service =
515       BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
516   if (event_service)
517     event_service->OnFinalizeSpeakerModel();
518 }
519
520 void HotwordService::StopTraining() {
521   training_ = false;
522
523   if (!IsServiceAvailable())
524     return;
525
526   HotwordPrivateEventService* event_service =
527       BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
528   if (event_service)
529     event_service->OnEnabledChanged(hotword_internal::kHotwordTrainingEnabled);
530 }
531
532 void HotwordService::NotifyHotwordTriggered() {
533   if (!IsServiceAvailable())
534     return;
535
536   HotwordPrivateEventService* event_service =
537       BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
538   if (event_service)
539     event_service->OnHotwordTriggered();
540 }
541
542 bool HotwordService::IsTraining() {
543   return training_;
544 }
545
546 void HotwordService::OnHotwordSearchEnabledChanged(
547     const std::string& pref_name) {
548   DCHECK_EQ(pref_name, std::string(prefs::kHotwordSearchEnabled));
549
550   ExtensionService* extension_service = GetExtensionService(profile_);
551   if (profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled))
552     EnableHotwordExtension(extension_service);
553   else
554     DisableHotwordExtension(extension_service);
555 }
556
557 void HotwordService::RequestHotwordSession(HotwordClient* client) {
558   if (!IsServiceAvailable() || (client_ && client_ != client))
559     return;
560
561   client_ = client;
562
563   HotwordPrivateEventService* event_service =
564       BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
565   if (event_service)
566     event_service->OnHotwordSessionRequested();
567 }
568
569 void HotwordService::StopHotwordSession(HotwordClient* client) {
570   if (!IsServiceAvailable())
571     return;
572
573   // Do nothing if there's no client.
574   if (!client_)
575     return;
576   DCHECK(client_ == client);
577
578   client_ = NULL;
579   HotwordPrivateEventService* event_service =
580       BrowserContextKeyedAPIFactory<HotwordPrivateEventService>::Get(profile_);
581   if (event_service)
582     event_service->OnHotwordSessionStopped();
583 }
584
585 void HotwordService::SetPreviousLanguagePref() {
586   profile_->GetPrefs()->SetString(prefs::kHotwordPreviousLanguage,
587                                   GetCurrentLocale(profile_));
588 }
589
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))
594     return false;
595
596   std::string previous_locale =
597       profile_->GetPrefs()->GetString(prefs::kHotwordPreviousLanguage);
598   std::string locale = GetCurrentLocale(profile_);
599
600   // If it's a new locale, then the old extension should be uninstalled.
601   return locale != previous_locale &&
602       HotwordService::DoesHotwordSupportLanguage(profile_);
603 }