extension_app_item.cc revision 1e9bf3e0803691d0a228da41fc608347b6db4340
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/app_list/extension_app_item.h"
6
7#include "base/prefs/pref_service.h"
8#include "chrome/browser/extensions/extension_prefs.h"
9#include "chrome/browser/extensions/extension_service.h"
10#include "chrome/browser/extensions/extension_sorting.h"
11#include "chrome/browser/extensions/extension_system.h"
12#include "chrome/browser/profiles/profile.h"
13#include "chrome/browser/ui/app_list/app_context_menu.h"
14#include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
15#include "chrome/browser/ui/extensions/extension_enable_flow.h"
16#include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
17#include "chrome/common/extensions/extension.h"
18#include "chrome/common/extensions/extension_constants.h"
19#include "chrome/common/extensions/extension_icon_set.h"
20#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
21#include "chrome/common/extensions/manifest_url_handler.h"
22#include "content/public/browser/user_metrics.h"
23#include "grit/theme_resources.h"
24#include "sync/api/string_ordinal.h"
25#include "ui/base/resource/resource_bundle.h"
26#include "ui/gfx/canvas.h"
27#include "ui/gfx/color_utils.h"
28#include "ui/gfx/image/canvas_image_source.h"
29#include "ui/gfx/image/image_skia_operations.h"
30
31using extensions::Extension;
32
33namespace {
34
35// Overlays a shortcut icon over the bottom left corner of a given image.
36class ShortcutOverlayImageSource : public gfx::CanvasImageSource {
37 public:
38  explicit ShortcutOverlayImageSource(const gfx::ImageSkia& icon)
39      : gfx::CanvasImageSource(icon.size(), false),
40        icon_(icon) {
41  }
42  virtual ~ShortcutOverlayImageSource() {}
43
44 private:
45  // gfx::CanvasImageSource overrides:
46  virtual void Draw(gfx::Canvas* canvas) OVERRIDE {
47    canvas->DrawImageInt(icon_, 0, 0);
48
49    // Draw the overlay in the bottom left corner of the icon.
50    const gfx::ImageSkia& overlay = *ui::ResourceBundle::GetSharedInstance().
51        GetImageSkiaNamed(IDR_APP_LIST_TAB_OVERLAY);
52    canvas->DrawImageInt(overlay, 0, icon_.height() - overlay.height());
53  }
54
55  gfx::ImageSkia icon_;
56
57  DISALLOW_COPY_AND_ASSIGN(ShortcutOverlayImageSource);
58};
59
60ExtensionSorting* GetExtensionSorting(Profile* profile) {
61  ExtensionService* service =
62      extensions::ExtensionSystem::Get(profile)->extension_service();
63  return service->extension_prefs()->extension_sorting();
64}
65
66const color_utils::HSL shift = {-1, 0, 0.6};
67
68}  // namespace
69
70ExtensionAppItem::ExtensionAppItem(Profile* profile,
71                                   const std::string& extension_id,
72                                   AppListControllerDelegate* controller,
73                                   const std::string& extension_name,
74                                   const gfx::ImageSkia& installing_icon,
75                                   bool is_platform_app)
76    : app_list::AppListItemModel(extension_id),
77      profile_(profile),
78      extension_id_(extension_id),
79      controller_(controller),
80      extension_name_(extension_name),
81      installing_icon_(
82          gfx::ImageSkiaOperations::CreateHSLShiftedImage(installing_icon,
83                                                          shift)),
84      is_platform_app_(is_platform_app) {
85  Reload();
86  GetExtensionSorting(profile_)->EnsureValidOrdinals(extension_id_,
87                                                     syncer::StringOrdinal());
88  UpdatePositionFromExtensionOrdering();
89}
90
91ExtensionAppItem::~ExtensionAppItem() {
92}
93
94bool ExtensionAppItem::HasOverlay() const {
95#if defined(OS_CHROMEOS)
96  return false;
97#else
98  return !is_platform_app_ && extension_id_ != extension_misc::kChromeAppId;
99#endif
100}
101
102void ExtensionAppItem::Reload() {
103  const Extension* extension = GetExtension();
104  bool is_installing = !extension;
105  SetIsInstalling(is_installing);
106  if (is_installing) {
107    SetTitleAndFullName(extension_name_, extension_name_);
108    UpdateIcon();
109    return;
110  }
111  SetTitleAndFullName(extension->short_name(), extension->name());
112  LoadImage(extension);
113}
114
115void ExtensionAppItem::UpdateIcon() {
116  if (!GetExtension()) {
117    gfx::ImageSkia icon = installing_icon_;
118    if (HasOverlay())
119      icon = gfx::ImageSkia(new ShortcutOverlayImageSource(icon), icon.size());
120    SetIcon(icon, !HasOverlay());
121    return;
122  }
123  gfx::ImageSkia icon = icon_->image_skia();
124
125  const ExtensionService* service =
126      extensions::ExtensionSystem::Get(profile_)->extension_service();
127  const bool enabled = service->IsExtensionEnabledForLauncher(extension_id_);
128  if (!enabled) {
129    const color_utils::HSL shift = {-1, 0, 0.6};
130    icon = gfx::ImageSkiaOperations::CreateHSLShiftedImage(icon, shift);
131  }
132
133  if (HasOverlay())
134    icon = gfx::ImageSkia(new ShortcutOverlayImageSource(icon), icon.size());
135
136  SetIcon(icon, !HasOverlay());
137}
138
139void ExtensionAppItem::Move(const ExtensionAppItem* prev,
140                            const ExtensionAppItem* next) {
141  if (!prev && !next)
142    return;  // No reordering necessary
143
144  ExtensionService* service =
145      extensions::ExtensionSystem::Get(profile_)->extension_service();
146  ExtensionSorting* sorting = service->extension_prefs()->extension_sorting();
147
148  syncer::StringOrdinal page;
149  std::string prev_id, next_id;
150  if (!prev) {
151    next_id = next->extension_id();
152    page = sorting->GetPageOrdinal(next_id);
153  } else if (!next) {
154    prev_id = prev->extension_id();
155    page = sorting->GetPageOrdinal(prev_id);
156  } else {
157    prev_id = prev->extension_id();
158    page = sorting->GetPageOrdinal(prev_id);
159    // Only set |next_id| if on the same page, otherwise just insert after prev.
160    if (page.Equals(sorting->GetPageOrdinal(next->extension_id())))
161      next_id = next->extension_id();
162  }
163  service->extension_prefs()->SetAppDraggedByUser(extension_id_);
164  sorting->SetPageOrdinal(extension_id_, page);
165  service->OnExtensionMoved(extension_id_, prev_id, next_id);
166  UpdatePositionFromExtensionOrdering();
167}
168
169const Extension* ExtensionAppItem::GetExtension() const {
170  const ExtensionService* service =
171      extensions::ExtensionSystem::Get(profile_)->extension_service();
172  const Extension* extension = service->GetInstalledExtension(extension_id_);
173  return extension;
174}
175
176void ExtensionAppItem::LoadImage(const Extension* extension) {
177  icon_.reset(new extensions::IconImage(
178      profile_,
179      extension,
180      extensions::IconsInfo::GetIcons(extension),
181      extension_misc::EXTENSION_ICON_MEDIUM,
182      extensions::IconsInfo::GetDefaultAppIcon(),
183      this));
184  UpdateIcon();
185}
186
187bool ExtensionAppItem::RunExtensionEnableFlow() {
188  const ExtensionService* service =
189      extensions::ExtensionSystem::Get(profile_)->extension_service();
190  if (service->IsExtensionEnabledForLauncher(extension_id_))
191    return false;
192
193  if (!extension_enable_flow_) {
194    controller_->OnShowExtensionPrompt();
195
196    extension_enable_flow_.reset(new ExtensionEnableFlow(
197        profile_, extension_id_, this));
198    extension_enable_flow_->StartForNativeWindow(
199        controller_->GetAppListWindow());
200  }
201  return true;
202}
203
204void ExtensionAppItem::Launch(int event_flags) {
205  // |extension| could be NULL when it is being unloaded for updating.
206  const Extension* extension = GetExtension();
207  if (!extension)
208    return;
209
210  if (RunExtensionEnableFlow())
211    return;
212
213  controller_->LaunchApp(profile_,
214                         extension,
215                         AppListControllerDelegate::LAUNCH_FROM_APP_LIST,
216                         event_flags);
217}
218
219void ExtensionAppItem::OnExtensionIconImageChanged(
220    extensions::IconImage* image) {
221  DCHECK(icon_.get() == image);
222  UpdateIcon();
223}
224
225void ExtensionAppItem::ExtensionEnableFlowFinished() {
226  extension_enable_flow_.reset();
227  controller_->OnCloseExtensionPrompt();
228
229  // Automatically launch app after enabling.
230  Launch(ui::EF_NONE);
231}
232
233void ExtensionAppItem::ExtensionEnableFlowAborted(bool user_initiated) {
234  extension_enable_flow_.reset();
235  controller_->OnCloseExtensionPrompt();
236}
237
238void ExtensionAppItem::Activate(int event_flags) {
239  // |extension| could be NULL when it is being unloaded for updating.
240  const Extension* extension = GetExtension();
241  if (!extension)
242    return;
243
244  if (RunExtensionEnableFlow())
245    return;
246
247  content::RecordAction(content::UserMetricsAction("AppList_ClickOnApp"));
248  CoreAppLauncherHandler::RecordAppListMainLaunch(extension);
249  controller_->ActivateApp(profile_,
250                           extension,
251                           AppListControllerDelegate::LAUNCH_FROM_APP_LIST,
252                           event_flags);
253}
254
255ui::MenuModel* ExtensionAppItem::GetContextMenuModel() {
256  if (!context_menu_) {
257    context_menu_.reset(new app_list::AppContextMenu(
258        this, profile_, extension_id_, controller_, is_platform_app_, false));
259  }
260
261  return context_menu_->GetMenuModel();
262}
263
264// static
265const char ExtensionAppItem::kAppType[] = "ExtensionAppItem";
266
267const char* ExtensionAppItem::GetAppType() const {
268  return ExtensionAppItem::kAppType;
269}
270
271void ExtensionAppItem::ExecuteLaunchCommand(int event_flags) {
272  Launch(event_flags);
273}
274
275void ExtensionAppItem::UpdatePositionFromExtensionOrdering() {
276  const syncer::StringOrdinal& page =
277      GetExtensionSorting(profile_)->GetPageOrdinal(extension_id_);
278  const syncer::StringOrdinal& launch =
279     GetExtensionSorting(profile_)->GetAppLaunchOrdinal(extension_id_);
280  set_position(syncer::StringOrdinal(
281      page.ToInternalValue() + launch.ToInternalValue()));
282}
283