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