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/ui/webui/app_list/start_page_handler.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/memory/scoped_ptr.h"
11#include "base/values.h"
12#include "base/version.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/browser/search/hotword_service.h"
15#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
16#include "chrome/browser/ui/app_list/app_list_service.h"
17#include "chrome/browser/ui/app_list/recommended_apps.h"
18#include "chrome/browser/ui/app_list/start_page_service.h"
19#include "chrome/browser/ui/host_desktop.h"
20#include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
21#include "chrome/common/pref_names.h"
22#include "components/omaha_query_params/omaha_query_params.h"
23#include "content/public/browser/web_ui.h"
24#include "extensions/browser/extension_registry.h"
25#include "extensions/browser/extension_system.h"
26#include "extensions/common/constants.h"
27#include "extensions/common/extension.h"
28#include "extensions/common/extension_icon_set.h"
29#include "ui/app_list/app_list_switches.h"
30#include "ui/app_list/speech_ui_model_observer.h"
31#include "ui/events/event_constants.h"
32
33namespace app_list {
34
35namespace {
36
37#if defined(OS_CHROMEOS)
38const char kOldHotwordExtensionVersionString[] = "0.1.1.5023";
39#endif
40
41scoped_ptr<base::DictionaryValue> CreateAppInfo(
42    const extensions::Extension* app) {
43  scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
44  dict->SetString("appId", app->id());
45  dict->SetString("textTitle", app->short_name());
46  dict->SetString("title", app->name());
47
48  const bool grayscale = false;
49  bool icon_exists = true;
50  GURL icon_url = extensions::ExtensionIconSource::GetIconURL(
51      app,
52      extension_misc::EXTENSION_ICON_MEDIUM,
53      ExtensionIconSet::MATCH_BIGGER,
54      grayscale,
55      &icon_exists);
56  dict->SetString("iconUrl", icon_url.spec());
57
58  return dict.Pass();
59}
60
61}  // namespace
62
63StartPageHandler::StartPageHandler()
64    : recommended_apps_(NULL),
65      extension_registry_observer_(this) {
66}
67
68StartPageHandler::~StartPageHandler() {
69  if (recommended_apps_)
70    recommended_apps_->RemoveObserver(this);
71}
72
73void StartPageHandler::RegisterMessages() {
74  web_ui()->RegisterMessageCallback(
75      "initialize",
76      base::Bind(&StartPageHandler::HandleInitialize, base::Unretained(this)));
77  web_ui()->RegisterMessageCallback(
78      "launchApp",
79      base::Bind(&StartPageHandler::HandleLaunchApp, base::Unretained(this)));
80  web_ui()->RegisterMessageCallback(
81      "speechResult",
82      base::Bind(&StartPageHandler::HandleSpeechResult,
83                 base::Unretained(this)));
84  web_ui()->RegisterMessageCallback(
85      "speechSoundLevel",
86      base::Bind(&StartPageHandler::HandleSpeechSoundLevel,
87                 base::Unretained(this)));
88  web_ui()->RegisterMessageCallback(
89      "setSpeechRecognitionState",
90      base::Bind(&StartPageHandler::HandleSpeechRecognition,
91                 base::Unretained(this)));
92}
93
94void StartPageHandler::OnExtensionLoaded(
95    content::BrowserContext* browser_context,
96    const extensions::Extension* extension) {
97#if defined(OS_CHROMEOS)
98  DCHECK_EQ(Profile::FromWebUI(web_ui()),
99            Profile::FromBrowserContext(browser_context));
100  if (extension->id() == extension_misc::kHotwordExtensionId)
101    OnHotwordEnabledChanged();
102#endif
103}
104
105void StartPageHandler::OnExtensionUnloaded(
106    content::BrowserContext* browser_context,
107    const extensions::Extension* extension,
108    extensions::UnloadedExtensionInfo::Reason reason) {
109#if defined(OS_CHROMEOS)
110  DCHECK_EQ(Profile::FromWebUI(web_ui()),
111            Profile::FromBrowserContext(browser_context));
112  if (extension->id() == extension_misc::kHotwordExtensionId)
113    OnHotwordEnabledChanged();
114#endif
115}
116
117void StartPageHandler::OnRecommendedAppsChanged() {
118  SendRecommendedApps();
119}
120
121void StartPageHandler::SendRecommendedApps() {
122  const RecommendedApps::Apps& recommends = recommended_apps_->apps();
123
124  base::ListValue recommended_list;
125  for (size_t i = 0; i < recommends.size(); ++i) {
126    recommended_list.Append(CreateAppInfo(recommends[i].get()).release());
127  }
128
129  web_ui()->CallJavascriptFunction("appList.startPage.setRecommendedApps",
130                                   recommended_list);
131}
132
133#if defined(OS_CHROMEOS)
134void StartPageHandler::OnHotwordEnabledChanged() {
135  // If the hotword extension is new enough, we should use the new
136  // hotwordPrivate API to provide the feature.
137  // TODO(mukai): remove this after everything gets stable.
138  Profile* profile = Profile::FromWebUI(web_ui());
139
140  extensions::ExtensionRegistry* registry =
141      extensions::ExtensionRegistry::Get(profile);
142  const extensions::Extension* hotword_extension =
143      registry->GetExtensionById(extension_misc::kHotwordExtensionId,
144                                 extensions::ExtensionRegistry::ENABLED);
145  if (hotword_extension &&
146      hotword_extension->version()->CompareTo(
147          base::Version(kOldHotwordExtensionVersionString)) <= 0 &&
148      !HotwordService::IsExperimentalHotwordingEnabled()) {
149    StartPageService* service = StartPageService::Get(profile);
150    web_ui()->CallJavascriptFunction(
151        "appList.startPage.setHotwordEnabled",
152        base::FundamentalValue(service->HotwordEnabled()));
153  }
154}
155#endif
156
157void StartPageHandler::HandleInitialize(const base::ListValue* args) {
158  Profile* profile = Profile::FromWebUI(web_ui());
159  StartPageService* service = StartPageService::Get(profile);
160  if (!service)
161    return;
162
163  recommended_apps_ = service->recommended_apps();
164  recommended_apps_->AddObserver(this);
165
166  SendRecommendedApps();
167
168#if defined(OS_CHROMEOS)
169  if (app_list::switches::IsVoiceSearchEnabled() &&
170      HotwordService::DoesHotwordSupportLanguage(profile)) {
171    OnHotwordEnabledChanged();
172    pref_change_registrar_.Init(profile->GetPrefs());
173    pref_change_registrar_.Add(
174        prefs::kHotwordSearchEnabled,
175        base::Bind(&StartPageHandler::OnHotwordEnabledChanged,
176                   base::Unretained(this)));
177
178    extension_registry_observer_.Add(
179        extensions::ExtensionRegistry::Get(profile));
180  }
181
182  extensions::ExtensionRegistry* registry =
183      extensions::ExtensionRegistry::Get(profile);
184  const extensions::Extension* hotword_extension =
185      registry->GetExtensionById(extension_misc::kHotwordExtensionId,
186                                 extensions::ExtensionRegistry::ENABLED);
187  if (hotword_extension &&
188      hotword_extension->version()->CompareTo(
189          base::Version(kOldHotwordExtensionVersionString)) <= 0) {
190    web_ui()->CallJavascriptFunction(
191        "appList.startPage.setNaclArch",
192        base::StringValue(omaha_query_params::OmahaQueryParams::GetNaclArch()));
193  }
194#endif
195
196  if (!app_list::switches::IsExperimentalAppListEnabled()) {
197    // If experimental hotwording is enabled, don't enable hotwording in the
198    // start page, since the hotword extension is taking care of this.
199    bool hotword_enabled = service->HotwordEnabled() &&
200        !HotwordService::IsExperimentalHotwordingEnabled();
201    web_ui()->CallJavascriptFunction(
202        "appList.startPage.onAppListShown",
203        base::FundamentalValue(hotword_enabled));
204  }
205}
206
207void StartPageHandler::HandleLaunchApp(const base::ListValue* args) {
208  std::string app_id;
209  CHECK(args->GetString(0, &app_id));
210
211  Profile* profile = Profile::FromWebUI(web_ui());
212  const extensions::Extension* app =
213      extensions::ExtensionRegistry::Get(profile)
214          ->GetExtensionById(app_id, extensions::ExtensionRegistry::EVERYTHING);
215  if (!app) {
216    NOTREACHED();
217    return;
218  }
219
220  AppListControllerDelegate* controller = AppListService::Get(
221      chrome::GetHostDesktopTypeForNativeView(
222          web_ui()->GetWebContents()->GetNativeView()))->
223              GetControllerDelegate();
224  controller->ActivateApp(profile,
225                          app,
226                          AppListControllerDelegate::LAUNCH_FROM_APP_LIST,
227                          ui::EF_NONE);
228}
229
230void StartPageHandler::HandleSpeechResult(const base::ListValue* args) {
231  base::string16 query;
232  bool is_final = false;
233  CHECK(args->GetString(0, &query));
234  CHECK(args->GetBoolean(1, &is_final));
235
236  StartPageService::Get(Profile::FromWebUI(web_ui()))->OnSpeechResult(
237      query, is_final);
238}
239
240void StartPageHandler::HandleSpeechSoundLevel(const base::ListValue* args) {
241  double level;
242  CHECK(args->GetDouble(0, &level));
243
244  StartPageService* service =
245      StartPageService::Get(Profile::FromWebUI(web_ui()));
246  if (service)
247    service->OnSpeechSoundLevelChanged(static_cast<int16>(level));
248}
249
250void StartPageHandler::HandleSpeechRecognition(const base::ListValue* args) {
251  std::string state_string;
252  CHECK(args->GetString(0, &state_string));
253
254  SpeechRecognitionState new_state = SPEECH_RECOGNITION_OFF;
255  if (state_string == "READY")
256    new_state = SPEECH_RECOGNITION_READY;
257  else if (state_string == "HOTWORD_RECOGNIZING")
258    new_state = SPEECH_RECOGNITION_HOTWORD_LISTENING;
259  else if (state_string == "RECOGNIZING")
260    new_state = SPEECH_RECOGNITION_RECOGNIZING;
261  else if (state_string == "IN_SPEECH")
262    new_state = SPEECH_RECOGNITION_IN_SPEECH;
263  else if (state_string == "STOPPING")
264    new_state = SPEECH_RECOGNITION_STOPPING;
265  else if (state_string == "NETWORK_ERROR")
266    new_state = SPEECH_RECOGNITION_NETWORK_ERROR;
267
268  StartPageService* service =
269      StartPageService::Get(Profile::FromWebUI(web_ui()));
270  if (service)
271    service->OnSpeechRecognitionStateChanged(new_state);
272}
273
274}  // namespace app_list
275