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