hotword_service.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
18bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)// Copyright 2013 The Chromium Authors. All rights reserved.
28bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
38bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)// found in the LICENSE file.
48bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
58bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/browser/search/hotword_service.h"
68bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
75d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "base/i18n/case_conversion.h"
85d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "base/metrics/field_trial.h"
95d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "base/metrics/histogram.h"
105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "base/prefs/pref_service.h"
115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "chrome/browser/browser_process.h"
125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "chrome/browser/extensions/extension_service.h"
138bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)#include "chrome/browser/profiles/profile.h"
145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "chrome/common/extensions/extension_constants.h"
155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "chrome/common/pref_names.h"
16a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)#include "content/public/browser/browser_thread.h"
175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "extensions/browser/extension_system.h"
185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "extensions/common/extension.h"
195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "ui/base/l10n/l10n_util.h"
208bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)namespace {
225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)const int kMaxTimesToShowOptInPopup = 10;
235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// Enum describing the state of the hotword preference.
255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// This is used for UMA stats -- do not reorder or delete items; only add to
265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// the end.
275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)enum HotwordEnabled {
285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  UNSET = 0,  // The hotword preference has not been set.
295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  ENABLED,    // The hotword preference is enabled.
305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  DISABLED,   // The hotword preference is disabled.
315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  NUM_HOTWORD_ENABLED_METRICS
325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)};
335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// Enum describing the availability state of the hotword extension.
355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// This is used for UMA stats -- do not reorder or delete items; only add to
365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// the end.
375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)enum HotwordExtensionAvailability {
385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  UNAVAILABLE = 0,
395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  AVAILABLE,
405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  PENDING_DOWNLOAD,
415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  DISABLED_EXTENSION,
425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  NUM_HOTWORD_EXTENSION_AVAILABILITY_METRICS
435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)};
445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void RecordAvailabilityMetrics(
465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    ExtensionService* service,
475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    const extensions::Extension* extension) {
485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  HotwordExtensionAvailability availability_state = UNAVAILABLE;
495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (extension) {
505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    availability_state = AVAILABLE;
515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  } else if (service->pending_extension_manager() &&
525d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)             service->pending_extension_manager()->IsIdPending(
535d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                 extension_misc::kHotwordExtensionId)) {
545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    availability_state = PENDING_DOWNLOAD;
555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  } else if (!service->IsExtensionEnabled(
565d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      extension_misc::kHotwordExtensionId)) {
575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    availability_state = DISABLED_EXTENSION;
585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  UMA_HISTOGRAM_ENUMERATION("Hotword.HotwordExtensionAvailability",
605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                            availability_state,
615d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                            NUM_HOTWORD_EXTENSION_AVAILABILITY_METRICS);
625d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
635d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
645d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}  // namespace
655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)namespace hotword_internal {
675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// Constants for the hotword field trial.
685d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)const char kHotwordFieldTrialName[] = "VoiceTrigger";
695d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)const char kHotwordFieldTrialDisabledGroupName[] = "Disabled";
705d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}  // namespace hotword_internal
715d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// static
735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)bool HotwordService::DoesHotwordSupportLanguage(Profile* profile) {
745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  std::string locale =
755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#if defined(OS_CHROMEOS)
765d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      // On ChromeOS locale is per-profile.
775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      profile->GetPrefs()->GetString(prefs::kApplicationLocale);
785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#else
795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      g_browser_process->GetApplicationLocale();
805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#endif
815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  // Only available for English now.
825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  std::string normalized_locale = l10n_util::NormalizeLocale(locale);
835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return normalized_locale == "en" || normalized_locale == "en_us" ||
845d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      normalized_locale =="en_US";
855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
868bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
878bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)HotwordService::HotwordService(Profile* profile)
888bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)    : profile_(profile) {
895d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  // This will be called during profile initialization which is a good time
905d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  // to check the user's hotword state.
915d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  HotwordEnabled enabled_state = UNSET;
925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (profile_->GetPrefs()->HasPrefPath(prefs::kHotwordSearchEnabled)) {
935d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    if (profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled))
945d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      enabled_state = ENABLED;
955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    else
965d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      enabled_state = DISABLED;
975d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
985d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  UMA_HISTOGRAM_ENUMERATION("Hotword.Enabled", enabled_state,
995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                            NUM_HOTWORD_ENABLED_METRICS);
1008bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)}
1018bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)
1028bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)HotwordService::~HotwordService() {
1038bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)}
1045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)bool HotwordService::ShouldShowOptInPopup() {
1065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (profile_->IsOffTheRecord())
1075d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return false;
1085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  // Profile is not off the record.
1105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  if (profile_->GetPrefs()->HasPrefPath(prefs::kHotwordSearchEnabled))
1115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return false;  // Already opted in or opted out;
1125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  int number_shown = profile_->GetPrefs()->GetInteger(
1145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      prefs::kHotwordOptInPopupTimesShown);
1155d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return number_shown < MaxNumberTimesToShowOptInPopup();
1165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
1175d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)int HotwordService::MaxNumberTimesToShowOptInPopup() {
1195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return kMaxTimesToShowOptInPopup;
1205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
1215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void HotwordService::ShowOptInPopup() {
1235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  int number_shown = profile_->GetPrefs()->GetInteger(
1245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      prefs::kHotwordOptInPopupTimesShown);
1255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  profile_->GetPrefs()->SetInteger(prefs::kHotwordOptInPopupTimesShown,
1265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)                                   ++number_shown);
1275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  // TODO(rlp): actually show opt in popup when linked up to extension.
1285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
1295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)bool HotwordService::IsServiceAvailable() {
1315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  extensions::ExtensionSystem* system =
1325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      extensions::ExtensionSystem::Get(profile_);
1335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  ExtensionService* service = system->extension_service();
1345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  // Do not include disabled extension (false parameter) because if the
1355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  // extension is disabled, it's not available.
1365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  const extensions::Extension* extension =
1375d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      service->GetExtensionById(extension_misc::kHotwordExtensionId, false);
1385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  RecordAvailabilityMetrics(service, extension);
1405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return extension && IsHotwordAllowed();
1425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
1435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)bool HotwordService::IsHotwordAllowed() {
1455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  std::string group = base::FieldTrialList::FindFullName(
1465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      hotword_internal::kHotwordFieldTrialName);
1475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return !group.empty() &&
1485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      group != hotword_internal::kHotwordFieldTrialDisabledGroupName &&
1495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      DoesHotwordSupportLanguage(profile_);
1505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)}
151a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
152a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)bool HotwordService::RetryHotwordExtension() {
153a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
154a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
155a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  extensions::ExtensionSystem* extension_system =
156a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      extensions::ExtensionSystem::Get(profile_);
157a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  if (!extension_system || !extension_system->extension_service())
158a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    return false;
159a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  ExtensionService* extension_service = extension_system->extension_service();
160a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)
161a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  extension_service->ReloadExtension(extension_misc::kHotwordExtensionId);
162a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)  return true;
163a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)}
164