Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / app_list / start_page_service.cc
index 6c9d35f..09c66ca 100644 (file)
 
 #include <string>
 
+#include "base/bind.h"
 #include "base/command_line.h"
 #include "base/memory/singleton.h"
+#include "base/metrics/user_metrics.h"
+#include "base/prefs/pref_service.h"
+#include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
-#include "chrome/browser/extensions/extension_system_factory.h"
-#include "chrome/browser/extensions/install_tracker_factory.h"
+#include "chrome/browser/media/media_stream_infobar_delegate.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/search/hotword_service.h"
+#include "chrome/browser/search/hotword_service_factory.h"
 #include "chrome/browser/ui/app_list/recommended_apps.h"
+#include "chrome/browser/ui/app_list/speech_recognizer.h"
+#include "chrome/browser/ui/app_list/start_page_observer.h"
+#include "chrome/browser/ui/app_list/start_page_service_factory.h"
 #include "chrome/common/chrome_switches.h"
-#include "chrome/common/extensions/extension.h"
+#include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
-#include "components/browser_context_keyed_service/browser_context_dependency_manager.h"
-#include "components/browser_context_keyed_service/browser_context_keyed_service_factory.h"
+#include "content/public/browser/notification_details.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
 #include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_source.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_delegate.h"
+#include "extensions/browser/extension_system_provider.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/common/extension.h"
+#include "ui/app_list/app_list_switches.h"
 
