1// Copyright (c) 2011 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/app_launcher_handler.h"
6
7#include <string>
8#include <vector>
9
10#include "base/metrics/histogram.h"
11#include "base/string_number_conversions.h"
12#include "base/string_split.h"
13#include "base/string_util.h"
14#include "base/utf_string_conversions.h"
15#include "base/values.h"
16#include "chrome/browser/extensions/apps_promo.h"
17#include "chrome/browser/extensions/extension_prefs.h"
18#include "chrome/browser/extensions/extension_service.h"
19#include "chrome/browser/platform_util.h"
20#include "chrome/browser/profiles/profile.h"
21#include "chrome/browser/ui/browser.h"
22#include "chrome/browser/ui/browser_list.h"
23#include "chrome/browser/ui/browser_window.h"
24#include "chrome/browser/ui/webui/extension_icon_source.h"
25#include "chrome/browser/ui/webui/shown_sections_handler.h"
26#include "chrome/common/extensions/extension.h"
27#include "chrome/common/extensions/extension_constants.h"
28#include "chrome/common/extensions/extension_icon_set.h"
29#include "chrome/common/extensions/extension_resource.h"
30#include "chrome/common/url_constants.h"
31#include "content/browser/disposition_utils.h"
32#include "content/browser/tab_contents/tab_contents.h"
33#include "content/common/notification_service.h"
34#include "content/common/notification_type.h"
35#include "googleurl/src/gurl.h"
36#include "grit/browser_resources.h"
37#include "grit/generated_resources.h"
38#include "net/base/escape.h"
39#include "ui/base/animation/animation.h"
40#include "webkit/glue/window_open_disposition.h"
41
42namespace {
43
44// The URL prefixes used by the NTP to signal when the web store or an app
45// has launched so we can record the proper histogram.
46const char* kPingLaunchAppByID = "record-app-launch-by-id";
47const char* kPingLaunchWebStore = "record-webstore-launch";
48const char* kPingLaunchAppByURL = "record-app-launch-by-url";
49
50const UnescapeRule::Type kUnescapeRules =
51    UnescapeRule::NORMAL | UnescapeRule::URL_SPECIAL_CHARS;
52
53extension_misc::AppLaunchBucket ParseLaunchSource(
54    const std::string& launch_source) {
55  int bucket_num = extension_misc::APP_LAUNCH_BUCKET_INVALID;
56  base::StringToInt(launch_source, &bucket_num);
57  extension_misc::AppLaunchBucket bucket =
58      static_cast<extension_misc::AppLaunchBucket>(bucket_num);
59  CHECK(bucket < extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
60  return bucket;
61}
62
63}  // namespace
64
65AppLauncherHandler::AppLauncherHandler(ExtensionService* extension_service)
66    : extensions_service_(extension_service),
67      promo_active_(false),
68      ignore_changes_(false) {
69}
70
71AppLauncherHandler::~AppLauncherHandler() {}
72
73// static
74void AppLauncherHandler::CreateAppInfo(const Extension* extension,
75                                       ExtensionPrefs* prefs,
76                                       DictionaryValue* value) {
77  bool enabled =
78      prefs->GetExtensionState(extension->id()) != Extension::DISABLED;
79  GURL icon_big =
80      ExtensionIconSource::GetIconURL(extension,
81                                      Extension::EXTENSION_ICON_LARGE,
82                                      ExtensionIconSet::MATCH_EXACTLY,
83                                      !enabled);
84  GURL icon_small =
85      ExtensionIconSource::GetIconURL(extension,
86                                      Extension::EXTENSION_ICON_BITTY,
87                                      ExtensionIconSet::MATCH_BIGGER,
88                                      !enabled);
89
90  value->Clear();
91  value->SetString("id", extension->id());
92  value->SetString("name", extension->name());
93  value->SetString("description", extension->description());
94  value->SetString("launch_url", extension->GetFullLaunchURL().spec());
95  value->SetString("options_url", extension->options_url().spec());
96  value->SetBoolean("can_uninstall",
97                    Extension::UserMayDisable(extension->location()));
98  value->SetString("icon_big", icon_big.spec());
99  value->SetString("icon_small", icon_small.spec());
100  value->SetInteger("launch_container", extension->launch_container());
101  value->SetInteger("launch_type",
102      prefs->GetLaunchType(extension->id(),
103                                     ExtensionPrefs::LAUNCH_DEFAULT));
104
105  int app_launch_index = prefs->GetAppLaunchIndex(extension->id());
106  if (app_launch_index == -1) {
107    // Make sure every app has a launch index (some predate the launch index).
108    app_launch_index = prefs->GetNextAppLaunchIndex();
109    prefs->SetAppLaunchIndex(extension->id(), app_launch_index);
110  }
111  value->SetInteger("app_launch_index", app_launch_index);
112
113  int page_index = prefs->GetPageIndex(extension->id());
114  if (page_index >= 0) {
115    // Only provide a value if one is stored
116    value->SetInteger("page_index", page_index);
117  }
118}
119
120// static
121bool AppLauncherHandler::HandlePing(Profile* profile, const std::string& path) {
122  std::vector<std::string> params;
123  base::SplitString(path, '+', &params);
124
125  // Check if the user launched an app from the most visited or recently
126  // closed sections.
127  if (kPingLaunchAppByURL == params.at(0)) {
128    CHECK(params.size() == 3);
129    RecordAppLaunchByURL(
130        profile, params.at(1), ParseLaunchSource(params.at(2)));
131    return true;
132  }
133
134  bool is_web_store_ping = kPingLaunchWebStore == params.at(0);
135  bool is_app_launch_ping = kPingLaunchAppByID == params.at(0);
136
137  if (!is_web_store_ping && !is_app_launch_ping)
138    return false;
139
140  CHECK(params.size() >= 2);
141
142  bool is_promo_active = params.at(1) == "true";
143
144  // At this point, the user must have used the app launcher, so we hide the
145  // promo if its still displayed.
146  if (is_promo_active) {
147    DCHECK(profile->GetExtensionService());
148    profile->GetExtensionService()->apps_promo()->ExpireDefaultApps();
149  }
150
151  if (is_web_store_ping) {
152    RecordWebStoreLaunch(is_promo_active);
153  }  else {
154    CHECK(params.size() == 3);
155    RecordAppLaunchByID(is_promo_active, ParseLaunchSource(params.at(2)));
156  }
157
158  return true;
159}
160
161WebUIMessageHandler* AppLauncherHandler::Attach(WebUI* web_ui) {
162  // TODO(arv): Add initialization code to the Apps store etc.
163  return WebUIMessageHandler::Attach(web_ui);
164}
165
166void AppLauncherHandler::RegisterMessages() {
167  web_ui_->RegisterMessageCallback("getApps",
168      NewCallback(this, &AppLauncherHandler::HandleGetApps));
169  web_ui_->RegisterMessageCallback("launchApp",
170      NewCallback(this, &AppLauncherHandler::HandleLaunchApp));
171  web_ui_->RegisterMessageCallback("setLaunchType",
172      NewCallback(this, &AppLauncherHandler::HandleSetLaunchType));
173  web_ui_->RegisterMessageCallback("uninstallApp",
174      NewCallback(this, &AppLauncherHandler::HandleUninstallApp));
175  web_ui_->RegisterMessageCallback("hideAppsPromo",
176      NewCallback(this, &AppLauncherHandler::HandleHideAppsPromo));
177  web_ui_->RegisterMessageCallback("createAppShortcut",
178      NewCallback(this, &AppLauncherHandler::HandleCreateAppShortcut));
179  web_ui_->RegisterMessageCallback("reorderApps",
180      NewCallback(this, &AppLauncherHandler::HandleReorderApps));
181  web_ui_->RegisterMessageCallback("setPageIndex",
182      NewCallback(this, &AppLauncherHandler::HandleSetPageIndex));
183  web_ui_->RegisterMessageCallback("promoSeen",
184      NewCallback(this, &AppLauncherHandler::HandlePromoSeen));
185}
186
187void AppLauncherHandler::Observe(NotificationType type,
188                                 const NotificationSource& source,
189                                 const NotificationDetails& details) {
190  if (ignore_changes_)
191    return;
192
193  switch (type.value) {
194    case NotificationType::EXTENSION_LOADED:
195    case NotificationType::EXTENSION_UNLOADED:
196    case NotificationType::EXTENSION_LAUNCHER_REORDERED:
197    // The promo may not load until a couple seconds after the first NTP view,
198    // so we listen for the load notification and notify the NTP when ready.
199    case NotificationType::WEB_STORE_PROMO_LOADED:
200      if (web_ui_->tab_contents())
201        HandleGetApps(NULL);
202      break;
203    case NotificationType::PREF_CHANGED: {
204      if (!web_ui_->tab_contents())
205        break;
206
207      DictionaryValue dictionary;
208      FillAppDictionary(&dictionary);
209      web_ui_->CallJavascriptFunction("appsPrefChangeCallback", dictionary);
210      break;
211    }
212    default:
213      NOTREACHED();
214  }
215}
216
217void AppLauncherHandler::FillAppDictionary(DictionaryValue* dictionary) {
218  ListValue* list = new ListValue();
219  const ExtensionList* extensions = extensions_service_->extensions();
220  ExtensionList::const_iterator it;
221  for (it = extensions->begin(); it != extensions->end(); ++it) {
222    // Don't include the WebStore and other component apps.
223    // The WebStore launcher gets special treatment in ntp/apps.js.
224    if ((*it)->is_app() && (*it)->location() != Extension::COMPONENT) {
225      DictionaryValue* app_info = new DictionaryValue();
226      CreateAppInfo(*it, extensions_service_->extension_prefs(), app_info);
227      list->Append(app_info);
228    }
229  }
230
231  extensions = extensions_service_->disabled_extensions();
232  for (it = extensions->begin(); it != extensions->end(); ++it) {
233    if ((*it)->is_app() && (*it)->location() != Extension::COMPONENT) {
234      DictionaryValue* app_info = new DictionaryValue();
235      CreateAppInfo(*it, extensions_service_->extension_prefs(), app_info);
236      list->Append(app_info);
237    }
238  }
239
240  dictionary->Set("apps", list);
241
242#if defined(OS_MACOSX)
243  // App windows are not yet implemented on mac.
244  dictionary->SetBoolean("disableAppWindowLaunch", true);
245  dictionary->SetBoolean("disableCreateAppShortcut", true);
246#endif
247
248#if defined(OS_CHROMEOS)
249  // Making shortcut does not make sense on ChromeOS because it does not have
250  // a desktop.
251  dictionary->SetBoolean("disableCreateAppShortcut", true);
252#endif
253
254  dictionary->SetBoolean(
255      "showLauncher",
256      extensions_service_->apps_promo()->ShouldShowAppLauncher(
257          extensions_service_->GetAppIds()));
258}
259
260void AppLauncherHandler::FillPromoDictionary(DictionaryValue* dictionary) {
261  dictionary->SetString("promoHeader", AppsPromo::GetPromoHeaderText());
262  dictionary->SetString("promoButton", AppsPromo::GetPromoButtonText());
263  dictionary->SetString("promoLink", AppsPromo::GetPromoLink().spec());
264  dictionary->SetString("promoExpire", AppsPromo::GetPromoExpireText());
265}
266
267void AppLauncherHandler::HandleGetApps(const ListValue* args) {
268  DictionaryValue dictionary;
269
270  // Tell the client whether to show the promo for this view. We don't do this
271  // in the case of PREF_CHANGED because:
272  //
273  // a) At that point in time, depending on the pref that changed, it can look
274  //    like the set of apps installed has changed, and we will mark the promo
275  //    expired.
276  // b) Conceptually, it doesn't really make sense to count a
277  //    prefchange-triggered refresh as a promo 'view'.
278  AppsPromo* apps_promo = extensions_service_->apps_promo();
279  PrefService* prefs = web_ui_->GetProfile()->GetPrefs();
280  bool apps_promo_just_expired = false;
281  if (apps_promo->ShouldShowPromo(extensions_service_->GetAppIds(),
282                                  &apps_promo_just_expired)) {
283    // Maximize the apps section on the first promo view.
284    apps_promo->MaximizeAppsIfFirstView();
285    dictionary.SetBoolean("showPromo", true);
286    FillPromoDictionary(&dictionary);
287    promo_active_ = true;
288  } else {
289    dictionary.SetBoolean("showPromo", false);
290    promo_active_ = false;
291  }
292
293  // If the default apps have just expired (user viewed them too many times with
294  // no interaction), then we uninstall them and focus the recent sites section.
295  if (apps_promo_just_expired) {
296    ignore_changes_ = true;
297    UninstallDefaultApps();
298    ignore_changes_ = false;
299    ShownSectionsHandler::SetShownSection(prefs, THUMB);
300  }
301
302  FillAppDictionary(&dictionary);
303  web_ui_->CallJavascriptFunction("getAppsCallback", dictionary);
304
305  // First time we get here we set up the observer so that we can tell update
306  // the apps as they change.
307  if (registrar_.IsEmpty()) {
308    registrar_.Add(this, NotificationType::EXTENSION_LOADED,
309        NotificationService::AllSources());
310    registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
311        NotificationService::AllSources());
312    registrar_.Add(this, NotificationType::EXTENSION_LAUNCHER_REORDERED,
313        NotificationService::AllSources());
314    registrar_.Add(this, NotificationType::WEB_STORE_PROMO_LOADED,
315        NotificationService::AllSources());
316  }
317  if (pref_change_registrar_.IsEmpty()) {
318    pref_change_registrar_.Init(
319        extensions_service_->extension_prefs()->pref_service());
320    pref_change_registrar_.Add(ExtensionPrefs::kExtensionsPref, this);
321  }
322}
323
324void AppLauncherHandler::HandleLaunchApp(const ListValue* args) {
325  std::string extension_id;
326  double source = -1.0;
327  bool alt_key = false;
328  bool ctrl_key = false;
329  bool meta_key = false;
330  bool shift_key = false;
331  double button = 0.0;
332
333  CHECK(args->GetString(0, &extension_id));
334  CHECK(args->GetDouble(1, &source));
335  if (args->GetSize() > 2) {
336      CHECK(args->GetBoolean(2, &alt_key));
337      CHECK(args->GetBoolean(3, &ctrl_key));
338      CHECK(args->GetBoolean(4, &meta_key));
339      CHECK(args->GetBoolean(5, &shift_key));
340      CHECK(args->GetDouble(6, &button));
341  }
342
343  extension_misc::AppLaunchBucket launch_bucket =
344      static_cast<extension_misc::AppLaunchBucket>(
345          static_cast<int>(source));
346  CHECK(launch_bucket >= 0 &&
347        launch_bucket < extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
348
349  const Extension* extension =
350      extensions_service_->GetExtensionById(extension_id, false);
351
352  // Prompt the user to re-enable the application if disabled.
353  if (!extension) {
354    PromptToEnableApp(extension_id);
355    return;
356  }
357
358  Profile* profile = extensions_service_->profile();
359
360  // If the user pressed special keys when clicking, override the saved
361  // preference for launch container.
362  bool middle_button = (button == 1.0);
363  WindowOpenDisposition disposition =
364        disposition_utils::DispositionFromClick(middle_button, alt_key,
365                                                ctrl_key, meta_key, shift_key);
366
367  if (extension_id != extension_misc::kWebStoreAppId) {
368    RecordAppLaunchByID(promo_active_, launch_bucket);
369    extensions_service_->apps_promo()->ExpireDefaultApps();
370  }
371
372  if (disposition == NEW_FOREGROUND_TAB || disposition == NEW_BACKGROUND_TAB) {
373    // TODO(jamescook): Proper support for background tabs.
374    Browser::OpenApplication(
375        profile, extension, extension_misc::LAUNCH_TAB, NULL);
376  } else if (disposition == NEW_WINDOW) {
377    // Force a new window open.
378    Browser::OpenApplication(
379            profile, extension, extension_misc::LAUNCH_WINDOW, NULL);
380  } else {
381    // Look at preference to find the right launch container.  If no preference
382    // is set, launch as a regular tab.
383    extension_misc::LaunchContainer launch_container =
384        extensions_service_->extension_prefs()->GetLaunchContainer(
385            extension, ExtensionPrefs::LAUNCH_REGULAR);
386
387    // To give a more "launchy" experience when using the NTP launcher, we close
388    // it automatically.
389    Browser* browser = BrowserList::GetLastActive();
390    TabContents* old_contents = NULL;
391    if (browser)
392      old_contents = browser->GetSelectedTabContents();
393
394    TabContents* new_contents = Browser::OpenApplication(
395        profile, extension, launch_container, old_contents);
396
397    // This will also destroy the handler, so do not perform any actions after.
398    if (new_contents != old_contents && browser->tab_count() > 1)
399      browser->CloseTabContents(old_contents);
400  }
401
402}
403
404void AppLauncherHandler::HandleSetLaunchType(const ListValue* args) {
405  std::string extension_id;
406  double launch_type;
407  CHECK(args->GetString(0, &extension_id));
408  CHECK(args->GetDouble(1, &launch_type));
409
410  const Extension* extension =
411      extensions_service_->GetExtensionById(extension_id, true);
412  CHECK(extension);
413
414  extensions_service_->extension_prefs()->SetLaunchType(
415      extension_id,
416      static_cast<ExtensionPrefs::LaunchType>(
417          static_cast<int>(launch_type)));
418}
419
420void AppLauncherHandler::HandleUninstallApp(const ListValue* args) {
421  std::string extension_id = UTF16ToUTF8(ExtractStringValue(args));
422  const Extension* extension = extensions_service_->GetExtensionById(
423      extension_id, false);
424  if (!extension)
425    return;
426
427  if (!Extension::UserMayDisable(extension->location())) {
428    LOG(ERROR) << "Attempt to uninstall an extension that is non-usermanagable "
429               << "was made. Extension id : " << extension->id();
430    return;
431  }
432  if (!extension_id_prompting_.empty())
433    return;  // Only one prompt at a time.
434
435  extension_id_prompting_ = extension_id;
436  GetExtensionUninstallDialog()->ConfirmUninstall(this, extension);
437}
438
439void AppLauncherHandler::HandleHideAppsPromo(const ListValue* args) {
440  // If the user has intentionally hidden the promotion, we'll uninstall all the
441  // default apps (we know the user hasn't installed any apps on their own at
442  // this point, or the promotion wouldn't have been shown).
443  ignore_changes_ = true;
444  UninstallDefaultApps();
445  extensions_service_->apps_promo()->HidePromo();
446  ignore_changes_ = false;
447  HandleGetApps(NULL);
448}
449
450void AppLauncherHandler::HandleCreateAppShortcut(const ListValue* args) {
451  std::string extension_id;
452  if (!args->GetString(0, &extension_id)) {
453    NOTREACHED();
454    return;
455  }
456
457  const Extension* extension =
458      extensions_service_->GetExtensionById(extension_id, true);
459  CHECK(extension);
460
461  Browser* browser = BrowserList::GetLastActive();
462  if (!browser)
463    return;
464  browser->window()->ShowCreateChromeAppShortcutsDialog(
465      browser->profile(), extension);
466}
467
468void AppLauncherHandler::HandleReorderApps(const ListValue* args) {
469  CHECK(args->GetSize() == 2);
470
471  std::string dragged_app_id;
472  ListValue* app_order;
473  CHECK(args->GetString(0, &dragged_app_id));
474  CHECK(args->GetList(1, &app_order));
475
476  std::vector<std::string> extension_ids;
477  for (size_t i = 0; i < app_order->GetSize(); ++i) {
478    std::string value;
479    if (app_order->GetString(i, &value))
480      extension_ids.push_back(value);
481  }
482
483  extensions_service_->extension_prefs()->SetAppDraggedByUser(dragged_app_id);
484  extensions_service_->extension_prefs()->SetAppLauncherOrder(extension_ids);
485}
486
487void AppLauncherHandler::HandleSetPageIndex(const ListValue* args) {
488  std::string extension_id;
489  double page_index;
490  CHECK(args->GetString(0, &extension_id));
491  CHECK(args->GetDouble(1, &page_index));
492
493  extensions_service_->extension_prefs()->SetPageIndex(extension_id,
494      static_cast<int>(page_index));
495}
496
497void AppLauncherHandler::HandlePromoSeen(const ListValue* args) {
498  UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppsPromoHistogram,
499                            extension_misc::PROMO_SEEN,
500                            extension_misc::PROMO_BUCKET_BOUNDARY);
501}
502
503// static
504void AppLauncherHandler::RecordWebStoreLaunch(bool promo_active) {
505  UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppLaunchHistogram,
506                            extension_misc::APP_LAUNCH_NTP_WEBSTORE,
507                            extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
508
509  if (!promo_active) return;
510
511  UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppsPromoHistogram,
512                            extension_misc::PROMO_LAUNCH_WEB_STORE,
513                            extension_misc::PROMO_BUCKET_BOUNDARY);
514}
515
516// static
517void AppLauncherHandler::RecordAppLaunchByID(
518    bool promo_active, extension_misc::AppLaunchBucket bucket) {
519  CHECK(bucket != extension_misc::APP_LAUNCH_BUCKET_INVALID);
520
521  UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppLaunchHistogram, bucket,
522                            extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
523
524  if (!promo_active) return;
525
526  UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppsPromoHistogram,
527                            extension_misc::PROMO_LAUNCH_APP,
528                            extension_misc::PROMO_BUCKET_BOUNDARY);
529}
530
531// static
532void AppLauncherHandler::RecordAppLaunchByURL(
533    Profile* profile,
534    std::string escaped_url,
535    extension_misc::AppLaunchBucket bucket) {
536  CHECK(bucket != extension_misc::APP_LAUNCH_BUCKET_INVALID);
537
538  GURL url(UnescapeURLComponent(escaped_url, kUnescapeRules));
539  DCHECK(profile->GetExtensionService());
540  if (!profile->GetExtensionService()->IsInstalledApp(url))
541    return;
542
543  UMA_HISTOGRAM_ENUMERATION(extension_misc::kAppLaunchHistogram, bucket,
544                            extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
545}
546
547void AppLauncherHandler::PromptToEnableApp(const std::string& extension_id) {
548  const Extension* extension =
549      extensions_service_->GetExtensionById(extension_id, true);
550  CHECK(extension);
551
552  ExtensionPrefs* extension_prefs = extensions_service_->extension_prefs();
553  if (!extension_prefs->DidExtensionEscalatePermissions(extension_id)) {
554    // Enable the extension immediately if its privileges weren't escalated.
555    extensions_service_->EnableExtension(extension_id);
556
557    // Launch app asynchronously so the image will update.
558    StringValue* app_id = Value::CreateStringValue(extension->id());
559    web_ui_->CallJavascriptFunction("launchAppAfterEnable", *app_id);
560    return;
561  }
562
563  if (!extension_id_prompting_.empty())
564    return;  // Only one prompt at a time.
565
566  extension_id_prompting_ = extension_id;
567  GetExtensionInstallUI()->ConfirmReEnable(this, extension);
568}
569
570void AppLauncherHandler::ExtensionDialogAccepted() {
571  // Do the uninstall work here.
572  DCHECK(!extension_id_prompting_.empty());
573
574  // The extension can be uninstalled in another window while the UI was
575  // showing. Do nothing in that case.
576  const Extension* extension =
577      extensions_service_->GetExtensionById(extension_id_prompting_, true);
578  if (!extension)
579    return;
580
581  extensions_service_->UninstallExtension(extension_id_prompting_,
582                                          false /* external_uninstall */, NULL);
583
584  extension_id_prompting_ = "";
585}
586
587void AppLauncherHandler::ExtensionDialogCanceled() {
588  const Extension* extension =
589      extensions_service_->GetExtensionById(extension_id_prompting_, true);
590  ExtensionService::RecordPermissionMessagesHistogram(
591      extension, "Extensions.Permissions_ReEnableCancel");
592
593  extension_id_prompting_ = "";
594}
595
596void AppLauncherHandler::InstallUIProceed() {
597  // Do the re-enable work here.
598  DCHECK(!extension_id_prompting_.empty());
599
600  // The extension can be uninstalled in another window while the UI was
601  // showing. Do nothing in that case.
602  const Extension* extension =
603      extensions_service_->GetExtensionById(extension_id_prompting_, true);
604  if (!extension)
605    return;
606
607  extensions_service_->GrantPermissionsAndEnableExtension(extension);
608
609  // We bounce this off the NTP so the browser can update the apps icon.
610  // If we don't launch the app asynchronously, then the app's disabled
611  // icon disappears but isn't replaced by the enabled icon, making a poor
612  // visual experience.
613  StringValue* app_id = Value::CreateStringValue(extension->id());
614  web_ui_->CallJavascriptFunction("launchAppAfterEnable", *app_id);
615
616  extension_id_prompting_ = "";
617}
618
619void AppLauncherHandler::InstallUIAbort() {
620  ExtensionDialogCanceled();
621}
622
623ExtensionUninstallDialog* AppLauncherHandler::GetExtensionUninstallDialog() {
624  if (!extension_uninstall_dialog_.get()) {
625    extension_uninstall_dialog_.reset(
626        new ExtensionUninstallDialog(web_ui_->GetProfile()));
627  }
628  return extension_uninstall_dialog_.get();
629}
630
631ExtensionInstallUI* AppLauncherHandler::GetExtensionInstallUI() {
632  if (!extension_install_ui_.get()) {
633    extension_install_ui_.reset(
634        new ExtensionInstallUI(web_ui_->GetProfile()));
635  }
636  return extension_install_ui_.get();
637}
638
639void AppLauncherHandler::UninstallDefaultApps() {
640  AppsPromo* apps_promo = extensions_service_->apps_promo();
641  const ExtensionIdSet& app_ids = apps_promo->old_default_apps();
642  for (ExtensionIdSet::const_iterator iter = app_ids.begin();
643       iter != app_ids.end(); ++iter) {
644    if (extensions_service_->GetExtensionById(*iter, true))
645      extensions_service_->UninstallExtension(*iter, false, NULL);
646  }
647}
648