1// Copyright (c) 2012 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/ntp/app_launcher_handler.h"
6
7#include <vector>
8
9#include "apps/metrics_names.h"
10#include "base/auto_reset.h"
11#include "base/bind.h"
12#include "base/bind_helpers.h"
13#include "base/i18n/rtl.h"
14#include "base/metrics/field_trial.h"
15#include "base/metrics/histogram.h"
16#include "base/prefs/pref_service.h"
17#include "base/prefs/scoped_user_pref_update.h"
18#include "base/strings/utf_string_conversions.h"
19#include "base/values.h"
20#include "chrome/browser/browser_process.h"
21#include "chrome/browser/chrome_notification_types.h"
22#include "chrome/browser/extensions/crx_installer.h"
23#include "chrome/browser/extensions/extension_service.h"
24#include "chrome/browser/extensions/extension_ui_util.h"
25#include "chrome/browser/extensions/launch_util.h"
26#include "chrome/browser/favicon/favicon_service_factory.h"
27#include "chrome/browser/profiles/profile.h"
28#include "chrome/browser/ui/app_list/app_list_util.h"
29#include "chrome/browser/ui/browser_dialogs.h"
30#include "chrome/browser/ui/browser_finder.h"
31#include "chrome/browser/ui/browser_tabstrip.h"
32#include "chrome/browser/ui/browser_window.h"
33#include "chrome/browser/ui/extensions/application_launch.h"
34#include "chrome/browser/ui/extensions/extension_enable_flow.h"
35#include "chrome/browser/ui/tabs/tab_strip_model.h"
36#include "chrome/browser/ui/webui/extensions/extension_basic_info.h"
37#include "chrome/browser/ui/webui/extensions/extension_icon_source.h"
38#include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
39#include "chrome/browser/ui/webui/ntp/new_tab_ui.h"
40#include "chrome/common/extensions/extension_constants.h"
41#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
42#include "chrome/common/pref_names.h"
43#include "chrome/common/url_constants.h"
44#include "chrome/common/web_application_info.h"
45#include "components/favicon_base/favicon_types.h"
46#include "content/public/browser/notification_service.h"
47#include "content/public/browser/web_ui.h"
48#include "content/public/common/favicon_url.h"
49#include "extensions/browser/app_sorting.h"
50#include "extensions/browser/extension_prefs.h"
51#include "extensions/browser/extension_registry.h"
52#include "extensions/browser/extension_system.h"
53#include "extensions/browser/management_policy.h"
54#include "extensions/browser/pref_names.h"
55#include "extensions/common/constants.h"
56#include "extensions/common/extension.h"
57#include "extensions/common/extension_icon_set.h"
58#include "extensions/common/extension_set.h"
59#include "grit/browser_resources.h"
60#include "grit/generated_resources.h"
61#include "ui/base/l10n/l10n_util.h"
62#include "ui/base/webui/web_ui_util.h"
63#include "ui/gfx/favicon_size.h"
64#include "url/gurl.h"
65
66using content::WebContents;
67using extensions::AppSorting;
68using extensions::CrxInstaller;
69using extensions::Extension;
70using extensions::ExtensionPrefs;
71using extensions::ExtensionRegistry;
72using extensions::ExtensionSet;
73using extensions::UnloadedExtensionInfo;
74
75namespace {
76
77void RecordAppLauncherPromoHistogram(
78      apps::AppLauncherPromoHistogramValues value) {
79  DCHECK_LT(value, apps::APP_LAUNCHER_PROMO_MAX);
80  UMA_HISTOGRAM_ENUMERATION(
81      "Apps.AppLauncherPromo", value, apps::APP_LAUNCHER_PROMO_MAX);
82}
83
84// This is used to avoid a DCHECK due to an unhandled WebUI callback. The
85// JavaScript used to switch between pages sends "pageSelected" which is used
86// in the context of the NTP for recording metrics we don't need here.
87void NoOpCallback(const base::ListValue* args) {}
88
89}  // namespace
90
91AppLauncherHandler::AppInstallInfo::AppInstallInfo() {}
92
93AppLauncherHandler::AppInstallInfo::~AppInstallInfo() {}
94
95AppLauncherHandler::AppLauncherHandler(ExtensionService* extension_service)
96    : extension_service_(extension_service),
97      ignore_changes_(false),
98      attempted_bookmark_app_install_(false),
99      has_loaded_apps_(false) {
100  if (IsAppLauncherEnabled())
101    RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_ALREADY_INSTALLED);
102  else if (ShouldShowAppLauncherPromo())
103    RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_SHOWN);
104}
105
106AppLauncherHandler::~AppLauncherHandler() {}
107
108void AppLauncherHandler::CreateAppInfo(
109    const Extension* extension,
110    ExtensionService* service,
111    base::DictionaryValue* value) {
112  value->Clear();
113
114  // The Extension class 'helpfully' wraps bidi control characters that
115  // impede our ability to determine directionality.
116  base::string16 short_name = base::UTF8ToUTF16(extension->short_name());
117  base::i18n::UnadjustStringForLocaleDirection(&short_name);
118  NewTabUI::SetUrlTitleAndDirection(
119      value,
120      short_name,
121      extensions::AppLaunchInfo::GetFullLaunchURL(extension));
122
123  base::string16 name = base::UTF8ToUTF16(extension->name());
124  base::i18n::UnadjustStringForLocaleDirection(&name);
125  NewTabUI::SetFullNameAndDirection(name, value);
126
127  bool enabled =
128      service->IsExtensionEnabled(extension->id()) &&
129      !extensions::ExtensionRegistry::Get(service->GetBrowserContext())
130           ->GetExtensionById(extension->id(),
131                              extensions::ExtensionRegistry::TERMINATED);
132  extensions::GetExtensionBasicInfo(extension, enabled, value);
133
134  value->SetBoolean("mayDisable", extensions::ExtensionSystem::Get(
135      service->profile())->management_policy()->UserMayModifySettings(
136      extension, NULL));
137
138  bool icon_big_exists = true;
139  // Instead of setting grayscale here, we do it in apps_page.js.
140  GURL icon_big = extensions::ExtensionIconSource::GetIconURL(
141      extension,
142      extension_misc::EXTENSION_ICON_LARGE,
143      ExtensionIconSet::MATCH_BIGGER,
144      false,
145      &icon_big_exists);
146  value->SetString("icon_big", icon_big.spec());
147  value->SetBoolean("icon_big_exists", icon_big_exists);
148  bool icon_small_exists = true;
149  GURL icon_small = extensions::ExtensionIconSource::GetIconURL(
150      extension,
151      extension_misc::EXTENSION_ICON_BITTY,
152      ExtensionIconSet::MATCH_BIGGER,
153      false,
154      &icon_small_exists);
155  value->SetString("icon_small", icon_small.spec());
156  value->SetBoolean("icon_small_exists", icon_small_exists);
157  value->SetInteger("launch_container",
158                    extensions::AppLaunchInfo::GetLaunchContainer(extension));
159  ExtensionPrefs* prefs = ExtensionPrefs::Get(service->profile());
160  value->SetInteger("launch_type", extensions::GetLaunchType(prefs, extension));
161  value->SetBoolean("is_component",
162                    extension->location() == extensions::Manifest::COMPONENT);
163  value->SetBoolean("is_webstore",
164      extension->id() == extension_misc::kWebStoreAppId);
165
166  AppSorting* sorting = prefs->app_sorting();
167  syncer::StringOrdinal page_ordinal = sorting->GetPageOrdinal(extension->id());
168  if (!page_ordinal.IsValid()) {
169    // Make sure every app has a page ordinal (some predate the page ordinal).
170    // The webstore app should be on the first page.
171    page_ordinal = extension->id() == extension_misc::kWebStoreAppId ?
172        sorting->CreateFirstAppPageOrdinal() :
173        sorting->GetNaturalAppPageOrdinal();
174    sorting->SetPageOrdinal(extension->id(), page_ordinal);
175  }
176  value->SetInteger("page_index",
177      sorting->PageStringOrdinalAsInteger(page_ordinal));
178
179  syncer::StringOrdinal app_launch_ordinal =
180      sorting->GetAppLaunchOrdinal(extension->id());
181  if (!app_launch_ordinal.IsValid()) {
182    // Make sure every app has a launch ordinal (some predate the launch
183    // ordinal). The webstore's app launch ordinal is always set to the first
184    // position.
185    app_launch_ordinal = extension->id() == extension_misc::kWebStoreAppId ?
186        sorting->CreateFirstAppLaunchOrdinal(page_ordinal) :
187        sorting->CreateNextAppLaunchOrdinal(page_ordinal);
188    sorting->SetAppLaunchOrdinal(extension->id(), app_launch_ordinal);
189  }
190  value->SetString("app_launch_ordinal", app_launch_ordinal.ToInternalValue());
191}
192
193void AppLauncherHandler::RegisterMessages() {
194  registrar_.Add(this, chrome::NOTIFICATION_APP_INSTALLED_TO_NTP,
195      content::Source<WebContents>(web_ui()->GetWebContents()));
196
197  // Some tests don't have a local state.
198#if defined(ENABLE_APP_LIST)
199  if (g_browser_process->local_state()) {
200    local_state_pref_change_registrar_.Init(g_browser_process->local_state());
201    local_state_pref_change_registrar_.Add(
202        prefs::kShowAppLauncherPromo,
203        base::Bind(&AppLauncherHandler::OnLocalStatePreferenceChanged,
204                   base::Unretained(this)));
205  }
206#endif
207  web_ui()->RegisterMessageCallback("getApps",
208      base::Bind(&AppLauncherHandler::HandleGetApps,
209                 base::Unretained(this)));
210  web_ui()->RegisterMessageCallback("launchApp",
211      base::Bind(&AppLauncherHandler::HandleLaunchApp,
212                 base::Unretained(this)));
213  web_ui()->RegisterMessageCallback("setLaunchType",
214      base::Bind(&AppLauncherHandler::HandleSetLaunchType,
215                 base::Unretained(this)));
216  web_ui()->RegisterMessageCallback("uninstallApp",
217      base::Bind(&AppLauncherHandler::HandleUninstallApp,
218                 base::Unretained(this)));
219  web_ui()->RegisterMessageCallback("createAppShortcut",
220      base::Bind(&AppLauncherHandler::HandleCreateAppShortcut,
221                 base::Unretained(this)));
222  web_ui()->RegisterMessageCallback("reorderApps",
223      base::Bind(&AppLauncherHandler::HandleReorderApps,
224                 base::Unretained(this)));
225  web_ui()->RegisterMessageCallback("setPageIndex",
226      base::Bind(&AppLauncherHandler::HandleSetPageIndex,
227                 base::Unretained(this)));
228  web_ui()->RegisterMessageCallback("saveAppPageName",
229      base::Bind(&AppLauncherHandler::HandleSaveAppPageName,
230                 base::Unretained(this)));
231  web_ui()->RegisterMessageCallback("generateAppForLink",
232      base::Bind(&AppLauncherHandler::HandleGenerateAppForLink,
233                 base::Unretained(this)));
234  web_ui()->RegisterMessageCallback("stopShowingAppLauncherPromo",
235      base::Bind(&AppLauncherHandler::StopShowingAppLauncherPromo,
236                 base::Unretained(this)));
237  web_ui()->RegisterMessageCallback("onLearnMore",
238      base::Bind(&AppLauncherHandler::OnLearnMore,
239                 base::Unretained(this)));
240  web_ui()->RegisterMessageCallback("pageSelected", base::Bind(&NoOpCallback));
241}
242
243void AppLauncherHandler::Observe(int type,
244                                 const content::NotificationSource& source,
245                                 const content::NotificationDetails& details) {
246  if (type == chrome::NOTIFICATION_APP_INSTALLED_TO_NTP) {
247    highlight_app_id_ = *content::Details<const std::string>(details).ptr();
248    if (has_loaded_apps_)
249      SetAppToBeHighlighted();
250    return;
251  }
252
253  if (ignore_changes_ || !has_loaded_apps_)
254    return;
255
256  switch (type) {
257    case chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED: {
258      const Extension* extension =
259          content::Details<const Extension>(details).ptr();
260      if (!extension->is_app())
261        return;
262
263      if (!extensions::ui_util::ShouldDisplayInNewTabPage(
264              extension, Profile::FromWebUI(web_ui()))) {
265        return;
266      }
267
268      scoped_ptr<base::DictionaryValue> app_info(GetAppInfo(extension));
269      if (app_info.get()) {
270        visible_apps_.insert(extension->id());
271
272        ExtensionPrefs* prefs =
273            ExtensionPrefs::Get(extension_service_->profile());
274        scoped_ptr<base::FundamentalValue> highlight(
275            base::Value::CreateBooleanValue(
276                prefs->IsFromBookmark(extension->id()) &&
277                attempted_bookmark_app_install_));
278        attempted_bookmark_app_install_ = false;
279        web_ui()->CallJavascriptFunction(
280            "ntp.appAdded", *app_info, *highlight);
281      }
282
283      break;
284    }
285    case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED:
286    case chrome::NOTIFICATION_EXTENSION_UNINSTALLED_DEPRECATED: {
287      const Extension* extension = NULL;
288      bool uninstalled = false;
289      if (type == chrome::NOTIFICATION_EXTENSION_UNINSTALLED_DEPRECATED) {
290        extension = content::Details<const Extension>(details).ptr();
291        uninstalled = true;
292      } else {  // NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED
293        if (content::Details<UnloadedExtensionInfo>(details)->reason ==
294            UnloadedExtensionInfo::REASON_UNINSTALL) {
295          // Uninstalls are tracked by
296          // NOTIFICATION_EXTENSION_UNINSTALLED_DEPRECATED.
297          return;
298        }
299        extension = content::Details<extensions::UnloadedExtensionInfo>(
300            details)->extension;
301        uninstalled = false;
302      }
303      if (!extension->is_app())
304        return;
305
306      if (!extensions::ui_util::ShouldDisplayInNewTabPage(
307              extension, Profile::FromWebUI(web_ui()))) {
308        return;
309      }
310
311      scoped_ptr<base::DictionaryValue> app_info(GetAppInfo(extension));
312      if (app_info.get()) {
313        if (uninstalled)
314          visible_apps_.erase(extension->id());
315
316        scoped_ptr<base::FundamentalValue> uninstall_value(
317            base::Value::CreateBooleanValue(uninstalled));
318        scoped_ptr<base::FundamentalValue> from_page(
319            base::Value::CreateBooleanValue(!extension_id_prompting_.empty()));
320        web_ui()->CallJavascriptFunction(
321            "ntp.appRemoved", *app_info, *uninstall_value, *from_page);
322      }
323      break;
324    }
325    case chrome::NOTIFICATION_EXTENSION_LAUNCHER_REORDERED: {
326      const std::string* id =
327          content::Details<const std::string>(details).ptr();
328      if (id) {
329        const Extension* extension =
330            extension_service_->GetInstalledExtension(*id);
331        if (!extension) {
332          // Extension could still be downloading or installing.
333          return;
334        }
335
336        base::DictionaryValue app_info;
337        CreateAppInfo(extension,
338                      extension_service_,
339                      &app_info);
340        web_ui()->CallJavascriptFunction("ntp.appMoved", app_info);
341      } else {
342        HandleGetApps(NULL);
343      }
344      break;
345    }
346    case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: {
347      CrxInstaller* crx_installer = content::Source<CrxInstaller>(source).ptr();
348      if (!Profile::FromWebUI(web_ui())->IsSameProfile(
349              crx_installer->profile())) {
350        return;
351      }
352      // Fall through.
353    }
354    case chrome::NOTIFICATION_EXTENSION_LOAD_ERROR: {
355      attempted_bookmark_app_install_ = false;
356      break;
357    }
358    default:
359      NOTREACHED();
360  }
361}
362
363void AppLauncherHandler::FillAppDictionary(base::DictionaryValue* dictionary) {
364  // CreateAppInfo and ClearOrdinals can change the extension prefs.
365  base::AutoReset<bool> auto_reset(&ignore_changes_, true);
366
367  base::ListValue* list = new base::ListValue();
368  Profile* profile = Profile::FromWebUI(web_ui());
369  PrefService* prefs = profile->GetPrefs();
370
371  for (std::set<std::string>::iterator it = visible_apps_.begin();
372       it != visible_apps_.end(); ++it) {
373    const Extension* extension = extension_service_->GetInstalledExtension(*it);
374    if (extension && extensions::ui_util::ShouldDisplayInNewTabPage(
375            extension, profile)) {
376      base::DictionaryValue* app_info = GetAppInfo(extension);
377      list->Append(app_info);
378    }
379  }
380
381  dictionary->Set("apps", list);
382
383  const base::ListValue* app_page_names =
384      prefs->GetList(prefs::kNtpAppPageNames);
385  if (!app_page_names || !app_page_names->GetSize()) {
386    ListPrefUpdate update(prefs, prefs::kNtpAppPageNames);
387    base::ListValue* list = update.Get();
388    list->Set(0, new base::StringValue(
389        l10n_util::GetStringUTF16(IDS_APP_DEFAULT_PAGE_NAME)));
390    dictionary->Set("appPageNames",
391                    static_cast<base::ListValue*>(list->DeepCopy()));
392  } else {
393    dictionary->Set("appPageNames",
394                    static_cast<base::ListValue*>(app_page_names->DeepCopy()));
395  }
396}
397
398base::DictionaryValue* AppLauncherHandler::GetAppInfo(
399    const Extension* extension) {
400  base::DictionaryValue* app_info = new base::DictionaryValue();
401  // CreateAppInfo can change the extension prefs.
402  base::AutoReset<bool> auto_reset(&ignore_changes_, true);
403  CreateAppInfo(extension,
404                extension_service_,
405                app_info);
406  return app_info;
407}
408
409void AppLauncherHandler::HandleGetApps(const base::ListValue* args) {
410  base::DictionaryValue dictionary;
411
412  // Tell the client whether to show the promo for this view. We don't do this
413  // in the case of PREF_CHANGED because:
414  //
415  // a) At that point in time, depending on the pref that changed, it can look
416  //    like the set of apps installed has changed, and we will mark the promo
417  //    expired.
418  // b) Conceptually, it doesn't really make sense to count a
419  //    prefchange-triggered refresh as a promo 'view'.
420  Profile* profile = Profile::FromWebUI(web_ui());
421
422  // The first time we load the apps we must add all current app to the list
423  // of apps visible on the NTP.
424  if (!has_loaded_apps_) {
425    ExtensionRegistry* registry = ExtensionRegistry::Get(profile);
426    const ExtensionSet& enabled_set = registry->enabled_extensions();
427    for (extensions::ExtensionSet::const_iterator it = enabled_set.begin();
428         it != enabled_set.end(); ++it) {
429      visible_apps_.insert((*it)->id());
430    }
431
432    const ExtensionSet& disabled_set = registry->disabled_extensions();
433    for (ExtensionSet::const_iterator it = disabled_set.begin();
434         it != disabled_set.end(); ++it) {
435      visible_apps_.insert((*it)->id());
436    }
437
438    const ExtensionSet& terminated_set = registry->terminated_extensions();
439    for (ExtensionSet::const_iterator it = terminated_set.begin();
440         it != terminated_set.end(); ++it) {
441      visible_apps_.insert((*it)->id());
442    }
443  }
444
445  SetAppToBeHighlighted();
446  FillAppDictionary(&dictionary);
447  web_ui()->CallJavascriptFunction("ntp.getAppsCallback", dictionary);
448
449  // First time we get here we set up the observer so that we can tell update
450  // the apps as they change.
451  if (!has_loaded_apps_) {
452    base::Closure callback = base::Bind(
453        &AppLauncherHandler::OnExtensionPreferenceChanged,
454        base::Unretained(this));
455    extension_pref_change_registrar_.Init(
456        ExtensionPrefs::Get(profile)->pref_service());
457    extension_pref_change_registrar_.Add(
458        extensions::pref_names::kExtensions, callback);
459    extension_pref_change_registrar_.Add(prefs::kNtpAppPageNames, callback);
460
461    registrar_.Add(this,
462                   chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
463                   content::Source<Profile>(profile));
464    registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
465        content::Source<Profile>(profile));
466    registrar_.Add(this,
467                   chrome::NOTIFICATION_EXTENSION_UNINSTALLED_DEPRECATED,
468                   content::Source<Profile>(profile));
469    registrar_.Add(this,
470                   chrome::NOTIFICATION_EXTENSION_LAUNCHER_REORDERED,
471                   content::Source<AppSorting>(
472                       ExtensionPrefs::Get(profile)->app_sorting()));
473    registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
474        content::Source<CrxInstaller>(NULL));
475    registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOAD_ERROR,
476        content::Source<Profile>(profile));
477  }
478
479  has_loaded_apps_ = true;
480}
481
482void AppLauncherHandler::HandleLaunchApp(const base::ListValue* args) {
483  std::string extension_id;
484  CHECK(args->GetString(0, &extension_id));
485  double source = -1.0;
486  CHECK(args->GetDouble(1, &source));
487  std::string url;
488  if (args->GetSize() > 2)
489    CHECK(args->GetString(2, &url));
490
491  extension_misc::AppLaunchBucket launch_bucket =
492      static_cast<extension_misc::AppLaunchBucket>(
493          static_cast<int>(source));
494  CHECK(launch_bucket >= 0 &&
495        launch_bucket < extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
496
497  const Extension* extension =
498      extension_service_->GetExtensionById(extension_id, false);
499
500  // Prompt the user to re-enable the application if disabled.
501  if (!extension) {
502    PromptToEnableApp(extension_id);
503    return;
504  }
505
506  Profile* profile = extension_service_->profile();
507
508  WindowOpenDisposition disposition = args->GetSize() > 3 ?
509        webui::GetDispositionFromClick(args, 3) : CURRENT_TAB;
510  if (extension_id != extension_misc::kWebStoreAppId) {
511    CHECK_NE(launch_bucket, extension_misc::APP_LAUNCH_BUCKET_INVALID);
512    CoreAppLauncherHandler::RecordAppLaunchType(launch_bucket,
513                                                extension->GetType());
514  } else {
515    CoreAppLauncherHandler::RecordWebStoreLaunch();
516  }
517
518  if (disposition == NEW_FOREGROUND_TAB || disposition == NEW_BACKGROUND_TAB ||
519      disposition == NEW_WINDOW) {
520    // TODO(jamescook): Proper support for background tabs.
521    AppLaunchParams params(profile, extension,
522                           disposition == NEW_WINDOW ?
523                               extensions::LAUNCH_CONTAINER_WINDOW :
524                               extensions::LAUNCH_CONTAINER_TAB,
525                           disposition);
526    params.override_url = GURL(url);
527    OpenApplication(params);
528  } else {
529    // To give a more "launchy" experience when using the NTP launcher, we close
530    // it automatically.
531    Browser* browser = chrome::FindBrowserWithWebContents(
532        web_ui()->GetWebContents());
533    WebContents* old_contents = NULL;
534    if (browser)
535      old_contents = browser->tab_strip_model()->GetActiveWebContents();
536
537    AppLaunchParams params(profile, extension,
538                           old_contents ? CURRENT_TAB : NEW_FOREGROUND_TAB);
539    params.override_url = GURL(url);
540    WebContents* new_contents = OpenApplication(params);
541
542    // This will also destroy the handler, so do not perform any actions after.
543    if (new_contents != old_contents && browser &&
544        browser->tab_strip_model()->count() > 1) {
545      chrome::CloseWebContents(browser, old_contents, true);
546    }
547  }
548}
549
550void AppLauncherHandler::HandleSetLaunchType(const base::ListValue* args) {
551  std::string extension_id;
552  double launch_type;
553  CHECK(args->GetString(0, &extension_id));
554  CHECK(args->GetDouble(1, &launch_type));
555
556  const Extension* extension =
557      extension_service_->GetExtensionById(extension_id, true);
558  if (!extension)
559    return;
560
561  // Don't update the page; it already knows about the launch type change.
562  base::AutoReset<bool> auto_reset(&ignore_changes_, true);
563
564  extensions::SetLaunchType(
565      extension_service_,
566      extension_id,
567      static_cast<extensions::LaunchType>(static_cast<int>(launch_type)));
568}
569
570void AppLauncherHandler::HandleUninstallApp(const base::ListValue* args) {
571  std::string extension_id;
572  CHECK(args->GetString(0, &extension_id));
573
574  const Extension* extension = extension_service_->GetInstalledExtension(
575      extension_id);
576  if (!extension)
577    return;
578
579  if (!extensions::ExtensionSystem::Get(extension_service_->profile())->
580          management_policy()->UserMayModifySettings(extension, NULL)) {
581    LOG(ERROR) << "Attempt to uninstall an extension that is non-usermanagable "
582               << "was made. Extension id : " << extension->id();
583    return;
584  }
585  if (!extension_id_prompting_.empty())
586    return;  // Only one prompt at a time.
587
588  extension_id_prompting_ = extension_id;
589
590  bool dont_confirm = false;
591  if (args->GetBoolean(1, &dont_confirm) && dont_confirm) {
592    base::AutoReset<bool> auto_reset(&ignore_changes_, true);
593    ExtensionUninstallAccepted();
594  } else {
595    GetExtensionUninstallDialog()->ConfirmUninstall(extension);
596  }
597}
598
599void AppLauncherHandler::HandleCreateAppShortcut(const base::ListValue* args) {
600  std::string extension_id;
601  CHECK(args->GetString(0, &extension_id));
602
603  const Extension* extension =
604      extension_service_->GetExtensionById(extension_id, true);
605  if (!extension)
606    return;
607
608  Browser* browser = chrome::FindBrowserWithWebContents(
609        web_ui()->GetWebContents());
610  chrome::ShowCreateChromeAppShortcutsDialog(
611      browser->window()->GetNativeWindow(), browser->profile(), extension,
612      base::Callback<void(bool)>());
613}
614
615void AppLauncherHandler::HandleReorderApps(const base::ListValue* args) {
616  CHECK(args->GetSize() == 2);
617
618  std::string dragged_app_id;
619  const base::ListValue* app_order;
620  CHECK(args->GetString(0, &dragged_app_id));
621  CHECK(args->GetList(1, &app_order));
622
623  std::string predecessor_to_moved_ext;
624  std::string successor_to_moved_ext;
625  for (size_t i = 0; i < app_order->GetSize(); ++i) {
626    std::string value;
627    if (app_order->GetString(i, &value) && value == dragged_app_id) {
628      if (i > 0)
629        CHECK(app_order->GetString(i - 1, &predecessor_to_moved_ext));
630      if (i + 1 < app_order->GetSize())
631        CHECK(app_order->GetString(i + 1, &successor_to_moved_ext));
632      break;
633    }
634  }
635
636  // Don't update the page; it already knows the apps have been reordered.
637  base::AutoReset<bool> auto_reset(&ignore_changes_, true);
638  ExtensionPrefs* extension_prefs =
639      ExtensionPrefs::Get(extension_service_->GetBrowserContext());
640  extension_prefs->SetAppDraggedByUser(dragged_app_id);
641  extension_prefs->app_sorting()->OnExtensionMoved(
642      dragged_app_id, predecessor_to_moved_ext, successor_to_moved_ext);
643}
644
645void AppLauncherHandler::HandleSetPageIndex(const base::ListValue* args) {
646  AppSorting* app_sorting =
647      ExtensionPrefs::Get(extension_service_->profile())->app_sorting();
648
649  std::string extension_id;
650  double page_index;
651  CHECK(args->GetString(0, &extension_id));
652  CHECK(args->GetDouble(1, &page_index));
653  const syncer::StringOrdinal& page_ordinal =
654      app_sorting->PageIntegerAsStringOrdinal(static_cast<size_t>(page_index));
655
656  // Don't update the page; it already knows the apps have been reordered.
657  base::AutoReset<bool> auto_reset(&ignore_changes_, true);
658  app_sorting->SetPageOrdinal(extension_id, page_ordinal);
659}
660
661void AppLauncherHandler::HandleSaveAppPageName(const base::ListValue* args) {
662  base::string16 name;
663  CHECK(args->GetString(0, &name));
664
665  double page_index;
666  CHECK(args->GetDouble(1, &page_index));
667
668  base::AutoReset<bool> auto_reset(&ignore_changes_, true);
669  PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
670  ListPrefUpdate update(prefs, prefs::kNtpAppPageNames);
671  base::ListValue* list = update.Get();
672  list->Set(static_cast<size_t>(page_index), new base::StringValue(name));
673}
674
675void AppLauncherHandler::HandleGenerateAppForLink(const base::ListValue* args) {
676  std::string url;
677  CHECK(args->GetString(0, &url));
678  GURL launch_url(url);
679
680  base::string16 title;
681  CHECK(args->GetString(1, &title));
682
683  double page_index;
684  CHECK(args->GetDouble(2, &page_index));
685  AppSorting* app_sorting =
686      ExtensionPrefs::Get(extension_service_->profile())->app_sorting();
687  const syncer::StringOrdinal& page_ordinal =
688      app_sorting->PageIntegerAsStringOrdinal(static_cast<size_t>(page_index));
689
690  Profile* profile = Profile::FromWebUI(web_ui());
691  FaviconService* favicon_service =
692      FaviconServiceFactory::GetForProfile(profile, Profile::EXPLICIT_ACCESS);
693  if (!favicon_service) {
694    LOG(ERROR) << "No favicon service";
695    return;
696  }
697
698  scoped_ptr<AppInstallInfo> install_info(new AppInstallInfo());
699  install_info->title = title;
700  install_info->app_url = launch_url;
701  install_info->page_ordinal = page_ordinal;
702
703  favicon_service->GetFaviconImageForPageURL(
704      FaviconService::FaviconForPageURLParams(
705          launch_url, favicon_base::FAVICON, gfx::kFaviconSize),
706      base::Bind(&AppLauncherHandler::OnFaviconForApp,
707                 base::Unretained(this),
708                 base::Passed(&install_info)),
709      &cancelable_task_tracker_);
710}
711
712void AppLauncherHandler::StopShowingAppLauncherPromo(
713    const base::ListValue* args) {
714#if defined(ENABLE_APP_LIST)
715  g_browser_process->local_state()->SetBoolean(
716      prefs::kShowAppLauncherPromo, false);
717  RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_DISMISSED);
718#endif
719}
720
721void AppLauncherHandler::OnLearnMore(const base::ListValue* args) {
722  RecordAppLauncherPromoHistogram(apps::APP_LAUNCHER_PROMO_LEARN_MORE);
723}
724
725void AppLauncherHandler::OnFaviconForApp(
726    scoped_ptr<AppInstallInfo> install_info,
727    const favicon_base::FaviconImageResult& image_result) {
728  scoped_ptr<WebApplicationInfo> web_app(new WebApplicationInfo());
729  web_app->title = install_info->title;
730  web_app->app_url = install_info->app_url;
731
732  if (!image_result.image.IsEmpty()) {
733    WebApplicationInfo::IconInfo icon;
734    icon.data = image_result.image.AsBitmap();
735    icon.width = icon.data.width();
736    icon.height = icon.data.height();
737    web_app->icons.push_back(icon);
738  }
739
740  scoped_refptr<CrxInstaller> installer(
741      CrxInstaller::CreateSilent(extension_service_));
742  installer->set_error_on_unsupported_requirements(true);
743  installer->set_page_ordinal(install_info->page_ordinal);
744  installer->InstallWebApp(*web_app);
745  attempted_bookmark_app_install_ = true;
746}
747
748void AppLauncherHandler::SetAppToBeHighlighted() {
749  if (highlight_app_id_.empty())
750    return;
751
752  base::StringValue app_id(highlight_app_id_);
753  web_ui()->CallJavascriptFunction("ntp.setAppToBeHighlighted", app_id);
754  highlight_app_id_.clear();
755}
756
757void AppLauncherHandler::OnExtensionPreferenceChanged() {
758  base::DictionaryValue dictionary;
759  FillAppDictionary(&dictionary);
760  web_ui()->CallJavascriptFunction("ntp.appsPrefChangeCallback", dictionary);
761}
762
763void AppLauncherHandler::OnLocalStatePreferenceChanged() {
764#if defined(ENABLE_APP_LIST)
765  web_ui()->CallJavascriptFunction(
766      "ntp.appLauncherPromoPrefChangeCallback",
767      base::FundamentalValue(g_browser_process->local_state()->GetBoolean(
768          prefs::kShowAppLauncherPromo)));
769#endif
770}
771
772void AppLauncherHandler::CleanupAfterUninstall() {
773  extension_id_prompting_.clear();
774}
775
776void AppLauncherHandler::PromptToEnableApp(const std::string& extension_id) {
777  if (!extension_id_prompting_.empty())
778    return;  // Only one prompt at a time.
779
780  extension_id_prompting_ = extension_id;
781  extension_enable_flow_.reset(new ExtensionEnableFlow(
782      Profile::FromWebUI(web_ui()), extension_id, this));
783  extension_enable_flow_->StartForWebContents(web_ui()->GetWebContents());
784}
785
786void AppLauncherHandler::ExtensionUninstallAccepted() {
787  // Do the uninstall work here.
788  DCHECK(!extension_id_prompting_.empty());
789
790  // The extension can be uninstalled in another window while the UI was
791  // showing. Do nothing in that case.
792  const Extension* extension =
793      extension_service_->GetInstalledExtension(extension_id_prompting_);
794  if (!extension)
795    return;
796
797  extension_service_->UninstallExtension(extension_id_prompting_,
798                                         false /* external_uninstall */, NULL);
799  CleanupAfterUninstall();
800}
801
802void AppLauncherHandler::ExtensionUninstallCanceled() {
803  CleanupAfterUninstall();
804}
805
806void AppLauncherHandler::ExtensionEnableFlowFinished() {
807  DCHECK_EQ(extension_id_prompting_, extension_enable_flow_->extension_id());
808
809  // We bounce this off the NTP so the browser can update the apps icon.
810  // If we don't launch the app asynchronously, then the app's disabled
811  // icon disappears but isn't replaced by the enabled icon, making a poor
812  // visual experience.
813  base::StringValue app_id(extension_id_prompting_);
814  web_ui()->CallJavascriptFunction("ntp.launchAppAfterEnable", app_id);
815
816  extension_enable_flow_.reset();
817  extension_id_prompting_ = "";
818}
819
820void AppLauncherHandler::ExtensionEnableFlowAborted(bool user_initiated) {
821  DCHECK_EQ(extension_id_prompting_, extension_enable_flow_->extension_id());
822
823  // We record the histograms here because ExtensionUninstallCanceled is also
824  // called when the extension uninstall dialog is canceled.
825  const Extension* extension =
826      extension_service_->GetExtensionById(extension_id_prompting_, true);
827  std::string histogram_name = user_initiated
828                                   ? "Extensions.Permissions_ReEnableCancel2"
829                                   : "Extensions.Permissions_ReEnableAbort2";
830  ExtensionService::RecordPermissionMessagesHistogram(
831      extension, histogram_name.c_str());
832
833  extension_enable_flow_.reset();
834  CleanupAfterUninstall();
835}
836
837extensions::ExtensionUninstallDialog*
838AppLauncherHandler::GetExtensionUninstallDialog() {
839  if (!extension_uninstall_dialog_.get()) {
840    Browser* browser = chrome::FindBrowserWithWebContents(
841        web_ui()->GetWebContents());
842    extension_uninstall_dialog_.reset(
843        extensions::ExtensionUninstallDialog::Create(
844            extension_service_->profile(), browser, this));
845  }
846  return extension_uninstall_dialog_.get();
847}
848