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
42 namespace {
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.
46 const char* kPingLaunchAppByID = "record-app-launch-by-id";
47 const char* kPingLaunchWebStore = "record-webstore-launch";
48 const char* kPingLaunchAppByURL = "record-app-launch-by-url";
49
50 const UnescapeRule::Type kUnescapeRules =
51 UnescapeRule::NORMAL | UnescapeRule::URL_SPECIAL_CHARS;
52
ParseLaunchSource(const std::string & launch_source)53 extension_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
AppLauncherHandler(ExtensionService * extension_service)65 AppLauncherHandler::AppLauncherHandler(ExtensionService* extension_service)
66 : extensions_service_(extension_service),
67 promo_active_(false),
68 ignore_changes_(false) {
69 }
70
~AppLauncherHandler()71 AppLauncherHandler::~AppLauncherHandler() {}
72
73 // static
CreateAppInfo(const Extension * extension,ExtensionPrefs * prefs,DictionaryValue * value)74 void 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
HandlePing(Profile * profile,const std::string & path)121 bool AppLauncherHandler::HandlePing(Profile* profile, const std::string& path) {
122 std::vector<std::string> params;
123 base::SplitString(path, '+', ¶ms);
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
Attach(WebUI * web_ui)161 WebUIMessageHandler* AppLauncherHandler::Attach(WebUI* web_ui) {
162 // TODO(arv): Add initialization code to the Apps store etc.
163 return WebUIMessageHandler::Attach(web_ui);
164 }
165
RegisterMessages()166 void 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
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)187 void 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
FillAppDictionary(DictionaryValue * dictionary)217 void 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
FillPromoDictionary(DictionaryValue * dictionary)260 void 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
HandleGetApps(const ListValue * args)267 void 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
HandleLaunchApp(const ListValue * args)324 void 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
HandleSetLaunchType(const ListValue * args)404 void 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
HandleUninstallApp(const ListValue * args)420 void 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
HandleHideAppsPromo(const ListValue * args)439 void 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
HandleCreateAppShortcut(const ListValue * args)450 void 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
HandleReorderApps(const ListValue * args)468 void 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
HandleSetPageIndex(const ListValue * args)487 void 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
HandlePromoSeen(const ListValue * args)497 void 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
RecordWebStoreLaunch(bool promo_active)504 void 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
RecordAppLaunchByID(bool promo_active,extension_misc::AppLaunchBucket bucket)517 void 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
RecordAppLaunchByURL(Profile * profile,std::string escaped_url,extension_misc::AppLaunchBucket bucket)532 void 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
PromptToEnableApp(const std::string & extension_id)547 void 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
ExtensionDialogAccepted()570 void 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
ExtensionDialogCanceled()587 void 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
InstallUIProceed()596 void 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
InstallUIAbort()619 void AppLauncherHandler::InstallUIAbort() {
620 ExtensionDialogCanceled();
621 }
622
GetExtensionUninstallDialog()623 ExtensionUninstallDialog* 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
GetExtensionInstallUI()631 ExtensionInstallUI* 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
UninstallDefaultApps()639 void 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