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/prefs/pref_service.h"
8 #include "chrome/browser/extensions/extension_service.h"
9 #include "chrome/browser/extensions/extension_util.h"
10 #include "chrome/browser/extensions/launch_util.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/ui/app_list/app_context_menu.h"
13 #include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
14 #include "chrome/browser/ui/app_list/app_list_service.h"
15 #include "chrome/browser/ui/extensions/extension_enable_flow.h"
16 #include "chrome/browser/ui/host_desktop.h"
17 #include "chrome/common/extensions/extension_constants.h"
18 #include "chrome/common/extensions/extension_metrics.h"
19 #include "content/public/browser/user_metrics.h"
20 #include "extensions/browser/app_sorting.h"
21 #include "extensions/browser/extension_prefs.h"
22 #include "extensions/browser/extension_system.h"
23 #include "extensions/common/extension.h"
24 #include "extensions/common/extension_icon_set.h"
25 #include "extensions/common/manifest_handlers/icons_handler.h"
26 #include "extensions/common/manifest_url_handlers.h"
27 #include "grit/theme_resources.h"
28 #include "sync/api/string_ordinal.h"
29 #include "ui/base/resource/resource_bundle.h"
30 #include "ui/gfx/canvas.h"
31 #include "ui/gfx/color_utils.h"
32 #include "ui/gfx/geometry/rect.h"
33 #include "ui/gfx/image/canvas_image_source.h"
34 #include "ui/gfx/image/image_skia_operations.h"
36 using extensions::Extension
;
40 // Overlays a shortcut icon over the bottom left corner of a given image.
41 class ShortcutOverlayImageSource
: public gfx::CanvasImageSource
{
43 explicit ShortcutOverlayImageSource(const gfx::ImageSkia
& icon
)
44 : gfx::CanvasImageSource(icon
.size(), false),
47 ~ShortcutOverlayImageSource() override
{}
50 // gfx::CanvasImageSource overrides:
51 void Draw(gfx::Canvas
* canvas
) override
{
52 canvas
->DrawImageInt(icon_
, 0, 0);
54 // Draw the overlay in the bottom left corner of the icon.
55 const gfx::ImageSkia
& overlay
= *ui::ResourceBundle::GetSharedInstance().
56 GetImageSkiaNamed(IDR_APP_LIST_TAB_OVERLAY
);
57 canvas
->DrawImageInt(overlay
, 0, icon_
.height() - overlay
.height());
62 DISALLOW_COPY_AND_ASSIGN(ShortcutOverlayImageSource
);
65 // Rounds the corners of a given image.
66 class RoundedCornersImageSource
: public gfx::CanvasImageSource
{
68 explicit RoundedCornersImageSource(const gfx::ImageSkia
& icon
)
69 : gfx::CanvasImageSource(icon
.size(), false),
72 ~RoundedCornersImageSource() override
{}
75 // gfx::CanvasImageSource overrides:
76 void Draw(gfx::Canvas
* canvas
) override
{
77 // The radius used to round the app icon.
78 const size_t kRoundingRadius
= 2;
80 canvas
->DrawImageInt(icon_
, 0, 0);
82 scoped_ptr
<gfx::Canvas
> masking_canvas(
83 new gfx::Canvas(gfx::Size(icon_
.width(), icon_
.height()), 1.0f
, false));
84 DCHECK(masking_canvas
);
87 opaque_paint
.setColor(SK_ColorWHITE
);
88 opaque_paint
.setFlags(SkPaint::kAntiAlias_Flag
);
89 masking_canvas
->DrawRoundRect(
90 gfx::Rect(icon_
.width(), icon_
.height()),
91 kRoundingRadius
, opaque_paint
);
93 SkPaint masking_paint
;
94 masking_paint
.setXfermodeMode(SkXfermode::kDstIn_Mode
);
96 gfx::ImageSkia(masking_canvas
->ExtractImageRep()), 0, 0, masking_paint
);
101 DISALLOW_COPY_AND_ASSIGN(RoundedCornersImageSource
);
104 extensions::AppSorting
* GetAppSorting(Profile
* profile
) {
105 return extensions::ExtensionPrefs::Get(profile
)->app_sorting();
108 gfx::ImageSkia
CreateDisabledIcon(const gfx::ImageSkia
& icon
) {
109 const color_utils::HSL shift
= {-1, 0, 0.6};
110 return gfx::ImageSkiaOperations::CreateHSLShiftedImage(icon
, shift
);
115 ExtensionAppItem::ExtensionAppItem(
117 const app_list::AppListSyncableService::SyncItem
* sync_item
,
118 const std::string
& extension_id
,
119 const std::string
& extension_name
,
120 const gfx::ImageSkia
& installing_icon
,
121 bool is_platform_app
)
122 : app_list::AppListItem(extension_id
),
124 extension_id_(extension_id
),
125 extension_enable_flow_controller_(NULL
),
126 extension_name_(extension_name
),
127 installing_icon_(CreateDisabledIcon(installing_icon
)),
128 is_platform_app_(is_platform_app
),
129 has_overlay_(false) {
131 if (sync_item
&& sync_item
->item_ordinal
.IsValid()) {
132 // An existing synced position exists, use that.
133 set_position(sync_item
->item_ordinal
);
134 // Only set the name from the sync item if it is empty.
136 SetName(sync_item
->item_name
);
139 GetAppSorting(profile_
)->EnsureValidOrdinals(extension_id_
,
140 syncer::StringOrdinal());
141 UpdatePositionFromExtensionOrdering();
144 ExtensionAppItem::~ExtensionAppItem() {
147 bool ExtensionAppItem::NeedsOverlay() const {
148 #if defined(OS_CHROMEOS)
149 // The overlay is disabled completely in ChromeOS.
153 extensions::LaunchType launch_type
=
155 ? extensions::GetLaunchType(extensions::ExtensionPrefs::Get(profile_
),
157 : extensions::LAUNCH_TYPE_WINDOW
;
159 // The overlay icon is disabled for hosted apps in windowed mode with
160 // bookmark apps enabled.
161 return !is_platform_app_
&& extension_id_
!= extension_misc::kChromeAppId
&&
162 (!extensions::util::IsNewBookmarkAppsEnabled() ||
163 launch_type
!= extensions::LAUNCH_TYPE_WINDOW
);
166 void ExtensionAppItem::Reload() {
167 const Extension
* extension
= GetExtension();
168 bool is_installing
= !extension
;
169 SetIsInstalling(is_installing
);
171 SetName(extension_name_
);
175 SetNameAndShortName(extension
->name(), extension
->short_name());
176 LoadImage(extension
);
179 void ExtensionAppItem::UpdateIcon() {
180 gfx::ImageSkia icon
= installing_icon_
;
182 // Use the app icon if the app exists. Turn the image greyscale if the app is
184 if (GetExtension() && icon_
) {
185 icon
= icon_
->image_skia();
186 const bool enabled
= extensions::util::IsAppLaunchable(extension_id_
,
189 icon
= CreateDisabledIcon(icon
);
191 if (GetExtension()->from_bookmark())
192 icon
= gfx::ImageSkia(new RoundedCornersImageSource(icon
), icon
.size());
194 // Paint the shortcut overlay if necessary.
195 has_overlay_
= NeedsOverlay();
197 icon
= gfx::ImageSkia(new ShortcutOverlayImageSource(icon
), icon
.size());
202 void ExtensionAppItem::Move(const ExtensionAppItem
* prev
,
203 const ExtensionAppItem
* next
) {
205 return; // No reordering necessary
207 extensions::ExtensionPrefs
* prefs
= extensions::ExtensionPrefs::Get(profile_
);
208 extensions::AppSorting
* sorting
= GetAppSorting(profile_
);
210 syncer::StringOrdinal page
;
211 std::string prev_id
, next_id
;
213 next_id
= next
->extension_id();
214 page
= sorting
->GetPageOrdinal(next_id
);
216 prev_id
= prev
->extension_id();
217 page
= sorting
->GetPageOrdinal(prev_id
);
219 prev_id
= prev
->extension_id();
220 page
= sorting
->GetPageOrdinal(prev_id
);
221 // Only set |next_id| if on the same page, otherwise just insert after prev.
222 if (page
.Equals(sorting
->GetPageOrdinal(next
->extension_id())))
223 next_id
= next
->extension_id();
225 prefs
->SetAppDraggedByUser(extension_id_
);
226 sorting
->SetPageOrdinal(extension_id_
, page
);
227 sorting
->OnExtensionMoved(extension_id_
, prev_id
, next_id
);
228 UpdatePositionFromExtensionOrdering();
231 const Extension
* ExtensionAppItem::GetExtension() const {
232 const ExtensionService
* service
=
233 extensions::ExtensionSystem::Get(profile_
)->extension_service();
234 const Extension
* extension
= service
->GetInstalledExtension(extension_id_
);
238 void ExtensionAppItem::LoadImage(const Extension
* extension
) {
239 icon_
.reset(new extensions::IconImage(
242 extensions::IconsInfo::GetIcons(extension
),
243 extension_misc::EXTENSION_ICON_MEDIUM
,
244 extensions::util::GetDefaultAppIcon(),
249 bool ExtensionAppItem::RunExtensionEnableFlow() {
250 if (extensions::util::IsAppLaunchableWithoutEnabling(extension_id_
, profile_
))
253 if (!extension_enable_flow_
) {
254 extension_enable_flow_controller_
= GetController();
255 extension_enable_flow_controller_
->OnShowChildDialog();
257 extension_enable_flow_
.reset(new ExtensionEnableFlow(
258 profile_
, extension_id_
, this));
259 extension_enable_flow_
->StartForNativeWindow(
260 extension_enable_flow_controller_
->GetAppListWindow());
265 void ExtensionAppItem::Launch(int event_flags
) {
266 // |extension| could be NULL when it is being unloaded for updating.
267 const Extension
* extension
= GetExtension();
271 // Don't auto-enable apps that cannot be launched.
272 if (!extensions::util::IsAppLaunchable(extension_id_
, profile_
))
275 if (RunExtensionEnableFlow())
278 GetController()->LaunchApp(profile_
,
280 AppListControllerDelegate::LAUNCH_FROM_APP_LIST
,
284 void ExtensionAppItem::OnExtensionIconImageChanged(
285 extensions::IconImage
* image
) {
286 DCHECK(icon_
.get() == image
);
290 void ExtensionAppItem::ExtensionEnableFlowFinished() {
291 extension_enable_flow_
.reset();
292 extension_enable_flow_controller_
->OnCloseChildDialog();
293 extension_enable_flow_controller_
= NULL
;
295 // Automatically launch app after enabling.
299 void ExtensionAppItem::ExtensionEnableFlowAborted(bool user_initiated
) {
300 extension_enable_flow_
.reset();
301 extension_enable_flow_controller_
->OnCloseChildDialog();
302 extension_enable_flow_controller_
= NULL
;
305 void ExtensionAppItem::Activate(int event_flags
) {
306 // |extension| could be NULL when it is being unloaded for updating.
307 const Extension
* extension
= GetExtension();
311 // Don't auto-enable apps that cannot be launched.
312 if (!extensions::util::IsAppLaunchable(extension_id_
, profile_
))
315 if (RunExtensionEnableFlow())
318 content::RecordAction(base::UserMetricsAction("AppList_ClickOnApp"));
319 extensions::RecordAppListMainLaunch(extension
);
320 GetController()->ActivateApp(profile_
,
322 AppListControllerDelegate::LAUNCH_FROM_APP_LIST
,
326 ui::MenuModel
* ExtensionAppItem::GetContextMenuModel() {
327 context_menu_
.reset(new app_list::AppContextMenu(
328 this, profile_
, extension_id_
, GetController()));
329 context_menu_
->set_is_platform_app(is_platform_app_
);
331 context_menu_
->set_is_in_folder(true);
332 return context_menu_
->GetMenuModel();
335 void ExtensionAppItem::OnExtensionPreferenceChanged() {
336 if (has_overlay_
!= NeedsOverlay())
341 const char ExtensionAppItem::kItemType
[] = "ExtensionAppItem";
343 const char* ExtensionAppItem::GetItemType() const {
344 return ExtensionAppItem::kItemType
;
347 void ExtensionAppItem::ExecuteLaunchCommand(int event_flags
) {
351 void ExtensionAppItem::UpdatePositionFromExtensionOrdering() {
352 const syncer::StringOrdinal
& page
=
353 GetAppSorting(profile_
)->GetPageOrdinal(extension_id_
);
354 const syncer::StringOrdinal
& launch
=
355 GetAppSorting(profile_
)->GetAppLaunchOrdinal(extension_id_
);
356 set_position(syncer::StringOrdinal(
357 page
.ToInternalValue() + launch
.ToInternalValue()));
360 AppListControllerDelegate
* ExtensionAppItem::GetController() {
361 return AppListService::Get(chrome::GetActiveDesktop())->
362 GetControllerDelegate();