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 38 using extensions::Extension; 39 40 namespace { 41 42 // Overlays a shortcut icon over the bottom left corner of a given image. 43 class 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. 68 class 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 106 extensions::AppSorting* GetAppSorting(Profile* profile) { 107 return extensions::ExtensionPrefs::Get(profile)->app_sorting(); 108 } 109 110 const color_utils::HSL shift = {-1, 0, 0.6}; 111 112 } // namespace 113 114 ExtensionAppItem::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 145 ExtensionAppItem::~ExtensionAppItem() { 146 } 147 148 bool 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 168 void 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 181 void 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 206 void 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 235 const 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 242 void 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 253 bool 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_->OnShowChildDialog(); 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 269 void 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 284 void ExtensionAppItem::OnExtensionIconImageChanged( 285 extensions::IconImage* image) { 286 DCHECK(icon_.get() == image); 287 UpdateIcon(); 288 } 289 290 void ExtensionAppItem::ExtensionEnableFlowFinished() { 291 extension_enable_flow_.reset(); 292 extension_enable_flow_controller_->OnCloseChildDialog(); 293 extension_enable_flow_controller_ = NULL; 294 295 // Automatically launch app after enabling. 296 Launch(ui::EF_NONE); 297 } 298 299 void ExtensionAppItem::ExtensionEnableFlowAborted(bool user_initiated) { 300 extension_enable_flow_.reset(); 301 extension_enable_flow_controller_->OnCloseChildDialog(); 302 extension_enable_flow_controller_ = NULL; 303 } 304 305 void 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 322 ui::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 331 void ExtensionAppItem::OnExtensionPreferenceChanged() { 332 if (has_overlay_ != NeedsOverlay()) 333 UpdateIcon(); 334 } 335 336 // static 337 const char ExtensionAppItem::kItemType[] = "ExtensionAppItem"; 338 339 const char* ExtensionAppItem::GetItemType() const { 340 return ExtensionAppItem::kItemType; 341 } 342 343 void ExtensionAppItem::ExecuteLaunchCommand(int event_flags) { 344 Launch(event_flags); 345 } 346 347 void 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 356 AppListControllerDelegate* ExtensionAppItem::GetController() { 357 return AppListService::Get(chrome::GetActiveDesktop())-> 358 GetControllerDelegate(); 359 } 360