extension_app_item.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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/command_line.h"
8#include "base/prefs/pref_service.h"
9#include "chrome/browser/extensions/extension_service.h"
10#include "chrome/browser/extensions/extension_util.h"
11#include "chrome/browser/extensions/launch_util.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/app_list/app_list_service.h"
16#include "chrome/browser/ui/extensions/extension_enable_flow.h"
17#include "chrome/browser/ui/host_desktop.h"
18#include "chrome/browser/ui/webui/ntp/core_app_launcher_handler.h"
19#include "chrome/common/chrome_switches.h"
20#include "chrome/common/extensions/extension_constants.h"
21#include "chrome/common/extensions/manifest_url_handler.h"
22#include "content/public/browser/user_metrics.h"
23#include "extensions/browser/app_sorting.h"
24#include "extensions/browser/extension_prefs.h"
25#include "extensions/browser/extension_system.h"
26#include "extensions/common/extension.h"
27#include "extensions/common/extension_icon_set.h"
28#include "extensions/common/manifest_handlers/icons_handler.h"
29#include "grit/theme_resources.h"
30#include "sync/api/string_ordinal.h"
31#include "ui/base/resource/resource_bundle.h"
32#include "ui/gfx/canvas.h"
33#include "ui/gfx/color_utils.h"
34#include "ui/gfx/image/canvas_image_source.h"
35#include "ui/gfx/image/image_skia_operations.h"
36#include "ui/gfx/rect.h"
37
38using extensions::Extension;
39
40namespace {
41
42// Overlays a shortcut icon over the bottom left corner of a given image.
43class ShortcutOverlayImageSource : public gfx::CanvasImageSource {
44 public:
45  explicit ShortcutOverlayImageSource(const gfx::ImageSkia& icon)
46      : gfx::CanvasImageSource(icon.size(), false),
47        icon_(icon) {
48  }
49  virtual ~ShortcutOverlayImageSource() {}
50
51 private:
52  // gfx::CanvasImageSource overrides:
53  virtual void Draw(gfx::Canvas* canvas) OVERRIDE {
54    canvas->DrawImageInt(icon_, 0, 0);
55
56    // Draw the overlay in the bottom left corner of the icon.
57    const gfx::ImageSkia& overlay = *ui::ResourceBundle::GetSharedInstance().
58        GetImageSkiaNamed(IDR_APP_LIST_TAB_OVERLAY);
59    canvas->DrawImageInt(overlay, 0, icon_.height() - overlay.height());
60  }
61
62  gfx::ImageSkia icon_;
63
64  DISALLOW_COPY_AND_ASSIGN(ShortcutOverlayImageSource);
65};
66
67// Rounds the corners of a given image.
68class RoundedCornersImageSource : public gfx::CanvasImageSource {
69 public:
70  explicit RoundedCornersImageSource(const gfx::ImageSkia& icon)
71      : gfx::CanvasImageSource(icon.size(), false),
72        icon_(icon) {
73  }
74  virtual ~RoundedCornersImageSource() {}
75
76 private:
77  // gfx::CanvasImageSource overrides:
78  virtual void Draw(gfx::Canvas* canvas) OVERRIDE {
79    // The radius used to round the app icon.
80    const size_t kRoundingRadius = 2;
81
82    canvas->DrawImageInt(icon_, 0, 0);
83
84    scoped_ptr<gfx::Canvas> masking_canvas(
85        new gfx::Canvas(gfx::Size(icon_.width(), icon_.height()), 1.0f, false));
86    DCHECK(masking_canvas);
87
88    SkPaint opaque_paint;
89    opaque_paint.setColor(SK_ColorWHITE);
90    opaque_paint.setFlags(SkPaint::kAntiAlias_Flag);
91    masking_canvas->DrawRoundRect(
92        gfx::Rect(icon_.width(), icon_.height()),
93        kRoundingRadius, opaque_paint);
94
95    SkPaint masking_paint;
96    masking_paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
97    canvas->DrawImageInt(
98        gfx::ImageSkia(masking_canvas->ExtractImageRep()), 0, 0, masking_paint);
99  }
100
101  gfx::ImageSkia icon_;
102
103  DISALLOW_COPY_AND_ASSIGN(RoundedCornersImageSource);
104};
105
106extensions::AppSorting* GetAppSorting(Profile* profile) {
107  return extensions::ExtensionPrefs::Get(profile)->app_sorting();
108}
109
110const color_utils::HSL shift = {-1, 0, 0.6};
111
112}  // namespace
113
114ExtensionAppItem::ExtensionAppItem(
115    Profile* profile,
116    const app_list::AppListSyncableService::SyncItem* sync_item,
117    const std::string& extension_id,
118    const std::string& extension_name,
119    const gfx::ImageSkia& installing_icon,
120    bool is_platform_app)
121    : app_list::AppListItem(extension_id),
122      profile_(profile),
123      extension_id_(extension_id),
124      extension_enable_flow_controller_(NULL),
125      extension_name_(extension_name),
126      installing_icon_(
127          gfx::ImageSkiaOperations::CreateHSLShiftedImage(installing_icon,
128                                                          shift)),
129      is_platform_app_(is_platform_app),
130      has_overlay_(false) {
131  Reload();
132  if (sync_item && sync_item->item_ordinal.IsValid()) {
133    // An existing synced position exists, use that.
134    set_position(sync_item->item_ordinal);
135    // Only set the name from the sync item if it is empty.
136    if (name().empty())
137      SetName(sync_item->item_name);
138    return;
139  }
140  GetAppSorting(profile_)->EnsureValidOrdinals(extension_id_,
141                                               syncer::StringOrdinal());
142  UpdatePositionFromExtensionOrdering();
143}
144
145ExtensionAppItem::~ExtensionAppItem() {
146}
147
148bool ExtensionAppItem::NeedsOverlay() const {
149  // The overlay icon is disabled for hosted apps in windowed mode with
150  // streamlined hosted apps.
151  bool streamlined_hosted_apps = CommandLine::ForCurrentProcess()->
152      HasSwitch(switches::kEnableStreamlinedHostedApps);
153#if defined(OS_CHROMEOS)
154  if (!streamlined_hosted_apps)
155    return false;
156#endif
157  extensions::LaunchType launch_type =
158      GetExtension()
159          ? extensions::GetLaunchType(extensions::ExtensionPrefs::Get(profile_),
160                                      GetExtension())
161          : extensions::LAUNCH_TYPE_WINDOW;
162
163  return !is_platform_app_ && extension_id_ != extension_misc::kChromeAppId &&
164      (!streamlined_hosted_apps ||
165       launch_type != extensions::LAUNCH_TYPE_WINDOW);
166}
167
168void ExtensionAppItem::Reload() {
169  const Extension* extension = GetExtension();
170  bool is_installing = !extension;
171  SetIsInstalling(is_installing);
172  if (is_installing) {
173    SetName(extension_name_);
174    UpdateIcon();
175    return;
176  }
177  SetNameAndShortName(extension->name(), extension->short_name());
178  LoadImage(extension);
179}
180
181void ExtensionAppItem::UpdateIcon() {
182  gfx::ImageSkia icon = installing_icon_;
183
184  // Use the app icon if the app exists. Turn the image greyscale if the app is
185  // not launchable.
186  if (GetExtension()) {
187    icon = icon_->image_skia();
188    const bool enabled = extensions::util::IsAppLaunchable(extension_id_,
189                                                           profile_);
190    if (!enabled) {
191      const color_utils::HSL shift = {-1, 0, 0.6};
192      icon = gfx::ImageSkiaOperations::CreateHSLShiftedImage(icon, shift);
193    }
194
195    if (GetExtension()->from_bookmark())
196      icon = gfx::ImageSkia(new RoundedCornersImageSource(icon), icon.size());
197  }
198  // Paint the shortcut overlay if necessary.
199  has_overlay_ = NeedsOverlay();
200  if (has_overlay_)
201    icon = gfx::ImageSkia(new ShortcutOverlayImageSource(icon), icon.size());
202
203  SetIcon(icon, true);
204}
205
206void ExtensionAppItem::Move(const ExtensionAppItem* prev,
207                            const ExtensionAppItem* next) {
208  if (!prev && !next)
209    return;  // No reordering necessary
210
211  extensions::ExtensionPrefs* prefs = extensions::ExtensionPrefs::Get(profile_);
212  extensions::AppSorting* sorting = GetAppSorting(profile_);
213
214  syncer::StringOrdinal page;
215  std::string prev_id, next_id;
216  if (!prev) {
217    next_id = next->extension_id();
218    page = sorting->GetPageOrdinal(next_id);
219  } else if (!next) {
220    prev_id = prev->extension_id();
221    page = sorting->GetPageOrdinal(prev_id);
222  } else {
223    prev_id = prev->extension_id();
224    page = sorting->GetPageOrdinal(prev_id);
225    // Only set |next_id| if on the same page, otherwise just insert after prev.
226    if (page.Equals(sorting->GetPageOrdinal(next->extension_id())))
227      next_id = next->extension_id();
228  }
229  prefs->SetAppDraggedByUser(extension_id_);
230  sorting->SetPageOrdinal(extension_id_, page);
231  sorting->OnExtensionMoved(extension_id_, prev_id, next_id);
232  UpdatePositionFromExtensionOrdering();
233}
234
235const Extension* ExtensionAppItem::GetExtension() const {
236  const ExtensionService* service =
237      extensions::ExtensionSystem::Get(profile_)->extension_service();
238  const Extension* extension = service->GetInstalledExtension(extension_id_);
239  return extension;
240}
241
242void ExtensionAppItem::LoadImage(const Extension* extension) {
243  icon_.reset(new extensions::IconImage(
244      profile_,
245      extension,
246      extensions::IconsInfo::GetIcons(extension),
247      extension_misc::EXTENSION_ICON_MEDIUM,
248      extensions::util::GetDefaultAppIcon(),
249      this));
250  UpdateIcon();
251}
252
253bool ExtensionAppItem::RunExtensionEnableFlow() {
254  if (extensions::util::IsAppLaunchableWithoutEnabling(extension_id_, profile_))
255    return false;
256
257  if (!extension_enable_flow_) {
258    extension_enable_flow_controller_ = GetController();
259    extension_enable_flow_controller_->OnShowExtensionPrompt();
260
261    extension_enable_flow_.reset(new ExtensionEnableFlow(
262        profile_, extension_id_, this));
263    extension_enable_flow_->StartForNativeWindow(
264        extension_enable_flow_controller_->GetAppListWindow());
265  }
266  return true;
267}
268
269void ExtensionAppItem::Launch(int event_flags) {
270  // |extension| could be NULL when it is being unloaded for updating.
271  const Extension* extension = GetExtension();
272  if (!extension)
273    return;
274
275  if (RunExtensionEnableFlow())
276    return;
277
278  GetController()->LaunchApp(profile_,
279                             extension,
280                             AppListControllerDelegate::LAUNCH_FROM_APP_LIST,
281                             event_flags);
282}
283
284void ExtensionAppItem::OnExtensionIconImageChanged(
285    extensions::IconImage* image) {
286  DCHECK(icon_.get() == image);
287  UpdateIcon();
288}
289
290void ExtensionAppItem::ExtensionEnableFlowFinished() {
291  extension_enable_flow_.reset();
292  extension_enable_flow_controller_->OnCloseExtensionPrompt();
293  extension_enable_flow_controller_ = NULL;
294
295  // Automatically launch app after enabling.
296  Launch(ui::EF_NONE);
297}
298
299void ExtensionAppItem::ExtensionEnableFlowAborted(bool user_initiated) {
300  extension_enable_flow_.reset();
301  extension_enable_flow_controller_->OnCloseExtensionPrompt();
302  extension_enable_flow_controller_ = NULL;
303}
304
305void ExtensionAppItem::Activate(int event_flags) {
306  // |extension| could be NULL when it is being unloaded for updating.
307  const Extension* extension = GetExtension();
308  if (!extension)
309    return;
310
311  if (RunExtensionEnableFlow())
312    return;
313
314  content::RecordAction(base::UserMetricsAction("AppList_ClickOnApp"));
315  CoreAppLauncherHandler::RecordAppListMainLaunch(extension);
316  GetController()->ActivateApp(profile_,
317                               extension,
318                               AppListControllerDelegate::LAUNCH_FROM_APP_LIST,
319                               event_flags);
320}
321
322ui::MenuModel* ExtensionAppItem::GetContextMenuModel() {
323  context_menu_.reset(new app_list::AppContextMenu(
324      this, profile_, extension_id_, GetController()));
325  context_menu_->set_is_platform_app(is_platform_app_);
326  if (IsInFolder())
327    context_menu_->set_is_in_folder(true);
328  return context_menu_->GetMenuModel();
329}
330
331void ExtensionAppItem::OnExtensionPreferenceChanged() {
332  if (has_overlay_ != NeedsOverlay())
333    UpdateIcon();
334}
335
336// static
337const char ExtensionAppItem::kItemType[] = "ExtensionAppItem";
338
339const char* ExtensionAppItem::GetItemType() const {
340  return ExtensionAppItem::kItemType;
341}
342
343void ExtensionAppItem::ExecuteLaunchCommand(int event_flags) {
344  Launch(event_flags);
345}
346
347void ExtensionAppItem::UpdatePositionFromExtensionOrdering() {
348  const syncer::StringOrdinal& page =
349      GetAppSorting(profile_)->GetPageOrdinal(extension_id_);
350  const syncer::StringOrdinal& launch =
351     GetAppSorting(profile_)->GetAppLaunchOrdinal(extension_id_);
352  set_position(syncer::StringOrdinal(
353      page.ToInternalValue() + launch.ToInternalValue()));
354}
355
356AppListControllerDelegate* ExtensionAppItem::GetController() {
357  return AppListService::Get(chrome::GetActiveDesktop())->
358      GetControllerDelegate();
359}
360