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