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