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