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