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