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