extension_app_item.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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/extension_icon_set.h"
22#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
23#include "chrome/common/extensions/manifest_url_handler.h"
24#include "content/public/browser/user_metrics.h"
25#include "extensions/browser/app_sorting.h"
26#include "extensions/browser/extension_prefs.h"
27#include "extensions/browser/extension_system.h"
28#include "extensions/common/extension.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  ExtensionService* service =
108      extensions::ExtensionSystem::Get(profile)->extension_service();
109  return service->extension_prefs()->app_sorting();
110}
111
112const color_utils::HSL shift = {-1, 0, 0.6};
113
114}  // namespace
115
116ExtensionAppItem::ExtensionAppItem(
117    Profile* profile,
118    const app_list::AppListSyncableService::SyncItem* sync_item,
119    const std::string& extension_id,
120    const std::string& extension_name,
121    const gfx::ImageSkia& installing_icon,
122    bool is_platform_app)
123    : app_list::AppListItem(extension_id),
124      profile_(profile),
125      extension_id_(extension_id),
126      extension_enable_flow_controller_(NULL),
127      extension_name_(extension_name),
128      installing_icon_(
129          gfx::ImageSkiaOperations::CreateHSLShiftedImage(installing_icon,
130                                                          shift)),
131      is_platform_app_(is_platform_app),
132      has_overlay_(false) {
133  Reload();
134  if (sync_item && sync_item->item_ordinal.IsValid()) {
135    // An existing synced position exists, use that.
136    set_position(sync_item->item_ordinal);
137    if (title().empty())
138      SetTitleAndFullName(sync_item->item_name, sync_item->item_name);
139    return;
140  }
141  GetAppSorting(profile_)->EnsureValidOrdinals(extension_id_,
142                                               syncer::StringOrdinal());
143  UpdatePositionFromExtensionOrdering();
144}
145
146ExtensionAppItem::~ExtensionAppItem() {
147}
148
149bool ExtensionAppItem::NeedsOverlay() const {
150  // The overlay icon is disabled for hosted apps in windowed mode with
151  // streamlined hosted apps.
152  bool streamlined_hosted_apps = CommandLine::ForCurrentProcess()->
153      HasSwitch(switches::kEnableStreamlinedHostedApps);
154#if defined(OS_CHROMEOS)
155  if (!streamlined_hosted_apps)
156    return false;
157#endif
158  const ExtensionService* service =
159      extensions::ExtensionSystem::Get(profile_)->extension_service();
160
161  extensions::LaunchType launch_type = GetExtension()
162      ? extensions::GetLaunchType(service->extension_prefs(), GetExtension())
163      : extensions::LAUNCH_TYPE_WINDOW;
164
165  return !is_platform_app_ && extension_id_ != extension_misc::kChromeAppId &&
166      (!streamlined_hosted_apps ||
167       launch_type != extensions::LAUNCH_TYPE_WINDOW);
168}
169
170void ExtensionAppItem::Reload() {
171  const Extension* extension = GetExtension();
172  bool is_installing = !extension;
173  SetIsInstalling(is_installing);
174  if (is_installing) {
175    SetTitleAndFullName(extension_name_, extension_name_);
176    UpdateIcon();
177    return;
178  }
179  SetTitleAndFullName(extension->short_name(), extension->name());
180  LoadImage(extension);
181}
182
183void ExtensionAppItem::UpdateIcon() {
184  gfx::ImageSkia icon = installing_icon_;
185
186  // Use the app icon if the app exists. Turn the image greyscale if the app is
187  // not launchable.
188  if (GetExtension()) {
189    icon = icon_->image_skia();
190    const bool enabled = extensions::util::IsAppLaunchable(extension_id_,
191                                                           profile_);
192    if (!enabled) {
193      const color_utils::HSL shift = {-1, 0, 0.6};
194      icon = gfx::ImageSkiaOperations::CreateHSLShiftedImage(icon, shift);
195    }
196
197    if (GetExtension()->from_bookmark())
198      icon = gfx::ImageSkia(new RoundedCornersImageSource(icon), icon.size());
199  }
200  // Paint the shortcut overlay if necessary.
201  has_overlay_ = NeedsOverlay();
202  if (has_overlay_)
203    icon = gfx::ImageSkia(new ShortcutOverlayImageSource(icon), icon.size());
204
205  SetIcon(icon, true);
206}
207
208void ExtensionAppItem::UpdateIconOverlay() {
209  if (has_overlay_ != NeedsOverlay())
210    UpdateIcon();
211}
212
213void ExtensionAppItem::Move(const ExtensionAppItem* prev,
214                            const ExtensionAppItem* next) {
215  if (!prev && !next)
216    return;  // No reordering necessary
217
218  ExtensionService* service =
219      extensions::ExtensionSystem::Get(profile_)->extension_service();
220  extensions::AppSorting* sorting = service->extension_prefs()->app_sorting();
221
222  syncer::StringOrdinal page;
223  std::string prev_id, next_id;
224  if (!prev) {
225    next_id = next->extension_id();
226    page = sorting->GetPageOrdinal(next_id);
227  } else if (!next) {
228    prev_id = prev->extension_id();
229    page = sorting->GetPageOrdinal(prev_id);
230  } else {
231    prev_id = prev->extension_id();
232    page = sorting->GetPageOrdinal(prev_id);
233    // Only set |next_id| if on the same page, otherwise just insert after prev.
234    if (page.Equals(sorting->GetPageOrdinal(next->extension_id())))
235      next_id = next->extension_id();
236  }
237  service->extension_prefs()->SetAppDraggedByUser(extension_id_);
238  sorting->SetPageOrdinal(extension_id_, page);
239  service->OnExtensionMoved(extension_id_, prev_id, next_id);
240  UpdatePositionFromExtensionOrdering();
241}
242
243const Extension* ExtensionAppItem::GetExtension() const {
244  const ExtensionService* service =
245      extensions::ExtensionSystem::Get(profile_)->extension_service();
246  const Extension* extension = service->GetInstalledExtension(extension_id_);
247  return extension;
248}
249
250void ExtensionAppItem::LoadImage(const Extension* extension) {
251  icon_.reset(new extensions::IconImage(
252      profile_,
253      extension,
254      extensions::IconsInfo::GetIcons(extension),
255      extension_misc::EXTENSION_ICON_MEDIUM,
256      extensions::IconsInfo::GetDefaultAppIcon(),
257      this));
258  UpdateIcon();
259}
260
261bool ExtensionAppItem::RunExtensionEnableFlow() {
262  if (extensions::util::IsAppLaunchableWithoutEnabling(extension_id_, profile_))
263    return false;
264
265  if (!extension_enable_flow_) {
266    extension_enable_flow_controller_ = GetController();
267    extension_enable_flow_controller_->OnShowExtensionPrompt();
268
269    extension_enable_flow_.reset(new ExtensionEnableFlow(
270        profile_, extension_id_, this));
271    extension_enable_flow_->StartForNativeWindow(
272        extension_enable_flow_controller_->GetAppListWindow());
273  }
274  return true;
275}
276
277void ExtensionAppItem::Launch(int event_flags) {
278  // |extension| could be NULL when it is being unloaded for updating.
279  const Extension* extension = GetExtension();
280  if (!extension)
281    return;
282
283  if (RunExtensionEnableFlow())
284    return;
285
286  GetController()->LaunchApp(profile_,
287                             extension,
288                             AppListControllerDelegate::LAUNCH_FROM_APP_LIST,
289                             event_flags);
290}
291
292void ExtensionAppItem::OnExtensionIconImageChanged(
293    extensions::IconImage* image) {
294  DCHECK(icon_.get() == image);
295  UpdateIcon();
296}
297
298void ExtensionAppItem::ExtensionEnableFlowFinished() {
299  extension_enable_flow_.reset();
300  extension_enable_flow_controller_->OnCloseExtensionPrompt();
301  extension_enable_flow_controller_ = NULL;
302
303  // Automatically launch app after enabling.
304  Launch(ui::EF_NONE);
305}
306
307void ExtensionAppItem::ExtensionEnableFlowAborted(bool user_initiated) {
308  extension_enable_flow_.reset();
309  extension_enable_flow_controller_->OnCloseExtensionPrompt();
310  extension_enable_flow_controller_ = NULL;
311}
312
313void ExtensionAppItem::Activate(int event_flags) {
314  // |extension| could be NULL when it is being unloaded for updating.
315  const Extension* extension = GetExtension();
316  if (!extension)
317    return;
318
319  if (RunExtensionEnableFlow())
320    return;
321
322  content::RecordAction(base::UserMetricsAction("AppList_ClickOnApp"));
323  CoreAppLauncherHandler::RecordAppListMainLaunch(extension);
324  GetController()->ActivateApp(profile_,
325                               extension,
326                               AppListControllerDelegate::LAUNCH_FROM_APP_LIST,
327                               event_flags);
328}
329
330ui::MenuModel* ExtensionAppItem::GetContextMenuModel() {
331  context_menu_.reset(new app_list::AppContextMenu(
332      this, profile_, extension_id_, GetController()));
333  context_menu_->set_is_platform_app(is_platform_app_);
334  if (IsInFolder())
335    context_menu_->set_is_in_folder(true);
336  return context_menu_->GetMenuModel();
337}
338
339// static
340const char ExtensionAppItem::kItemType[] = "ExtensionAppItem";
341
342const char* ExtensionAppItem::GetItemType() const {
343  return ExtensionAppItem::kItemType;
344}
345
346void ExtensionAppItem::ExecuteLaunchCommand(int event_flags) {
347  Launch(event_flags);
348}
349
350void ExtensionAppItem::UpdatePositionFromExtensionOrdering() {
351  const syncer::StringOrdinal& page =
352      GetAppSorting(profile_)->GetPageOrdinal(extension_id_);
353  const syncer::StringOrdinal& launch =
354     GetAppSorting(profile_)->GetAppLaunchOrdinal(extension_id_);
355  set_position(syncer::StringOrdinal(
356      page.ToInternalValue() + launch.ToInternalValue()));
357}
358
359AppListControllerDelegate* ExtensionAppItem::GetController() {
360  return AppListService::Get(chrome::GetActiveDesktop())->
361      GetControllerDelegate();
362}
363