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.
5 #include "chrome/browser/ui/app_list/extension_app_item.h"
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"
38 using extensions::Extension
;
42 // Overlays a shortcut icon over the bottom left corner of a given image.
43 class ShortcutOverlayImageSource
: public gfx::CanvasImageSource
{
45 explicit ShortcutOverlayImageSource(const gfx::ImageSkia
& icon
)
46 : gfx::CanvasImageSource(icon
.size(), false),
49 virtual ~ShortcutOverlayImageSource() {}
52 // gfx::CanvasImageSource overrides:
53 virtual void Draw(gfx::Canvas
* canvas
) OVERRIDE
{
54 canvas
->DrawImageInt(icon_
, 0, 0);
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());
64 DISALLOW_COPY_AND_ASSIGN(ShortcutOverlayImageSource
);
67 // Rounds the corners of a given image.
68 class RoundedCornersImageSource
: public gfx::CanvasImageSource
{
70 explicit RoundedCornersImageSource(const gfx::ImageSkia
& icon
)
71 : gfx::CanvasImageSource(icon
.size(), false),
74 virtual ~RoundedCornersImageSource() {}
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;
82 canvas
->DrawImageInt(icon_
, 0, 0);
84 scoped_ptr
<gfx::Canvas
> masking_canvas(
85 new gfx::Canvas(gfx::Size(icon_
.width(), icon_
.height()), 1.0f
, false));
86 DCHECK(masking_canvas
);
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
);
95 SkPaint masking_paint
;
96 masking_paint
.setXfermodeMode(SkXfermode::kDstIn_Mode
);
98 gfx::ImageSkia(masking_canvas
->ExtractImageRep()), 0, 0, masking_paint
);
101 gfx::ImageSkia icon_
;
103 DISALLOW_COPY_AND_ASSIGN(RoundedCornersImageSource
);
106 extensions::AppSorting
* GetAppSorting(Profile
* profile
) {
107 ExtensionService
* service
=
108 extensions::ExtensionSystem::Get(profile
)->extension_service();
109 return service
->extension_prefs()->app_sorting();
112 const color_utils::HSL shift
= {-1, 0, 0.6};
116 ExtensionAppItem::ExtensionAppItem(
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
),
125 extension_id_(extension_id
),
126 extension_enable_flow_controller_(NULL
),
127 extension_name_(extension_name
),
129 gfx::ImageSkiaOperations::CreateHSLShiftedImage(installing_icon
,
131 is_platform_app_(is_platform_app
),
132 has_overlay_(false) {
134 if (sync_item
&& sync_item
->item_ordinal
.IsValid()) {
135 // An existing synced position exists, use that.
136 set_position(sync_item
->item_ordinal
);
138 SetTitleAndFullName(sync_item
->item_name
, sync_item
->item_name
);
141 GetAppSorting(profile_
)->EnsureValidOrdinals(extension_id_
,
142 syncer::StringOrdinal());
143 UpdatePositionFromExtensionOrdering();
146 ExtensionAppItem::~ExtensionAppItem() {
149 bool 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
)
158 const ExtensionService
* service
=
159 extensions::ExtensionSystem::Get(profile_
)->extension_service();
161 extensions::LaunchType launch_type
= GetExtension()
162 ? extensions::GetLaunchType(service
->extension_prefs(), GetExtension())
163 : extensions::LAUNCH_TYPE_WINDOW
;
165 return !is_platform_app_
&& extension_id_
!= extension_misc::kChromeAppId
&&
166 (!streamlined_hosted_apps
||
167 launch_type
!= extensions::LAUNCH_TYPE_WINDOW
);
170 void ExtensionAppItem::Reload() {
171 const Extension
* extension
= GetExtension();
172 bool is_installing
= !extension
;
173 SetIsInstalling(is_installing
);
175 SetTitleAndFullName(extension_name_
, extension_name_
);
179 SetTitleAndFullName(extension
->short_name(), extension
->name());
180 LoadImage(extension
);
183 void ExtensionAppItem::UpdateIcon() {
184 gfx::ImageSkia icon
= installing_icon_
;
186 // Use the app icon if the app exists. Turn the image greyscale if the app is
188 if (GetExtension()) {
189 icon
= icon_
->image_skia();
190 const bool enabled
= extensions::util::IsAppLaunchable(extension_id_
,
193 const color_utils::HSL shift
= {-1, 0, 0.6};
194 icon
= gfx::ImageSkiaOperations::CreateHSLShiftedImage(icon
, shift
);
197 if (GetExtension()->from_bookmark())
198 icon
= gfx::ImageSkia(new RoundedCornersImageSource(icon
), icon
.size());
200 // Paint the shortcut overlay if necessary.
201 has_overlay_
= NeedsOverlay();
203 icon
= gfx::ImageSkia(new ShortcutOverlayImageSource(icon
), icon
.size());
208 void ExtensionAppItem::UpdateIconOverlay() {
209 if (has_overlay_
!= NeedsOverlay())
213 void ExtensionAppItem::Move(const ExtensionAppItem
* prev
,
214 const ExtensionAppItem
* next
) {
216 return; // No reordering necessary
218 ExtensionService
* service
=
219 extensions::ExtensionSystem::Get(profile_
)->extension_service();
220 extensions::AppSorting
* sorting
= service
->extension_prefs()->app_sorting();
222 syncer::StringOrdinal page
;
223 std::string prev_id
, next_id
;
225 next_id
= next
->extension_id();
226 page
= sorting
->GetPageOrdinal(next_id
);
228 prev_id
= prev
->extension_id();
229 page
= sorting
->GetPageOrdinal(prev_id
);
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();
237 service
->extension_prefs()->SetAppDraggedByUser(extension_id_
);
238 sorting
->SetPageOrdinal(extension_id_
, page
);
239 service
->OnExtensionMoved(extension_id_
, prev_id
, next_id
);
240 UpdatePositionFromExtensionOrdering();
243 const Extension
* ExtensionAppItem::GetExtension() const {
244 const ExtensionService
* service
=
245 extensions::ExtensionSystem::Get(profile_
)->extension_service();
246 const Extension
* extension
= service
->GetInstalledExtension(extension_id_
);
250 void ExtensionAppItem::LoadImage(const Extension
* extension
) {
251 icon_
.reset(new extensions::IconImage(
254 extensions::IconsInfo::GetIcons(extension
),
255 extension_misc::EXTENSION_ICON_MEDIUM
,
256 extensions::IconsInfo::GetDefaultAppIcon(),
261 bool ExtensionAppItem::RunExtensionEnableFlow() {
262 if (extensions::util::IsAppLaunchableWithoutEnabling(extension_id_
, profile_
))
265 if (!extension_enable_flow_
) {
266 extension_enable_flow_controller_
= GetController();
267 extension_enable_flow_controller_
->OnShowExtensionPrompt();
269 extension_enable_flow_
.reset(new ExtensionEnableFlow(
270 profile_
, extension_id_
, this));
271 extension_enable_flow_
->StartForNativeWindow(
272 extension_enable_flow_controller_
->GetAppListWindow());
277 void ExtensionAppItem::Launch(int event_flags
) {
278 // |extension| could be NULL when it is being unloaded for updating.
279 const Extension
* extension
= GetExtension();
283 if (RunExtensionEnableFlow())
286 GetController()->LaunchApp(profile_
,
288 AppListControllerDelegate::LAUNCH_FROM_APP_LIST
,
292 void ExtensionAppItem::OnExtensionIconImageChanged(
293 extensions::IconImage
* image
) {
294 DCHECK(icon_
.get() == image
);
298 void ExtensionAppItem::ExtensionEnableFlowFinished() {
299 extension_enable_flow_
.reset();
300 extension_enable_flow_controller_
->OnCloseExtensionPrompt();
301 extension_enable_flow_controller_
= NULL
;
303 // Automatically launch app after enabling.
307 void ExtensionAppItem::ExtensionEnableFlowAborted(bool user_initiated
) {
308 extension_enable_flow_
.reset();
309 extension_enable_flow_controller_
->OnCloseExtensionPrompt();
310 extension_enable_flow_controller_
= NULL
;
313 void ExtensionAppItem::Activate(int event_flags
) {
314 // |extension| could be NULL when it is being unloaded for updating.
315 const Extension
* extension
= GetExtension();
319 if (RunExtensionEnableFlow())
322 content::RecordAction(base::UserMetricsAction("AppList_ClickOnApp"));
323 CoreAppLauncherHandler::RecordAppListMainLaunch(extension
);
324 GetController()->ActivateApp(profile_
,
326 AppListControllerDelegate::LAUNCH_FROM_APP_LIST
,
330 ui::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_
);
335 context_menu_
->set_is_in_folder(true);
336 return context_menu_
->GetMenuModel();
340 const char ExtensionAppItem::kItemType
[] = "ExtensionAppItem";
342 const char* ExtensionAppItem::GetItemType() const {
343 return ExtensionAppItem::kItemType
;
346 void ExtensionAppItem::ExecuteLaunchCommand(int event_flags
) {
350 void 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()));
359 AppListControllerDelegate
* ExtensionAppItem::GetController() {
360 return AppListService::Get(chrome::GetActiveDesktop())->
361 GetControllerDelegate();