-namespace app_list {
-
-class StartPageService::Factory : public BrowserContextKeyedServiceFactory {
- public:
-  static StartPageService* GetForProfile(Profile* profile) {
-    if (!CommandLine::ForCurrentProcess()->HasSwitch(
-            switches::kShowAppListStartPage)) {
-      return NULL;
-    }
-
-    return static_cast<StartPageService*>(
-        GetInstance()->GetServiceForBrowserContext(profile, true));
-  }
+using base::RecordAction;
+using base::UserMetricsAction;
 
-  static Factory* GetInstance() {
-    return Singleton<Factory>::get();
-  }
-
- private:
-  friend struct DefaultSingletonTraits<Factory>;
-
-  Factory()
-      : BrowserContextKeyedServiceFactory(
-            "AppListStartPageService",
-            BrowserContextDependencyManager::GetInstance()) {
-    DependsOn(extensions::ExtensionSystemFactory::GetInstance());
-    DependsOn(extensions::InstallTrackerFactory::GetInstance());
-  }
+namespace app_list {
 
-  virtual ~Factory() {}
+namespace {
 
-  // BrowserContextKeyedServiceFactory overrides:
-  virtual BrowserContextKeyedService* BuildServiceInstanceFor(
-      content::BrowserContext* context) const OVERRIDE {
-     Profile* profile = static_cast<Profile*>(context);
-     return new StartPageService(profile);
-  }
+bool InSpeechRecognition(SpeechRecognitionState state) {
+  return state == SPEECH_RECOGNITION_RECOGNIZING ||
+      state == SPEECH_RECOGNITION_IN_SPEECH;
+}
 
-  DISALLOW_COPY_AND_ASSIGN(Factory);
-};
+}
 
-class StartPageService::ExitObserver : public content::NotificationObserver {
+class StartPageService::ProfileDestroyObserver
+    : public content::NotificationObserver {
  public:
-  explicit ExitObserver(StartPageService* service) : service_(service) {
+  explicit ProfileDestroyObserver(StartPageService* service)
+      : service_(service) {
     registrar_.Add(this,
-                   chrome::NOTIFICATION_APP_TERMINATING,
-                   content::NotificationService::AllSources());
+                   chrome::NOTIFICATION_PROFILE_DESTROYED,
+                   content::Source<Profile>(service_->profile()));
   }
-  virtual ~ExitObserver() {}
+  ~ProfileDestroyObserver() override {}
 
  private:
   // content::NotificationObserver
-  virtual void Observe(int type,
-                       const content::NotificationSource& source,
-                       const content::NotificationDetails& details) OVERRIDE {
-    DCHECK_EQ(chrome::NOTIFICATION_APP_TERMINATING, type);
+  void Observe(int type,
+               const content::NotificationSource& source,
+               const content::NotificationDetails& details) override {
+    DCHECK_EQ(chrome::NOTIFICATION_PROFILE_DESTROYED, type);
+    DCHECK_EQ(service_->profile(), content::Source<Profile>(source).ptr());
     service_->Shutdown();
   }
 
   StartPageService* service_;  // Owner of this class.
   content::NotificationRegistrar registrar_;
 
-  DISALLOW_COPY_AND_ASSIGN(ExitObserver);
+  DISALLOW_COPY_AND_ASSIGN(ProfileDestroyObserver);
+};
+
+class StartPageService::StartPageWebContentsDelegate
+    : public content::WebContentsDelegate {
+ public:
+  StartPageWebContentsDelegate() {}
+  ~StartPageWebContentsDelegate() override {}
+
+  void RequestMediaAccessPermission(
+      content::WebContents* web_contents,
+      const content::MediaStreamRequest& request,
+      const content::MediaResponseCallback& callback) override {
+    if (MediaStreamInfoBarDelegate::Create(web_contents, request, callback))
+      NOTREACHED() << "Media stream not allowed for WebUI";
+  }
+
+  bool CheckMediaAccessPermission(content::WebContents* web_contents,
+                                  const GURL& security_origin,
+                                  content::MediaStreamType type) override {
+    return MediaCaptureDevicesDispatcher::GetInstance()
+        ->CheckMediaAccessPermission(web_contents, security_origin, type);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(StartPageWebContentsDelegate);
 };
 
 // static
 StartPageService* StartPageService::Get(Profile* profile) {
-  return Factory::GetForProfile(profile);
+  return StartPageServiceFactory::GetForProfile(profile);
 }
 
 StartPageService::StartPageService(Profile* profile)
     : profile_(profile),
-      exit_observer_(new ExitObserver(this)),
-      recommended_apps_(new RecommendedApps(profile)) {
+      profile_destroy_observer_(new ProfileDestroyObserver(this)),
+      recommended_apps_(new RecommendedApps(profile)),
+      state_(app_list::SPEECH_RECOGNITION_OFF),
+      speech_button_toggled_manually_(false),
+      speech_result_obtained_(false),
+      webui_finished_loading_(false),
+      weak_factory_(this) {
+  // If experimental hotwording is enabled, then we're always "ready".
+  // Transitioning into the "hotword recognizing" state is handled by the
+  // hotword extension.
+  if (HotwordService::IsExperimentalHotwordingEnabled())
+    state_ = app_list::SPEECH_RECOGNITION_READY;
+
+  if (app_list::switches::IsExperimentalAppListEnabled())
+    LoadContents();
+}
+
+StartPageService::~StartPageService() {}
+
+void StartPageService::AddObserver(StartPageObserver* observer) {
+  observers_.AddObserver(observer);
+}
+
+void StartPageService::RemoveObserver(StartPageObserver* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+void StartPageService::AppListShown() {
+  if (!contents_) {
+    LoadContents();
+  } else if (contents_->GetWebUI() &&
+             !HotwordService::IsExperimentalHotwordingEnabled()) {
+    // If experimental hotwording is enabled, don't call onAppListShown.
+    // onAppListShown() initializes the web speech API, which is not used with
+    // experimental hotwording.
+    contents_->GetWebUI()->CallJavascriptFunction(
+        "appList.startPage.onAppListShown",
+        base::FundamentalValue(HotwordEnabled()));
+  }
+}
+
+void StartPageService::AppListHidden() {
+  if (contents_->GetWebUI()) {
+    contents_->GetWebUI()->CallJavascriptFunction(
+        "appList.startPage.onAppListHidden");
+  }
+  if (!app_list::switches::IsExperimentalAppListEnabled())
+    UnloadContents();
+
+  if (HotwordService::IsExperimentalHotwordingEnabled() &&
+      speech_recognizer_) {
+    speech_recognizer_->Stop();
+  }
+}
+
+void StartPageService::ToggleSpeechRecognition() {
+  DCHECK(contents_);
+  speech_button_toggled_manually_ = true;
+  if (!contents_->GetWebUI())
+    return;
+
+  if (!webui_finished_loading_) {
+    pending_webui_callbacks_.push_back(
+        base::Bind(&StartPageService::ToggleSpeechRecognition,
+                   base::Unretained(this)));
+    return;
+  }
+
+  if (HotwordService::IsExperimentalHotwordingEnabled()) {
+    if (!speech_recognizer_) {
+      std::string profile_locale;
+#if defined(OS_CHROMEOS)
+      profile_locale = profile_->GetPrefs()->GetString(
+          prefs::kApplicationLocale);
+#endif
+      if (profile_locale.empty())
+        profile_locale = g_browser_process->GetApplicationLocale();
+
+      speech_recognizer_.reset(
+          new SpeechRecognizer(weak_factory_.GetWeakPtr(),
+                               profile_->GetRequestContext(),
+                               profile_locale));
+    }
+
+    speech_recognizer_->Start();
+    return;
+  }
+
+  contents_->GetWebUI()->CallJavascriptFunction(
+      "appList.startPage.toggleSpeechRecognition");
+}
+
+bool StartPageService::HotwordEnabled() {
+  if (HotwordService::IsExperimentalHotwordingEnabled()) {
+    auto prefs = profile_->GetPrefs();
+    return HotwordServiceFactory::IsServiceAvailable(profile_) &&
+        (prefs->GetBoolean(prefs::kHotwordSearchEnabled) ||
+         prefs->GetBoolean(prefs::kHotwordAlwaysOnSearchEnabled));
+  }
+#if defined(OS_CHROMEOS)
+  return HotwordServiceFactory::IsServiceAvailable(profile_) &&
+      profile_->GetPrefs()->GetBoolean(prefs::kHotwordSearchEnabled);
+#else
+  return false;
+#endif
+}
+
+content::WebContents* StartPageService::GetStartPageContents() {
+  return app_list::switches::IsExperimentalAppListEnabled() ? contents_.get()
+                                                            : NULL;
+}
+
+content::WebContents* StartPageService::GetSpeechRecognitionContents() {
+  if (app_list::switches::IsVoiceSearchEnabled()) {
+    if (!contents_)
+      LoadContents();
+    return contents_.get();
+  }
+  return NULL;
+}
+
+void StartPageService::OnSpeechResult(
+    const base::string16& query, bool is_final) {
+  if (is_final) {
+    speech_result_obtained_ = true;
+    RecordAction(UserMetricsAction("AppList_SearchedBySpeech"));
+  }
+  FOR_EACH_OBSERVER(StartPageObserver,
+                    observers_,
+                    OnSpeechResult(query, is_final));
+}
+
+void StartPageService::OnSpeechSoundLevelChanged(int16_t level) {
+  FOR_EACH_OBSERVER(StartPageObserver,
+                    observers_,
+                    OnSpeechSoundLevelChanged(level));
+}
+
+void StartPageService::OnSpeechRecognitionStateChanged(
+    SpeechRecognitionState new_state) {
+
+  if (HotwordService::IsExperimentalHotwordingEnabled() &&
+      new_state == SPEECH_RECOGNITION_READY &&
+      speech_recognizer_) {
+    speech_recognizer_->Stop();
+  }
+
+  if (!InSpeechRecognition(state_) && InSpeechRecognition(new_state)) {
+    if (!speech_button_toggled_manually_ &&
+        state_ == SPEECH_RECOGNITION_HOTWORD_LISTENING) {
+      RecordAction(UserMetricsAction("AppList_HotwordRecognized"));
+    } else {
+      RecordAction(UserMetricsAction("AppList_VoiceSearchStartedManually"));
+    }
+  } else if (InSpeechRecognition(state_) && !InSpeechRecognition(new_state) &&
+             !speech_result_obtained_) {
+    RecordAction(UserMetricsAction("AppList_VoiceSearchCanceled"));
+  }
+  speech_button_toggled_manually_ = false;
+  speech_result_obtained_ = false;
+  state_ = new_state;
+  FOR_EACH_OBSERVER(StartPageObserver,
+                    observers_,
+                    OnSpeechRecognitionStateChanged(new_state));
+}
+
+content::WebContents* StartPageService::GetSpeechContents() {
+  return GetSpeechRecognitionContents();
+}
+
+void StartPageService::Shutdown() {
+  UnloadContents();
+}
+
+void StartPageService::WebUILoaded() {
+  // There's a race condition between the WebUI loading, and calling its JS
+  // functions. Specifically, calling LoadContents() doesn't mean that the page
+  // has loaded, but several code paths make this assumption. This function
+  // allows us to defer calling JS functions until after the page has finished
+  // loading.
+  webui_finished_loading_ = true;
+  for (const auto& cb : pending_webui_callbacks_)
+    cb.Run();
+  pending_webui_callbacks_.clear();
+}
+
+void StartPageService::LoadContents() {
   contents_.reset(content::WebContents::Create(
       content::WebContents::CreateParams(profile_)));
+  contents_delegate_.reset(new StartPageWebContentsDelegate());
+  contents_->SetDelegate(contents_delegate_.get());
 
   GURL url(chrome::kChromeUIAppListStartPageURL);
   CommandLine* command_line = CommandLine::ForCurrentProcess();
-  if (command_line->HasSwitch(switches::kAppListStartPageURL)) {
+  if (command_line->HasSwitch(::switches::kAppListStartPageURL)) {
     url = GURL(
-        command_line->GetSwitchValueASCII(switches::kAppListStartPageURL));
+        command_line->GetSwitchValueASCII(::switches::kAppListStartPageURL));
   }
 
   contents_->GetController().LoadURL(
       url,
       content::Referrer(),
-      content::PAGE_TRANSITION_AUTO_TOPLEVEL,
+      ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
       std::string());
 }
 
-StartPageService::~StartPageService() {}
-
-void StartPageService::Shutdown() {
+void StartPageService::UnloadContents() {
   contents_.reset();
+  webui_finished_loading_ = false;
 }
 
 }  // namespace app_list